oracle-databasenumberssqldatatypesoracle12cvirtual-column

Concatenating numbers in virtual column expression throws ORA-12899: value too large for column


While I gave this answer to a question yesterday, I suggested to use a VIRTUAL COLUMN for computed values instead of manually updating it.

I did a test myself, and figured out an issue with the data size that the virtual column expression takes while concatenating two NUMBER type columns. Though, no issue while concatenating two characters.

DB version :

SQL> select banner from v$version where rownum = 1;

BANNER
--------------------------------------------------------------------------------
Oracle Database 12c Enterprise Edition Release 12.1.0.1.0 - 64bit Production

SQL>

Test case 1 : Concatenating strings

SQL> CREATE TABLE t(
  2  ID varchar2(2),
  3  num varchar2(2),
  4  text VARCHAR2(10) generated always as (id||'_'||num) VIRTUAL
  5  );

Table created.

SQL>
SQL> INSERT INTO t(ID, num) VALUES ('a', 'e');

1 row created.

SQL> INSERT INTO t(ID, num) VALUES ('b', 'f');

1 row created.

SQL> INSERT INTO t(ID, num) VALUES ('c', 'g');

1 row created.

SQL>
SQL> SELECT * FROM T;

ID NU TEXT
-- -- ----------
a  e  a_e
b  f  b_f
c  g  c_g

SQL>

So, no issues with concatenating two character type columns.

Test case 2 : concatenating numbers

SQL> CREATE TABLE t(
  2  ID NUMBER,
  3  num NUMBER,
  4  text VARCHAR2(10) generated always as (to_char(id)||'_'||to_char(num)) VIRTUAL
  5  );
text VARCHAR2(10) generated always as (to_char(id)||'_'||to_char(num)) VIRTUAL
*
ERROR at line 4:
ORA-12899: value too large for column "TEXT" (actual: 10, maximum: 81)

Not allowed? Huh! Let's increase the size -

SQL> CREATE TABLE t(
  2  ID NUMBER,
  3  num NUMBER,
  4  text VARCHAR2(81) generated always as (to_char(id)||'_'||to_char(num)) VIRTUAL
  5  );

Table created.

SQL>
SQL> INSERT INTO t(ID, num) VALUES (1, 4);

1 row created.

SQL> INSERT INTO t(ID, num) VALUES (2, 5);

1 row created.

SQL> INSERT INTO t(ID, num) VALUES (3, 6);

1 row created.

SQL>
SQL> SELECT * FROM T;

        ID        NUM
---------- ----------
TEXT
--------------------------------------------------------------------------------
         1          4
1_4

         2          5
2_5

         3          6
3_6


SQL> set linesize 200
SQL> SELECT * FROM T;

        ID        NUM TEXT
---------- ---------- ----------------------------------------------------------------------------------------------------
         1          4 1_4
         2          5 2_5
         3          6 3_6

SQL>

So what happened now? Table got created, but why VIRTUAL COLUMN occupies that much size when the expected data size is just 3 bytes, however it takes 81 bytes.

Checking the length, value is correct, however, the data size is much larger. For example, I expect the length to be 3, so I declare the size of the column as 10 bytes. But the virtual column expression yields the value with a size much more than that.

SQL> CREATE TABLE t(
  2  ID NUMBER,
  3  num NUMBER,
  4  text VARCHAR2(10) generated always as (length(to_char(id)||'_'||to_char(num))) VIRTUAL
  5  );
text VARCHAR2(10) generated always as (length(to_char(id)||'_'||to_char(num))) VIRTUAL
*
ERROR at line 4:
ORA-12899: value too large for column "TEXT" (actual: 10, maximum: 40)


SQL>
SQL> CREATE TABLE t(
  2  ID NUMBER,
  3  num NUMBER,
  4  text VARCHAR2(81) generated always as (length(to_char(id)||'_'||to_char(num))) VIRTUAL
  5  );

Table created.

SQL>
SQL> INSERT INTO t(ID, num) VALUES (1, 4);

1 row created.

SQL> INSERT INTO t(ID, num) VALUES (2, 5);

1 row created.

SQL> INSERT INTO t(ID, num) VALUES (3, 6);

1 row created.

SQL>
SQL> SELECT * FROM T;

        ID        NUM TEXT
---------- ---------- ----------------------------------------------------------------------------------------------------
         1          4 3
         2          5 3
         3          6 3

SQL> clear columns
columns cleared
SQL> SELECT * FROM T;

        ID        NUM TEXT
---------- ---------- ---------------------------------------------------------------------------------
         1          4 3
         2          5 3
         3          6 3

Any insight is more than welcome.

UDPATE Thanks to Alex Poole. I did not think about the implicit conversion, so I did not care to CAST the expression explicitly. So, the below works -

SQL> DROP TABLE t PURGE;

Table dropped.

SQL>
SQL> CREATE TABLE t(
  2  ID NUMBER,
  3  num NUMBER,
  4  text VARCHAR2(10) generated always as (cast(to_char(id)||'_'||to_char(num) as varchar2(3))) VIRTUAL
  5  );

Table created.

SQL>
SQL> INSERT INTO t(ID, num) VALUES (1, 4);

1 row created.

SQL> INSERT INTO t(ID, num) VALUES (2, 5);

1 row created.

SQL> INSERT INTO t(ID, num) VALUES (3, 6);

1 row created.

SQL>
SQL> SELECT * FROM T;

        ID        NUM TEXT
---------- ---------- ----------
         1          4 1_4
         2          5 2_5
         3          6 3_6

SQL>

Solution

  • Your numbers are not constrained. With single digit (positive) numbers you know the concatendated length can only be three, but the virtual column has to be large enough for any number - so it looks like it's allowing up to 40 digits for the implicit format model (38 significant digits, the decimal separator, and the sign; @collspar's lexicalisation).

    Having said that, constraining the number column wouldn't be reflected in the virtual column length - making both columns NUMBER(1,0) still leaves the concatenation requiring 81 characters. Taking the substring of the generated value won't work either, in this case getting ORA-12899: value too large for column "TEXT" (actual: 10, maximum: 40). Supplying a format model for each to_char() call, e.g. of FM999), would work but restricts the values either side of the underscore rather than the overall length directly.

    If you want to restrict the column size, you can cast it to the same data type and size, which is more explicit:

    text VARCHAR2(10) generated always as 
        (cast(to_char(id)||'_'||to_char(num) as VARCHAR2(10))) VIRTUAL