25

This blog explains, that the output of sys_guid() is not random for every system:

http://feuerthoughts.blogspot.de/2006/02/watch-out-for-sequential-oracle-guids.html

Unfortunately I have to use such a system.

How to ensure to get a random UUID? Is it possible with sys_guid()? If not how to reliably get a random UUID on Oracle?

jpaugh
  • 6,634
  • 4
  • 38
  • 90
ceving
  • 21,900
  • 13
  • 104
  • 178
  • 1
    In general, UUIDs are [not reliably random](https://blogs.msdn.microsoft.com/oldnewthing/20120523-00/?p=7553). – jpaugh Oct 26 '16 at 20:39
  • @jpaugh With "random UUID" I intend "type 4 UUID". I think everybody here knows what [pseudorandomness](https://en.wikipedia.org/wiki/Pseudorandomness) means. – ceving Oct 27 '16 at 07:28
  • True, but I totally misunderstood what you were asking for. Please review and adjust my edit, if needed. – jpaugh Oct 27 '16 at 20:00

10 Answers10

34

Here's a complete example, based on @Pablo Santa Cruz's answer and the code you posted.

I'm not sure why you got an error message. It's probably an issue with SQL Developer. Everything works fine when you run it in SQL*Plus, and add a function:

   create or replace and compile
   java source named "RandomUUID"
   as
   public class RandomUUID
   {
      public static String create()
      {
              return java.util.UUID.randomUUID().toString();
      }
   }
   /
Java created.
   CREATE OR REPLACE FUNCTION RandomUUID
   RETURN VARCHAR2
   AS LANGUAGE JAVA
   NAME 'RandomUUID.create() return java.lang.String';
   /
Function created.
   select randomUUID() from dual;
RANDOMUUID()
--------------------------------------------------------------
4d3c8bdd-5379-4aeb-bc56-fcb01eb7cc33

But I would stick with SYS_GUID if possible. Look at ID 1371805.1 on My Oracle Support - this bug is supposedly fixed in 11.2.0.3.

EDIT

Which one is faster depends on how the functions are used.

It looks like the Java version is slightly faster when used in SQL. However, if you're going to use this function in a PL/SQL context, the PL/SQL function is about twice as fast. (Probably because it avoids overhead of switching between engines.)

Here's a quick example:

--Create simple table
create table test1(a number);
insert into test1 select level from dual connect by level <= 100000;
commit;

--SQL Context: Java function is slightly faster
--
--PL/SQL: 2.979, 2.979, 2.964 seconds
--Java: 2.48, 2.465, 2.481 seconds
select count(*)
from test1
--where to_char(a) > random_uuid() --PL/SQL
where to_char(a) > RandomUUID() --Java
;

--PL/SQL Context: PL/SQL function is about twice as fast
--
--PL/SQL: 0.234, 0.218, 0.234
--Java: 0.52, 0.515, 0.53
declare
    v_test1 raw(30);
    v_test2 varchar2(36);
begin
    for i in 1 .. 10000 loop
        --v_test1 := random_uuid; --PL/SQL
        v_test2 := RandomUUID; --Java
    end loop;
end;
/

Version 4 GUIDs are not completely random. Some of the bytes are supposed to be fixed. I'm not sure why this was done, or if it matters, but according to https://www.cryptosys.net/pki/uuid-rfc4122.html:

The procedure to generate a version 4 UUID is as follows:

Generate 16 random bytes (=128 bits)
Adjust certain bits according to RFC 4122 section 4.4 as follows:
    set the four most significant bits of the 7th byte to 0100'B, so the high nibble is "4"
    set the two most significant bits of the 9th byte to 10'B, so the high nibble will be one of "8", "9", "A", or "B".
Encode the adjusted bytes as 32 hexadecimal digits
Add four hyphen "-" characters to obtain blocks of 8, 4, 4, 4 and 12 hex digits
Output the resulting 36-character string "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"

The values from the Java version appear to conform to the standard.

Jon Heller
  • 34,999
  • 6
  • 74
  • 132
  • How to find out if calling the Java function is faster than using Oracles dbms_crypto package I used in my work around? – ceving Dec 25 '12 at 10:06
  • The UIDs on my Solaris system are predictable and this is not allowed for the application. – ceving Dec 25 '12 at 10:16
  • @Jon Heller I tried SYS_GUID() but the result is not what I expect. Version 4 of UUID should look like xxxxxxxxxxxx-4xxx-yxxx-xxxxxxxxxxxx or? – ZerOne Jul 17 '18 at 08:50
  • @ZerOne SYS_GUID() seems to work correctly, see the edit. – Jon Heller Jul 17 '18 at 19:34
  • @JonHeller thanks for your edit. maybe I misunderstand you, but do you mean it works correct in their own Oracle way? because its not like the format it should have according to the RFC – ZerOne Jul 18 '18 at 14:21
  • @ZerOne I think the Java implementation matches the RFC? But I confess to only looking at summaries of the RFC, and not reading and understanding the whole thing. – Jon Heller Jul 19 '18 at 03:10
18

https://stackoverflow.com/a/10899320/1194307

The following function use sys_guid() and transform it into uuid format:

create or replace function random_uuid return VARCHAR2 is
  v_uuid VARCHAR2(40);
begin
  select 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') into v_uuid from dual;
  return v_uuid;
end random_uuid;

It do not need create dbms_crypto package and grant it.

Community
  • 1
  • 1
lonecat
  • 309
  • 2
  • 5
  • Thanks but `regexp_replace` sounds quite expensive. UUID generation needs to be very fast. – ceving Nov 06 '13 at 09:11
  • 2
    This function will not be random on all platforms. For example, on Solaris only one or two characters change with each execution. – Jon Heller Feb 24 '17 at 01:55
14

I use this now as a workaround:

create or replace function random_uuid return RAW is
  v_uuid RAW(16);
begin
  v_uuid := sys.dbms_crypto.randombytes(16);
  return (utl_raw.overlay(utl_raw.bit_or(utl_raw.bit_and(utl_raw.substr(v_uuid, 7, 1), '0F'), '40'), v_uuid, 7));
end random_uuid;

The function requires dbms_crypto and utl_raw. Both require an execute grant.

grant execute on sys.dbms_crypto to uuid_user;
ceving
  • 21,900
  • 13
  • 104
  • 178
  • Could you explain what's going on when you're applying the utl_raw functions to the randombytes output? – tribunal88 May 28 '15 at 14:34
  • 1
    @tribunal88 I would say some electrons are floating around. ;-) – ceving May 29 '15 at 13:36
  • I meant why do we need to apply any post-processing at all to the randombytes(16) output? – tribunal88 Jun 04 '15 at 12:38
  • 1
    Scratch that. I see what you're doing. You're setting the version number of the UUID to "4" as per section 4.1.3 of RFC 4122. http://www.ietf.org/rfc/rfc4122.txt – tribunal88 Jun 08 '15 at 13:46
  • 1
    Technically this is not compliant with v4 of the RFC as the two most significant bits of the 9th byte should be set to `10`. Therefore, extract the 7th, 8th, and 9th bytes, bit-wise AND them against `0x0FFFBF`, and then bit-wise OR them against `0x400080` before overlaying them. – Geraint Ballinger Mar 17 '20 at 10:50
6

The easiest and shortest way to get a Java-based function for me was:

create or replace function random_uuid return varchar2 as
language java
name 'java.util.UUID.randomUUID() return String';

I can't completely understand why it does not compile if I add .toString() though.

Kirill
  • 6,762
  • 4
  • 51
  • 81
4

I wasn't fully satisfied with any of the above answers: Java is often not installed, dbms_crypto needs a grant and sys_guid() is sequential.

I settled on this.

create or replace function random_uuid return VARCHAR2 is
   random_hex varchar2(32);
begin
  random_hex := translate(DBMS_RANDOM.string('l', 32), 'ghijklmnopqrstuvwxyz', '0123456789abcdef0123');
  return          substr(random_hex, 1, 8) 
        || '-' || substr(random_hex, 9, 4)  
        || '-' || substr(random_hex, 13, 4)  
        || '-' || substr(random_hex, 17, 4)  
        || '-' || substr(random_hex, 21, 12);
end random_uuid;
/

dbms_random is (by default) public so no grants are required and it is reasonably random. Note that dbms_random is not cryptographically secure so if you need that, use the dbms_crypto approach above. The hex value distribution is also skewed by the translate function. If you need real UUID 4 output then you can tweak the substr (I just need uniqueness).

The same technique could be used in sql without a function with some imagination:

select 
          substr(rand, 1, 8) 
|| '-' || substr(rand, 9, 4)  
|| '-' || substr(rand, 13, 4)  
|| '-' || substr(rand, 17, 4)  
|| '-' || substr(rand, 21, 12)  
from (select translate(DBMS_RANDOM.string('l', 32), 'ghijklmnopqrstuvwxyz', '0123456789abcdef0123') rand  from dual);
MarkJ
  • 171
  • 2
  • 7
3

You can write a Java procedure and compile it and run it inside Oracle. In that procedure, you can use:

UUID uuid = UUID.randomUUID();
return uuid.toString();

To generate desired value.

Here's a link on how to compile java procedures in Oracle.

Pablo Santa Cruz
  • 176,835
  • 32
  • 241
  • 292
  • Is there an example anywhere available? – ceving Dec 19 '12 at 11:39
  • Yes, take a look at the link I've just posted. Oracle's database has a Java Virtual Machine on its context to run this kind of java code on its context without the need of running an **external program**. – Pablo Santa Cruz Dec 19 '12 at 11:40
  • Does not work. As soon as I try to use UUID my Oracle refuses to compile it: http://pastebin.com/kL4jB2KX – ceving Dec 19 '12 at 12:24
  • I asked how to use java.util.UUID in PL/SQL at Oracle but till now there is no answer: https://forums.oracle.com/forums/thread.jspa?threadID=2478217&tstart=0 – ceving Dec 19 '12 at 16:16
3

According to UUID Version 4 format should be xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx. @lonecat answer provide this format, also @ceving answer partially provide version 4 requirements. Missing part is format y, y should be one of 8, 9, a, or b.

After mixing these answers and fix the y part, code looks like below:

create or replace function fn_uuid return varchar2 is
  /* UUID Version 4 must be formatted as xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx where x is any hexadecimal character (lower case only) and y is one of 8, 9, a, or b.*/

  v_uuid_raw raw(16);
  v_uuid     varchar2(36);
  v_y        varchar2(1);
begin

  v_uuid_raw := sys.dbms_crypto.randombytes(16);
  v_uuid_raw := utl_raw.overlay(utl_raw.bit_or(utl_raw.bit_and(utl_raw.substr(v_uuid_raw, 7, 1), '0F'), '40'), v_uuid_raw, 7);

  v_y := case round(dbms_random.value(1, 4))
            when 1 then
             '8'
            when 2 then
             '9'
            when 3 then
             'a'
            when 4 then
             'b'
           end;

  v_uuid_raw := utl_raw.overlay(utl_raw.bit_or(utl_raw.bit_and(utl_raw.substr(v_uuid_raw, 9, 1), '0F'), v_y || '0'), v_uuid_raw, 9);
  v_uuid     := regexp_replace(lower(v_uuid_raw), '([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');

  return v_uuid;
end fn_uuid;
Uğur Yeşilyurt
  • 1,339
  • 2
  • 12
  • 21
2

It may not be unique, but generate a "GUID-like" random string:

 FUNCTION RANDOM_GUID
    RETURN VARCHAR2 IS
    RNG    NUMBER;
    N      BINARY_INTEGER;
    CCS    VARCHAR2 (128);
    XSTR   VARCHAR2 (4000) := NULL;
  BEGIN
    CCS := '0123456789' || 'ABCDEF';
    RNG := 15;

    FOR I IN 1 .. 32 LOOP
      N := TRUNC (RNG * DBMS_RANDOM.VALUE) + 1;
      XSTR := XSTR || SUBSTR (CCS, N, 1);
    END LOOP;

    RETURN XSTR;
  END RANDOM_GUID;

Adapted from source of DBMS_RANDOM.STRING.

marciel.deg
  • 400
  • 3
  • 17
2

Accepted answer from ceving is inconsistent with RFC4122: the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved should be set to zero and one, respectively. That makes y equal to 8,9,a or b in already mentioned by uğur-yeşilyurt format xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx

My solution made point blank along RFC:

create or replace function random_uuid return raw is
  /*
  Set the four most significant bits (bits 12 through 15) of the
      time_hi_and_version field to the 4-bit version number from
      Section 4.1.3.
  */
  v_time_hi_and_version raw(2) := utl_raw.bit_and(utl_raw.bit_or(dbms_crypto.randombytes(2), '4000'), '4FFF');
  /*
  Set the two most significant bits (bits 6 and 7) of the
      clock_seq_hi_and_reserved to zero and one, respectively.
  */
  v_clock_seq_hi_and_reserved raw(1) := utl_raw.bit_and(utl_raw.bit_or(dbms_crypto.randombytes(1), '80'), 'BF');
  /*
  Set all the other bits to randomly (or pseudo-randomly) chosen
      values.
  */
  v_time raw(6) := dbms_crypto.randombytes(6);
  v_clock_seq_low_and_node raw(7) := dbms_crypto.randombytes(7);
begin
  return v_time || v_time_hi_and_version || v_clock_seq_hi_and_reserved || v_clock_seq_low_and_node;
end random_uuid;

EDIT:

Although first implementation easy to understand it's rather inefficient. Next solution is 3 to 4 times faster.

create or replace function random_uuid2 return raw is
  v_uuid raw(16) := dbms_crypto.randombytes(16);
begin
   v_uuid :=  utl_raw.bit_or(v_uuid, '00000000000040008000000000000000');
   v_uuid := utl_raw.bit_and(v_uuid, 'FFFFFFFFFFFF4FFFBFFFFFFFFFFFFFFF');
  return v_uuid;
end;

This test demostrates that random_uuid takes about one millisecond and random_uuid2 only 250 microseconds. Concatenation in the first version consumed too much time;

declare
   dummy_uuid raw(16);
begin
   for i in 1 .. 20000 loop
      --dummy_uuid := random_uuid;
      dummy_uuid := random_uuid2;
   end loop;
end;
Community
  • 1
  • 1
Leonid
  • 108
  • 1
  • 9
1

there are some pure plsql functions written by me and one of my friend that generates uuid version 4 and formats any type of GUIDs. also formatters written in two way. one concating string and one use regex for formatting uuid

CREATE OR REPLACE FUNCTION RANDOM_UUD_RAW
  RETURN RAW IS V_UUID RAW(16);
  BEGIN V_UUID := SYS.DBMS_CRYPTO.Randombytes(16);
    V_UUID := UTL_RAW.Overlay(UTL_RAW.Bit_or(UTL_RAW.Bit_and(UTL_RAW.Substr(V_UUID, 7, 1), '0F'), '40'), V_UUID, 7, 1);
    V_UUID := UTL_RAW.Overlay(UTL_RAW.Bit_or(UTL_RAW.Bit_and(UTL_RAW.Substr(V_UUID, 9, 1), '3F'), '80'), V_UUID, 9, 1);
    RETURN V_UUID;
  END RANDOM_UUD_RAW; --
CREATE OR REPLACE FUNCTION UUID_FORMATTER_CONCAT(V_UUID RAW)
  RETURN VARCHAR2 IS V_STR VARCHAR2(36);
  BEGIN V_STR := lower(SUBSTR(V_UUID, 1, 8) || '-' || SUBSTR(V_UUID, 9, 4) || '-' || SUBSTR(V_UUID, 13, 4) || '-' || SUBSTR(V_UUID, 17, 4) || '-' || SUBSTR(V_UUID, 21));
    RETURN V_STR;
  END UUID_FORMATTER_CONCAT; --
CREATE OR REPLACE FUNCTION UUID_FORMATTER_REGEX(V_UUID RAW)
  RETURN VARCHAR2 IS V_STR VARCHAR2(36);
  BEGIN V_STR := lower(regexp_replace(V_UUID, '(.{8})(.{4})(.{4})(.{4})(.{12})', '\1-\2-\3-\4-\5'));
    RETURN V_STR;
  END UUID_FORMATTER_REGEX; --
CREATE OR REPLACE FUNCTION RANDOM_UUID_STR
  RETURN VARCHAR2 AS BEGIN RETURN UUID_FORMATTER_CONCAT(RANDOM_UUD_RAW());
  END RANDOM_UUID_STR; --
CREATE OR REPLACE FUNCTION RANDOM_UUID_STR_REGEX
  RETURN VARCHAR2 AS BEGIN RETURN UUID_FORMATTER_REGEX(RANDOM_UUD_RAW());
  END RANDOM_UUID_STR_REGEX;


Sina Salmani
  • 89
  • 1
  • 9