5

We are testing out Oracle at my work and im in charge of building all of the database objects (tables, procs, trigger, etc) on Oracle, and we currently use Microsoft SQL Server 2008 R2. We use uniqueidentifier's for almost all of our ID column's. I used this function to create GUID's:

CREATE OR REPLACE FUNCTION NEWID RETURN CHAR IS guid CHAR(36) ;
BEGIN
    SELECT SYS_GUID() INTO guid FROM DUAL;
    guid :=
               SUBSTR(guid,  1, 8) ||
        '-' || SUBSTR(guid,  9, 4) ||
        '-' || SUBSTR(guid, 13, 4) ||
        '-' || SUBSTR(guid, 17, 4) ||
        '-' || SUBSTR(guid, 21);
    RETURN guid;
END NEWID;
/

But now I cant figure out how to use it as the default value on columns when creating tables. Here is a non-working example:

CREATE TABLE "NonWorkingExample"
(   
  "ID"                              CHAR(36)      NOT NULL DEFAULT   NEWID(),
  "UnitNumber"                      NUMBER(38)    NOT NULL,
  "StartDateTime"                   TIMESTAMP     NOT NULL,
  "EndDateTime"                     TIMESTAMP     NULL,

  CONSTRAINT PK_RentalAgreements  PRIMARY KEY ("ID")
);

And the error:

Error starting at line 1 in command:
CREATE TABLE "NonWorkingExample"
(   
  "ID"                          CHAR(36)      NOT NULL DEFAULT NEWID(),
  "UnitNumber"                  NUMBER(38)    NOT NULL,
  "StartDateTime"               TIMESTAMP     NOT NULL,
  "EndDateTime"                 TIMESTAMP     NULL,

  CONSTRAINT PK_RentalAgreements  PRIMARY KEY ("ID")
)
Error at Command Line:3 Column:58
Error report:
SQL Error: ORA-00907: missing right parenthesis
00907. 00000 -  "missing right parenthesis"
*Cause:    
*Action:

Any help would be much appreciated. Thank you.

thinkdevcode
  • 921
  • 1
  • 8
  • 13
  • 1
    BTW, I could not get your function to run as written. It compiled file, but `select newid from dual` resulted in `ORA-06502: PL/SQL: numeric or value error: character string buffer too small` Changing the declaration of guid to `varchar2(36)` fixed it. – Shannon Severance Jul 22 '11 at 22:37
  • Interesting. I got that function from another SO post – thinkdevcode Jul 24 '11 at 16:29

6 Answers6

11

I completely agree with the other answers that if you really want to call a function, you'll need to use a trigger or you'll need to embed the function call in the INSERT statement itself. Embedding the function call in the INSERT statement is more efficient than forcing Oracle to execute a trigger for every row that you insert.

I should point out, though, that you can use SYS_GUID() alone as the default value for a column without worrying about a trigger

SQL> create table foo (
  2    col1 varchar2(32) default sys_guid(),
  3    col2 number
  4  );

Table created.

SQL> insert into foo( col2 ) values( 1 );

1 row created.

SQL> select * from foo;

COL1                                   COL2
-------------------------------- ----------
7B64E8AE7404421C80A590F65873CD79          1

Do you really need the extra dashes in your GUID values? Could you potentially add the dashes only when you display the data? Or, since you're on 11g, add a function-based virtual column that converts the dash-free GUID to a GUID in the format that you prefer?

And since you're coming from SQL Server, I should point out that it would be conventional in Oracle to use a sequence to populate synthetic primary keys rather than using a GUID. Using a sequence to populate the key would generally be more efficient than calling SYS_GUID.

Justin Cave
  • 227,342
  • 24
  • 367
  • 384
  • 1
    Embedding a PLSQL function in the insert would still cause a context shift. – Allan Jul 22 '11 at 21:28
  • @Shannon - Are you saying that you have seen SYS_GUID return duplicate values? That seems unlikely to me-- do you have code that reproduces the problem? – Justin Cave Jul 22 '11 at 22:38
  • No, I wasn't talking about sys_guid. But never mind, the guids are different in the middle and I wasn't careful enough when reviewing them. – Shannon Severance Jul 22 '11 at 22:43
  • +1 especially for suggesting sequences and normal (dash-free) sys_guid's – Rob van Wijk Jul 23 '11 at 11:20
  • Unfortunately we need dashes in the GUID as that is how uniqueidentifier works in MSSQL and they want to keep the same format. Id love to use a sequential number as the PK but alas, thats not how the system is built. – thinkdevcode Jul 24 '11 at 16:25
  • @Allan - Edited my post to remove the incorrect comment about the additional context shift. – Justin Cave Aug 11 '11 at 06:49
  • Beware virtual columns though. As awesome an idea as they are, I just found out a gotcha with them! At least in the build of 11g that we have, if you query a virtual column in a SQL statement with more than one join, the virtual column will always return `NULL` instead of its actual value. – eidylon Sep 10 '12 at 18:16
  • 1
    "Using dashes in GUID values" - or using UUIDs - means working with values that conform [to the RFC standard](https://stackoverflow.com/a/6515403/716114) instead of the Oracle's way of doing things. – havelock Feb 09 '18 at 21:28
7

Suggestion:

  • Use triggers instead.

For example:

CREATE TRIGGER SetGUIDforTableXYZ BEFORE INSERT ON TableXYZ
FOR EACH ROW
BEGIN
    :new.ID := NEWID();
END;

That should do the trick (assuming I haven't messed up the syntax somewhere).

Kerri Shotts
  • 2,372
  • 1
  • 17
  • 19
  • 4
    @thinkdevcode - a solution which works is more performant than a solution which doesn't work :) – APC Jul 22 '11 at 23:36
6

As @Kerri implied, you cannot use a PLSQL function as a default value in a table definition. The relevant statement in the Oracle documentation is "A DEFAULT expression cannot contain references to PL/SQL functions...".

In addition to being the only way to insert a formatted GUID, as you're attempting to do, a trigger provides a second advantage: unlike with a default value, the value set by the trigger cannot be overridden by a careless developer.

As an aside, you should really revise your newid function to use direct assignment rather than select ... from dual:

CREATE OR REPLACE FUNCTION NEWID RETURN CHAR IS 
    guid VARCHAR(36);
BEGIN
    guid := SYS_GUID();
    guid :=
               SUBSTR(guid,  1, 8) ||
        '-' || SUBSTR(guid,  9, 4) ||
        '-' || SUBSTR(guid, 13, 4) ||
        '-' || SUBSTR(guid, 17, 4) ||
        '-' || SUBSTR(guid, 21);
    RETURN guid;
END NEWID;
/

Finally, I would advise you to use varchar2 instead of char. Contrary to popular opinion, there is no storage or efficiency advantage to char, so you might as well use varchar2 for everything, just for simplicity's sake.

Allan
  • 17,141
  • 4
  • 52
  • 69
5

You can't use a function for this. You don't see it because your syntax is wrong (DEFAULT before CONSTRAINTS). You should have:

"ID"    CHAR(36)  DEFAULT NEWID()   NOT NULL ,

at which point you would receive the following message (11g):

SQL> create table tt (id varchar2(36) default newid() not null);
create table tt (id varchar2(36) default newid() not null)
                                   *
ERROR at line 1:
ORA-04044: procedure, function, package, or type is not allowed here


SQL>

As @Kerri states, it'll take a trigger to do this automatically.

DCookie
  • 42,630
  • 11
  • 83
  • 92
0

How about removing the function and trigger dependencies by trying something like this:

CREATE TABLE "NonWorkingExample" 
(    
  "ID"                          CHAR(36)      NOT NULL DEFAULT regexp_replace((rawtohex(sys_guid()), '([A-F0-9]{8})([A-F0-9]{4})([A-F0-9]{4})([A-F0-9]{4})([A-F0-9]{12})', '\1-\2-\3-\4-\5'), 
  "UnitNumber"                  NUMBER(38)    NOT NULL, 
  "StartDateTime"               TIMESTAMP     NOT NULL, 
  "EndDateTime"                 TIMESTAMP     NULL, 

  CONSTRAINT PK_RentalAgreements  PRIMARY KEY ("ID") 
) 
scottrudy
  • 1,633
  • 1
  • 14
  • 24
0

If Oracle SYS_GUID()'s somewhat similar/sequential GUIDs bother you, you can wrap java.util.UUID.randomUUID() in a pl/sql function to get less predictable values:

CREATE OR REPLACE FUNCTION Java_Util_Random_UUID RETURN VARCHAR2 AS
language java
name 'java.util.UUID.randomUUID() return String';
James Daily
  • 587
  • 6
  • 21