web-dev-qa-db-fra.com

Comment générer un UUID version 4 (aléatoire) sur Oracle?

Ce blog explique que la sortie de sys_guid() n'est pas aléatoire pour tous les systèmes:

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

Malheureusement, je dois utiliser un tel système.

Comment s'assurer d'obtenir un UUID aléatoire? Est-ce possible avec sys_guid()? Sinon, comment obtenir de manière fiable un UUID aléatoire sur Oracle?

16
ceving

Je l'utilise maintenant comme solution de contournement:

 créer ou remplacer la fonction random_uuid retourner RAW est 
 v_uuid RAW (16); 
 commencer 
 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 ; 

La fonction nécessite dbms_crypto et utl_raw. Les deux nécessitent une subvention d'exécution.

grant execute on sys.dbms_crypto to uuid_user;
9
ceving

Voici un exemple complet, basé sur la réponse de @Pablo Santa Cruz et le code que vous avez publié.

Je ne sais pas pourquoi vous avez reçu un message d'erreur. C'est probablement un problème avec SQL Developer. Tout fonctionne correctement lorsque vous l'exécutez dans SQL * Plus et ajoutez une fonction:

   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

Mais je resterais avec SYS_GUID si possible. Regardez l'ID 1371805.1 sur My Oracle Support - ce bogue est censé être corrigé dans 11.2.0.3.

[~ # ~] modifier [~ # ~]

Laquelle est la plus rapide dépend de la façon dont les fonctions sont utilisées.

Il semble que la version Java est légèrement plus rapide lorsqu'elle est utilisée dans SQL. Cependant, si vous utilisez cette fonction dans un contexte PL/SQL, la fonction PL/SQL est environ deux fois plus rapide. (Probablement parce qu'il évite les frais généraux de commutation entre les moteurs.)

Voici un exemple rapide:

--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;
/

Les GUID de la version 4 ne sont pas complètement aléatoires. Certains octets sont censés être corrigés. Je ne sais pas pourquoi cela a été fait, ou si cela importe, mais selon https://www.cryptosys.net/pki/uuid-rfc4122.html :

La procédure pour générer un UUID version 4 est la suivante:

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"

Les valeurs de la version Java Java semblent conformes à la norme.

24
Jon Heller

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

La fonction suivante utilise sys_guid () et la transforme en format uuid:

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;

Il n'a pas besoin de créer de package dbms_crypto et de l'accorder.

15
lonecat

Vous pouvez écrire une procédure Java et la compiler et l'exécuter dans Oracle. Dans cette procédure, vous pouvez utiliser:

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

Pour générer la valeur souhaitée.

Voici un lien sur la façon de compiler Java procédures dans Oracle.

3
Pablo Santa Cruz

Le moyen le plus simple et le plus court d'obtenir une fonction basée sur Java pour moi était:

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

Je ne peux pas comprendre complètement pourquoi il ne compile pas si j'ajoute .toString() cependant.

2
Derp

Il peut ne pas être unique, mais générer une chaîne aléatoire "de type GUID":

 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;

Adapté de la source de DBMS_RANDOM.STRING.

2
marciel.deg

La réponse acceptée de la ceving est incompatible avec RFC4122 : les deux bits les plus significatifs (bits 6 et 7) de clock_seq_hi_and_reserved doivent être respectivement mis à zéro et à un. Cela fait y égal à 8,9, a ou b dans déjà mentionné par le format uğur-yeşilyurt xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx

Ma solution a fait le point sur 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;

ÉDITER:

Bien que la première implémentation soit facile à comprendre, elle est plutôt inefficace. La solution suivante est 3 à 4 fois plus rapide.

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;

Ce test montre que random_uuid prend environ une milliseconde et random_uuid2 seulement 250 microsecondes. La concaténation dans la première version a pris trop de temps;

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

Selon l'UUID, le format de la version 4 doit être xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx. @ lonecat réponse fournit ce format, également @ ceving réponse fournit partiellement les exigences de la version 4. La partie manquante est au format y, y doit être l'un des 8, 9, a ou b.

Après avoir mélangé ces réponses et corrigé la partie y, le code ressemble à ci-dessous:

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;
1
Uğur Yeşilyurt

il y a des fonctions plsql pures écrites par moi et un de mes amis qui génèrent la version 4 de uuid et formate tout type de GUID. également des formateurs écrits en deux sens. une chaîne concatérante et une expression régulière pour formater 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;


1
Sina Salmani