web-dev-qa-db-ja.com

Oracleでバージョン4(ランダム)UUIDを生成する方法は?

このブログでは、sys_guid()の出力がすべてのシステムでランダムではないことを説明しています。

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

残念ながら、このようなシステムを使用する必要があります。

ランダムなUUIDを確実に取得する方法は? sys_guid()で可能ですか? OracleでランダムUUIDを確実に取得する方法がない場合

16
ceving

これを回避策として使用します。

関数を作成または置換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 ; 

この関数には、dbms_cryptoおよびutl_rawが必要です。どちらも実行許可が必要です。

grant execute on sys.dbms_crypto to uuid_user;
9
ceving

@Pablo Santa Cruzの回答と投稿したコードに基づいた完全な例です。

エラーメッセージが表示される理由がわかりません。これはおそらくSQL Developerの問題です。 SQL * Plusで実行し、関数を追加すると、すべて正常に機能します。

   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

しかし、私はSYS_GUID 可能なら。 My Oracle SupportのID 1371805.1を見てください-このバグは11.2.0.3で修正されたと思われます。

[〜#〜] edit [〜#〜]

どちらが速いかは、関数の使用方法によって異なります。

JavaバージョンはSQLで使用すると若干速くなります。ただし、この関数をPL/SQLコンテキストで使用する場合、PL/SQL関数は約2倍になります。 (おそらく、エンジン間の切り替えのオーバーヘッドが回避されるため)

以下に簡単な例を示します。

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

バージョン4のGUIDは完全にランダムではありません。バイトの一部は修正されることになっています。なぜこれが行われたのか、それが重要なのかはわかりませんが、 https://www.cryptosys.net/pki/uuid-rfc4122.html

バージョン4 UUIDを生成する手順は次のとおりです。

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"

Javaバージョンからの値は、標準に準拠しているようです。

24
Jon Heller

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

次の関数はsys_guid()を使用して、それを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;

Dbms_cryptoパッケージを作成して付与する必要はありません。

15
lonecat

Javaプロシージャを作成してコンパイルし、Oracle内で実行できます。そのプロシージャでは、次を使用できます。

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

目的の値を生成します。

こちら コンパイル方法に関するリンクJava Oracleの手順。

3

Javaベースの関数を取得する最も簡単で最短の方法は次のとおりです。

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

.toString()を追加してもコンパイルできない理由を完全には理解できません。

2
Derp

一意ではないかもしれませんが、「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;

DBMS_RANDOM.STRINGのソースから適応。

2
marciel.deg

Cevingから受け入れられた回答は RFC4122 と矛盾しています。clock_seq_hi_and_reservedの2つの最上位ビット(ビット6および7)は、それぞれ0と1に設定する必要があります。これにより、yğur-yeşilyurt形式xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxxで既に言及されているyが8,9、aまたはbに等しくなります

私のソリューションは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;

編集:

最初の実装は理解しやすいものの、かなり非効率的です。次のソリューションは3〜4倍高速です。

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;

このテストは、random_uuidが約1ミリ秒かかり、random_uuid2がたった250マイクロ秒であることを示しています。最初のバージョンの連結では時間がかかりすぎました。

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

UUIDバージョン4によると、フォーマットはxxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxxでなければなりません。 @ lonecat answerはこの形式を提供し、 @ ceving answerはバージョン4の要件を部分的に提供します。欠落している部分はフォーマットyです。yは8、9、a、またはbのいずれかでなければなりません。

これらの答えを組み合わせてy部分を修正すると、コードは次のようになります。

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

私と私の友人の1人によって書かれた純粋なplsql関数があり、uuidバージョン4を生成し、あらゆる種類のGUIDをフォーマットします。また、2つの方法で記述されたフォーマッタ。 1つは文字列を連結し、もう1つは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