Pretty print in Oracle PL/SQL - oracle

Is there a better way to print to STDOUT in Oracle PL/SQL?
DBMS_OUTPUT.PUT_LINE seems very basic and coarse.
Should there be something like Python's pprint (Pretty Print)?

PL/SQL is designed to process data, not display it, or interact with the client calling it (so there's no mechanism for user input, for example).
The DBMS_OUTPUT package "enables you to send messages from stored procedures and packages. The package is especially useful for displaying PL/SQL debugging information". It isn't designed for 'pretty' output because PL/SQL isn't designed for that kind of work. And it's important to realise that the client calling your procedure might not look at or display the DBMS_OUTPUT buffer, so what you write to it could be lost anyway.
PL/SQL doesn't print anything to stdout; the DBMS_OUTPUT calls write to a buffer - if it's enabled at all - and then once the PL/SQL has finished executing, the client can read that buffer and display the contents somewhere (again if it's enabled). That also means you can't use it to track progress, since you don't see anything during execution, only when it's complete; so even for debugging it's not always the best tool.
Depending on what you're trying to do you can make things look slightly better in SQL*Plus by having doing set serveroutput on format wrapped, which stops it losing whitespace at the start of buffer lines. But that's usually a minor benefit.
More generally your procedure should pass the results of its processing to the caller via OUT parameters, or by making it a function that returns something useful.
If you're currently trying to display the results of a query using dbms_output.put_line then that isn't a good idea. You could instead return a collection or a ref cursor to the client, and allow the client to worry about how to display it. You can easily display a ref cursor in SQL*Plus or SQL Developer using bind variables.

Pretty enough?
--Pretty print
CREATE OR REPLACE PROCEDURE PP(input varchar2, p_pn varchar2:='PP')
AS
pos INTEGER;
len INTEGER := 4000;
nl VARCHAR2 (2) := CHR (10);
r_ VARCHAR2 (2) := CHR (13);
v_padded varchar2(64):=p_pn;--rpad(p_pn, 5, ' ');
BEGIN
IF LENGTH (input) > len
THEN
pos := INSTR (input, nl, 1, 1);
IF pos > 0 AND pos < len
THEN
DBMS_OUTPUT.put_line (v_padded||': '||REPLACE (SUBSTR (input, 1, pos - 1), r_, ''));
pp (SUBSTR (input, pos + 1),p_pn);
ELSE
IF pos = 0 AND LENGTH (input) <= len
THEN
DBMS_OUTPUT.put_line (v_padded||': '||REPLACE (SUBSTR (input, 1, len), r_, ''));
ELSE
DBMS_OUTPUT.put_line (v_padded||': '||REPLACE (SUBSTR (input, 1, len), r_, ''));
pp (SUBSTR (input, len + 1),p_pn);
END IF;
END IF;
ELSE
DBMS_OUTPUT.put_line (v_padded||': '||REPLACE (SUBSTR (input, 1, len), r_, ''));
END IF;
EXCEPTION
WHEN OTHERS
THEN
RAISE;
END PP;
Sample output:
SQL>
FUN_ENCRYPT_PERSON_ID: Input = 123456789123
FUN_ENCRYPT_PERSON_ID: Suffix = 123
FUN_ENCRYPT_PERSON_ID: Base = 876543210
FUN_ENCRYPT_PERSON_ID: 2->5 1 10000 10000
FUN_ENCRYPT_PERSON_ID: 3->9 2 200000000 200010000
FUN_ENCRYPT_PERSON_ID: 4->2 3 30 200010030
FUN_ENCRYPT_PERSON_ID: 5->1 4 4 200010034
FUN_ENCRYPT_PERSON_ID: 6->3 5 500 200010534
FUN_ENCRYPT_PERSON_ID: 7->6 6 600000 200610534
FUN_ENCRYPT_PERSON_ID: 8->4 7 7000 200617534
FUN_ENCRYPT_PERSON_ID: 9->8 8 80000000 280617534
FUN_ENCRYPT_PERSON_ID: Output = 280617534123

There is not a builtin that I'm aware of. But I wrote a proc to pretty-print a resultset from within a stored procedure.
The proc is called print_out_resultset. Example usage:
....
cursor mycur (mynum in number) is
select * from mytable where mycol >= mynum;
myrow mytable%rowtype;
myvar number;
...
begin
....
print_out_resultset(query_string =>
'select * from mytable where mycol >= :b1',
col1 => 'SAM', col2 => 'FRED', col3 => 'ERIC',
b1 => myvar,
maxrows => 4);
open mycur(myvar);
fetch mycur into myrow;
while mycur%found loop
....
example result
SQL> set serverout on;
SQL> execute myproc;
SAM FRED ERIC
-----------------------------------
32A 49B 15C
34A 11B 99C
11F 99A 887
77E 88J 976
the code is here: http://toolkit.rdbms-insight.com/print_out_resultset.php
I used this only for troubleshooting, many years ago; test before deploying in production of course

Actually there is a standard approach, but it works well only with varchar variables:
declare
v_str varchar2(100):= 'formatted';
v_int int := 10 ;
begin
dbms_output.put_line(
UTL_LMS.FORMAT_MESSAGE('Somebody told that "%s" output works well with '
||'varchar variables, but not with int variables: "%d". '
||'Only with constant integers (%d) it works.'
, v_str, v_int, 100)
);
end;

If you are trying to print json object, there is a better way to print using the JSON_UTIL_PKG and JSON_PRINTER.
L_JSON_LIST JSON_LIST;
L_JSON JSON;
L_CLOB CLOB;
BEGIN
--JSON_UTIL_PKG.SQL_TO_JSON returns a list, so storing it in a JSON_LIST.
L_JSON_LIST := JSON_UTIL_PKG.SQL_TO_JSON('SELECT
''abc'' "Address1",
''def'' "Address2",
''gh'' "City"
FROM
DUAL');
DBMS_LOB.CREATETEMPORARY(L_CLOB, TRUE);
--get the first item in the list
L_JSON := JSON(L_JSON_LIST.GET(1));
--It takes a json object and will return it in a clob.
JSON_PRINTER.PRETTY_PRINT(OBJ => L_JSON, BUF => L_CLOB);
--output
DBMS_OUTPUT.PUT_LINE(L_CLOB);
END;
And, the output looks like the following,
{
"Address1" : "abc"
,
"Address2" : "def"
,
"City" : "gh"
}

Maybe you are looking for fnd_file function
fnd_file.put_line(fnd_file.output,:message);
where :message is the value u want to print.

Related

Pl/SQL function returns string with changed characters

How to write a simple function that returns in parameter changed so it doesn't contain certain symbols anymore?
(č=>c, ć=>c, š=>s, đ=>d, ž=>z..)
e.g. *đurđević* => '*djurdjevic*'
e.g. *kuća* => *kuca*
e.g. *čaćkati* => *cackati*
I have no code so far. I am very new at this and am trying to learn something.
For Croatian characters you can use a combination of TRANSLATE and REPLACE, since TRANSLATE doesn't support one to many translation (e.g. đ => dj).
SELECT REPLACE(TRANSLATE('đak žvakaća čičak šuma','žćčš', 'zccs'), 'đ', 'dj') out FROM dual;
And the output:
out
--------------------------
djak zvakaca cicak suma
Edit
So here are two wrapper functions which implements this feature. The first one uses the built-ins while the other one has its own custom implementation.
-- first
CREATE OR REPLACE FUNCTION f_translate(p_string IN VARCHAR)
RETURN VARCHAR
AS
BEGIN
RETURN REPLACE(TRANSLATE(p_string,'žćčš', 'zccs'), 'đ', 'dj');
END;
-- second
CREATE OR REPLACE FUNCTION f_translate_custom(p_string IN VARCHAR)
RETURN VARCHAR
AS
v_current varchar(1);
v_retval VARCHAR(255);
BEGIN
FOR i IN 1..LENGTH(p_string) LOOP
v_current := SUBSTR(p_string, i, 1);
v_retval := v_retval || CASE v_current
WHEN 'č' THEN 'c'
WHEN 'ć' THEN 'c'
WHEN 'ž' THEN 'z'
WHEN 'š' THEN 's'
WHEN 'đ' THEN 'dj'
ELSE v_current
END;
END LOOP;
RETURN v_retval;
END;
And some test code.
SET SERVEROUTPUT ON;
BEGIN
DBMS_OUTPUT.PUT_LINE('Built-in: ' || f_translate('đak žvakaća čičak šuma'));
DBMS_OUTPUT.PUT_LINE('Custom: ' || f_translate_custom('đak žvakaća čičak šuma'));
END;
Check out translate
https://www.techonthenet.com/oracle/functions/translate.php
Example:
TRANSLATE('1tech23', '123', '456')
Result: '4tech56'
You can try the convert function.
SELECT CONVERT('Ä Ê Í Õ Ø A B C D E ', 'US7ASCII', 'WE8ISO8859P1')
FROM DUAL;
CONVERT('ÄÊÍÕØABCDE'
---------------------
A E I ? ? A B C D E ?
This is the closest you can get without creating any structure:
SELECT utl_raw.cast_to_varchar2((nlssort('čaćkati', 'nls_sort=binary_ai')))
FROM dual;
If you have a lot of combinations, I suggest on creating a table with possible combinations and using the TRANSLATE function.

Oracle PL/SQL speed of NVL/LENGTH/TRIM calls versus IS NOT NULL AND != ' '

I try to find the best way to check if a CHAR/VARCHAR2 variable contains characters (NULL or spaces should be considered the same, as "no-value"):
I know there are several solutions, but it appears that (NVL(LENGTH(TRIM(v)),0) > 0) is faster than (v IS NOT NULL AND v != ' ')
Any idea why? Or did I do something wrong in my test code?
Tested with Oracle 18c on Linux, UTF-8 db charset ...
I get the following results:
time:+000000000 00:00:03.582731000
time:+000000000 00:00:02.494980000
set serveroutput on;
create or replace procedure test1
is
ts timestamp(3);
x integer;
y integer;
v char(500);
--v varchar2(500);
begin
ts := systimestamp;
--v := null;
v := 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
for x in 1..50000000
loop
if v is not null and v != ' ' then
y := x;
end if;
end loop;
dbms_output.put_line('time:' || (systimestamp - ts) ) ;
end;
/
create or replace procedure test2
is
ts timestamp(3);
x integer;
y integer;
v char(500);
--v varchar2(500);
begin
ts := systimestamp;
--v := null;
v := 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
for x in 1..50000000
loop
if nvl(length(trim(v)),0) > 0 then
y := x;
end if;
end loop;
dbms_output.put_line('time:' || (systimestamp - ts) ) ;
end;
/
begin
test1();
test2();
end;
/
drop procedure test1;
drop procedure test2;
quit;
The best practice is to ignore the speed difference between small functions and use whatever is easiest.
In realistic database programming, the time to run functions like NVL or IS NOT NULL is completely irrelevant compared to the time needed to read data from disk or the time needed to join data. If one function saves 1 seconds per 50 million rows, nobody will notice. Whereas if a SQL statement reads 50 million rows with a full table scan instead of using an index, or vice-versa, that could completely break an application.
It's unusual to care about these kinds of problems in a database. (But not impossible - if you have a specific use case, then please add it to the question.) If you really need optimal procedural code you may want to look into writing an external procedure in Java or C.

Shortname function not working properly

There is this code for generatig unique shortname from a table MMSTREPHDR .
I have shortnames kv,kv1,kv2,kv3 already in MMSTREPHDR . But on passing parameter kv it gives me kv1 and not kv4(since it's in LOOP) . Can't figure out what's wrong ?
FUNCTION FUN_GENERATE_SNAME (p_name VARCHAR2)
RETURN VARCHAR2
IS
vl_sname VARCHAR2 (15);
n_cnt NUMBER := 1;
vl_sub NUMBER;
CURSOR c1 (vl_sname VARCHAR2)
IS
SELECT a.repsname, a.repcode
FROM MMSTREPHDR a
WHERE TRIM (UPPER (a.repsname)) = TRIM (UPPER (vl_sname));
BEGIN
vl_sname := TRIM (SUBSTR (p_name, 1, 15));
FOR i IN c1 (vl_sname)
LOOP
vl_sub := LENGTH (TO_CHAR (n_cnt));
vl_sname := SUBSTR (vl_sname, 1, (15 - vl_sub)) || n_cnt;
n_cnt := n_cnt + 1;
END LOOP;
RETURN vl_sname;
EXCEPTION
WHEN OTHERS
THEN
RETURN vl_sname;
END fun_generate_sname;
Your starting parameter is 'kv'. This is what you pass to the cursor. Consequently your cursor will select one row , the row where MMSTREPHD.repsname = 'kv'.
So your loop logic will be executed once. So cnt = . Hencevl_sname` becomes 'kv1', which is the value you get when the loop exits cleanly.
The cleanest way to fix this would be to admit that MMSTREPHD.repsname is a smart key, consisting of two elements: a subsystem name and a report number. Splitting the column into two columns would make it a cinch to find the next report number for a given sub-system. You can even retain the composite value as a virtual column (11g or later), or maintain it with triggers which sucks a bit.
Otherwise:
select concat(p_name
, trim(to_char(max(to_number(nvl(replace(repsname,p_name),'0')))+1)) )
into vl_sname
from MMSTREPHD
where repsname like p_name||'%'
Caveat - I haven't tested this (yet) so ths brackets may not pair up correctly.
You are concatenating the in below statement as
vl_sname := SUBSTR (vl_sname, 1, (15 - vl_sub)) || n_cnt;
here n_cnt is given initial value to it as 1 in code above thats why its fetching you value as KV1.You should keep it null and after statement should increment it by 1 in loop. Hope will be usefull
try this function
function FUN_GENERATE_SNAME(p_name varchar2) return varchar2 is
l_idx number;
l_name_ln := length(trim(p_name));
begin
select max(substr(trim(UPPER(a.repsname)), 1, -length(trim(UPPER(a.repsname)) + l_name_ln))
into l_idx
from MMSTREPHDR a
where substr(trim(UPPER(a.repsname)), 1, l_name_ln) = trim(UPPER(vl_sname));
if l_idx is null then
-- mean name is unique
return vl_sname;
else
return vl_sname ||(l_idx + 1);
end if;
exception
when others then
return vl_sname;
end fun_generate_sname;

Is the use of SELECT COUNT(*) before SELECT INTO slower than using Exceptions?

My last question got me thinking.
1)
SELECT COUNT(*) INTO count FROM foo WHERE bar = 123;
IF count > 0 THEN
SELECT a INTO var FROM foo WHERE bar = 123;
-- do stuff
ELSE
-- do other stuff
END IF;
2)
BEGIN
SELECT a INTO var FROM foo where bar = 123;
-- do stuff
EXCEPTION
WHEN no_data_found THEN
--do other stuff
END ;
I assume number 2 is faster because it requires one less trip to the database.
Is there any situation where 1 would be superior, that I am not considering?
EDIT: I'm going to let this question hang for a few more days, to gather some more votes on the answers, before answering it.
If you use exact queries from the question then 1st variant of course slower because it must count all records in table which satisfies criteria.
It must be writed as
SELECT COUNT(*) INTO row_count FROM foo WHERE bar = 123 and rownum = 1;
or
select 1 into row_count from dual where exists (select 1 from foo where bar = 123);
because checking for record existence is enough for your purpose.
Of course, both variants don't guarantee that someone else don't change something in foo between two statements, but it's not an issue if this check is a part of more complex scenario. Just think about situation when someone changed value of foo.a after selecting it's value into var while performing some actions which refers selected var value. So in complex scenarios better to handle such concurrency issues on application logic level.
To perform atomic operations is better to use single SQL statement.
Any of variants above requires 2 context switches between SQL and PL/SQL and 2 queries so performs slower then any variant described below in cases when row found in a table.
There are another variants to check existence of row without exception:
select max(a), count(1) into var, row_count
from foo
where bar = 123 and rownum < 3;
If row_count = 1 then only one row satisfies criteria.
Sometime it's enough to check only for existence because of unique constraint on the foo which guarantees that there are no duplicated bar values in foo. E.g. bar is primary key.
In such cases it's possible to simplify query:
select max(a) into var from foo where bar = 123;
if(var is not null) then
...
end if;
or use cursor for processing values:
for cValueA in (
select a from foo where bar = 123
) loop
...
end loop;
Next variant is from link, provided by #user272735 in his answer:
select
(select a from foo where bar = 123)
into var
from dual;
From my experience any variant without exception blocks in most cases faster then a variant with exceptions, but if number of executions of such block is low then better to use exception block with handling of no_data_found and too_many_rows exceptions to improve code readability.
Right point to choose to use exception or don't use it, is to ask a question "Is this situation are normal for application?". If row not found and it's a expected situation which can be handled (e.g. add new row or take data from another place and so on) is better to avoid exception. If it's unexpected and there are no way to fix a situation, then catch exception to customize error message, write it to event log and re-throw, or just don't catch it at all.
To compare performance just make a simple test case on you system whith both variants called many times and compare.
Say more, in 90 percent of applications this question is more theoretical than practical because there are a lot of another sources of performance issues which must be taken into account first.
Update
I reproduced example from this page at SQLFiddle site with a little corrections (link).
Results prove that variant with selecting from dual performs best: a little overhead when most of queries succeed and lowest performance degradation when number of missing rows raises.
Surprisingly variant with count() and two queries showed best result in case if all queries failed.
| FNAME | LOOP_COUNT | ALL_FAILED | ALL_SUCCEED | variant name |
----------------------------------------------------------------
| f1 | 2000 | 2.09 | 0.28 | exception |
| f2 | 2000 | 0.31 | 0.38 | cursor |
| f3 | 2000 | 0.26 | 0.27 | max() |
| f4 | 2000 | 0.23 | 0.28 | dual |
| f5 | 2000 | 0.22 | 0.58 | count() |
-- FNAME - tested function name
-- LOOP_COUNT - number of loops in one test run
-- ALL_FAILED - time in seconds if all tested rows missed from table
-- ALL_SUCCEED - time in seconds if all tested rows found in table
-- variant name - short name of tested variant
Below is a setup code for test environment and test script.
create table t_test(a, b)
as
select level,level from dual connect by level<=1e5
/
insert into t_test(a, b) select null, level from dual connect by level < 100
/
create unique index x_text on t_test(a)
/
create table timings(
fname varchar2(10),
loop_count number,
exec_time number
)
/
create table params(pstart number, pend number)
/
-- loop bounds
insert into params(pstart, pend) values(1, 2000)
/
-- f1 - exception handling
create or replace function f1(p in number) return number
as
res number;
begin
select b into res
from t_test t
where t.a=p and rownum = 1;
return res;
exception when no_data_found then
return null;
end;
/
-- f2 - cursor loop
create or replace function f2(p in number) return number
as
res number;
begin
for rec in (select b from t_test t where t.a=p and rownum = 1) loop
res:=rec.b;
end loop;
return res;
end;
/
-- f3 - max()
create or replace function f3(p in number) return number
as
res number;
begin
select max(b) into res
from t_test t
where t.a=p and rownum = 1;
return res;
end;
/
-- f4 - select as field in select from dual
create or replace function f4(p in number) return number
as
res number;
begin
select
(select b from t_test t where t.a=p and rownum = 1)
into res
from dual;
return res;
end;
/
-- f5 - check count() then get value
create or replace function f5(p in number) return number
as
res number;
cnt number;
begin
select count(*) into cnt
from t_test t where t.a=p and rownum = 1;
if(cnt = 1) then
select b into res from t_test t where t.a=p;
end if;
return res;
end;
/
Test script:
declare
v integer;
v_start integer;
v_end integer;
vStartTime number;
begin
select pstart, pend into v_start, v_end from params;
vStartTime := dbms_utility.get_cpu_time;
for i in v_start .. v_end loop
v:=f1(i);
end loop;
insert into timings(fname, loop_count, exec_time)
values ('f1', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
end;
/
declare
v integer;
v_start integer;
v_end integer;
vStartTime number;
begin
select pstart, pend into v_start, v_end from params;
vStartTime := dbms_utility.get_cpu_time;
for i in v_start .. v_end loop
v:=f2(i);
end loop;
insert into timings(fname, loop_count, exec_time)
values ('f2', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
end;
/
declare
v integer;
v_start integer;
v_end integer;
vStartTime number;
begin
select pstart, pend into v_start, v_end from params;
vStartTime := dbms_utility.get_cpu_time;
for i in v_start .. v_end loop
v:=f3(i);
end loop;
insert into timings(fname, loop_count, exec_time)
values ('f3', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
end;
/
declare
v integer;
v_start integer;
v_end integer;
vStartTime number;
begin
select pstart, pend into v_start, v_end from params;
vStartTime := dbms_utility.get_cpu_time;
for i in v_start .. v_end loop
v:=f4(i);
end loop;
insert into timings(fname, loop_count, exec_time)
values ('f4', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
end;
/
declare
v integer;
v_start integer;
v_end integer;
vStartTime number;
begin
select pstart, pend into v_start, v_end from params;
--v_end := v_start + trunc((v_end-v_start)*2/3);
vStartTime := dbms_utility.get_cpu_time;
for i in v_start .. v_end loop
v:=f5(i);
end loop;
insert into timings(fname, loop_count, exec_time)
values ('f5', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
end;
/
select * from timings order by fname
/
First see: Oracle PL/SQL - Are NO_DATA_FOUND Exceptions bad for stored procedure performance? that is essentially the same question than yours. And after that see About the performance of exception handling.
In both scenarios you should be prepared also to handle too_many_rows exception unless your database schema enforces uniqueness of bar.
This is PL/SQL so you're on a constant database trip - instead you should be afraid/aware of PL/SQL - SQL context switches. See also what Tom says:
But don't be afraid at all to invoke SQL from PLSQL - that is what PLSQL does best.
In first place you shouldn't be worried about the performance but the correctness of a program. In this regard my vote goes for scenario #2.
I'm not sure about faster, but I'd say (2) is clearly superior because you're not considering the case where someone issues DELETE FROM foo where bar='123' in between your statements in (1).
Such a situation I usually do like this:
DECALRE
CURSOR cur IS
SELECT a FROM foo where bar = 123;
BEGIN
OPEN cur;
FETCH cur INTO var;
IF cur%FOUND THEN
-- do stuff, maybe a LOOP if required
ELSE
--do other stuff
END;
END;
This has some benfits:
You read only one record from database, the rest you skip. Should be the fastest way of doing it in case you just have to know if number of rows > 1.
You don't handle a "normal" situation with an "exception" handler, some people consider this as "more beautiful" coding.

How to generate a version 4 (random) UUID on Oracle?

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?
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.
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.
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;
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.
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);
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.
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.
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;
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;
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;

Resources