oracle function call is being cached during bulk insert - sql

when inserting bulk rows and using a function call as one of the column values i'm getting exactly the same values for every 10-11 rows from the function. the function is actually generating UUID values and returns unique results. If i replace the function call in the insert statement with the actual code of the function it never repeats.
So what i conclude from this is that oracle actually caches the result of the function and calls it only once for every 10-11 rows it's inserting. how can i change this behavior?
the function i'm calling i've taken from http://www.oracle-base.com/articles/9i/UUID9i.php :
create or replace
FUNCTION new_uuid RETURN VARCHAR2 AS
l_seed BINARY_INTEGER;
l_random_num NUMBER(5);
l_date VARCHAR2(25);
l_random VARCHAR2(4);
l_ip_address VARCHAR2(12);
BEGIN
l_seed := TO_NUMBER(TO_CHAR(SYSDATE,'YYYYDDMMSS'));
DBMS_RANDOM.initialize (val => l_seed);
l_random_num := TRUNC(DBMS_RANDOM.value(low => 1, high => 65535));
DBMS_RANDOM.terminate;
l_date := conversion_api.to_hex(TO_NUMBER(TO_CHAR(SYSTIMESTAMP,'FFSSMIHH24DDMMYYYY')));
l_random := RPAD(conversion_api.to_hex(l_random_num), 4, '0');
l_ip_address := conversion_api.to_hex(TO_NUMBER(REPLACE(NVL(SYS_CONTEXT('USERENV','IP_ADDRESS'), '123.123.123.123'), '.', '')));
RETURN SUBSTR(l_date, 1, 8) || '-' ||
SUBSTR(l_date, 9, 4) || '-' ||
SUBSTR(l_date, 13, 4) || '-' ||
RPAD(SUBSTR(l_date, 17), 4, '0') || '-' ||
RPAD(L_RANDOM || L_IP_ADDRESS, 12, '0');
END;
and here's the insert statement i'm using:
INSERT INTO My_TABLE(ID, NAME,)
SELECT NEW_UUID(), NAME
FROM MY_TABLE2;
COMMIT;
the select inside this statement produces lots of repeating UUIDs. while this statement produces unique ones:
SELECT RPAD(RPAD(my_schema.conversion_api.to_hex(TRUNC(DBMS_RANDOM.VALUE( 1, 65535))), 4, '0') || my_schema.conversion_api.to_hex(TO_NUMBER(REPLACE(NVL(SYS_CONTEXT('USERENV','IP_ADDRESS'), '123.123.123.123'), '.', ''))), 12, '0') sss
FROM my_schema.MY_TABLE

APC's diagnostic is correct. You need to have entropy in your random generator seed.
Though, Oracle already has a unique id generator which is SYS_GUID().
SELECT sys_guid(), name FROM my_table2;
You can try this which produces 9 GUIDs:
SELECT sys_guid() from dual connect by level < 10;
Don't try to reinvent the wheel when it already exists.

The thing is, "random" isn't actually random. Given the same seed for DBMS_RANDOM.INITIALISE(), subsequent calls to DBMS_RANDOM.VALUE() will return the same result. Check it out:
SQL> exec DBMS_RANDOM.initialize (val => 1)
PL/SQL procedure successfully completed.
SQL> select TRUNC(DBMS_RANDOM.value(low => 1, high => 65535)) from dual
2 /
TRUNC(DBMS_RANDOM.VALUE(LOW=>1,HIGH=>65535))
--------------------------------------------
49214
SQL> r
1* select TRUNC(DBMS_RANDOM.value(low => 1, high => 65535)) from dual
TRUNC(DBMS_RANDOM.VALUE(LOW=>1,HIGH=>65535))
--------------------------------------------
56385
SQL> r
1* select TRUNC(DBMS_RANDOM.value(low => 1, high => 65535)) from dual
TRUNC(DBMS_RANDOM.VALUE(LOW=>1,HIGH=>65535))
--------------------------------------------
23941
SQL> exec DBMS_RANDOM.initialize (val => 1)
PL/SQL procedure successfully completed.
SQL> select TRUNC(DBMS_RANDOM.value(low => 1, high => 65535)) from dual;
TRUNC(DBMS_RANDOM.VALUE(LOW=>1,HIGH=>65535))
--------------------------------------------
49214
SQL> r
1* select TRUNC(DBMS_RANDOM.value(low => 1, high => 65535)) from dual
TRUNC(DBMS_RANDOM.VALUE(LOW=>1,HIGH=>65535))
--------------------------------------------
56385
SQL> r
1* select TRUNC(DBMS_RANDOM.value(low => 1, high => 65535)) from dual
TRUNC(DBMS_RANDOM.VALUE(LOW=>1,HIGH=>65535))
--------------------------------------------
23941
SQL>
If we look at the code you got from Tim's site we see this line:
l_seed := TO_NUMBER(TO_CHAR(SYSDATE,'YYYYDDMMSS'));
From which we can surmise that your process is inserting 10-11 rows per second :)
If you replace SYSDATE with SYSTIMESTAMP and change the mask to go to millisecs (or smaller) then you should get a different seed each time, and hence a different value each time. Note that you still need to force the re-evaluation of the function to guarantee getting a different result for each row (see the demo below).
Um, did I say "guarantee". Uh-oh. It is in the nature of the random that it can produce the same result two goes running. So perhaps that should be "to minimize teh chances of getting the same result for each row".
Alternatively, remove the initialisation from the function and call it before you start your bulk inserts. Whether this is feasible depends entirely on your business logic.
Demonstration
Here is a function which generates a "random" number:
create or replace function get_random_number
(p_seed in number := 0)
return pls_integer
is
begin
if p_seed = 0
then
DBMS_RANDOM.initialize (val => TO_NUMBER(TO_CHAR(SYSDATE,'YYYYDDMMSS')));
else
DBMS_RANDOM.initialize (val => p_seed);
end if;
return TRUNC(DBMS_RANDOM.value(low => 1, high => 65535));
end;
/
If we call it twenty times with the default parameter it returns the same number each time:
SQL> select rownum
, get_random_number
from dual
connect by level <= 20
/
2 3 4 5
ROWNUM GET_RANDOM_NUMBER
---------- -----------------
1 10239
2 10239
3 10239
4 10239
5 10239
6 10239
7 10239
8 10239
9 10239
10 10239
11 10239
12 10239
13 10239
14 10239
15 10239
16 10239
17 10239
18 10239
19 10239
20 10239
20 rows selected.
SQL>
Whereas if we pass a value it uses a different seed each time and lo! we get a different result:
SQL> select rownum
, get_random_number(rownum)
from dual
connect by level <= 20
/
2 3 4 5
ROWNUM GET_RANDOM_NUMBER(ROWNUM)
---------- -------------------------
1 49214
2 6476
3 42426
4 2370
5 48546
6 52483
7 6964
8 46764
9 27569
10 7673
11 52446
12 50229
13 27861
14 31413
15 11518
16 13471
17 38766
18 9949
19 61656
20 25797
20 rows selected.
SQL>
This works because passing in ROWNUM forces the evaluation of the function for each row. You should not use ROWNUM as the seed in a production system: timestamps are better. Or concatenate the datetime with the rownum to provide a unique seed for each row.

Haven't tried it, but I believe Oracle is calculating the value for your new_uuid() function once, and outputting for each returned row (same as if you did select systimestamp, whatever from whatever... it would output the same timestamp for all rows.
So, you can modify your function to take some input from each row (for the seed perhaps?), or just use sequences.

Related

Oracle get all matched occurrences from one column with REGEXP

Been using this query but I don't get what I want, anyone can help, please? I want to consolidate all the matched multiple occurrences and display the records in the table.
SELECT REGEXP_SUBSTR(STR,'^C-.+|^WHT.+',1, LEVEL,1,'m') AS CHARGE_NUMBER
FROM
(
SELECT DBMS_LOB.SUBSTR(COL_DATA, 2000,1) AS STR, TRANX_DATE, USER_ID
FROM TABLE_1
)
CONNECT BY LEVEL <= LENGTH(REGEXP_REPLACE(STR,'^C-.+|^WHT.+',1,1,'m'))+1
Data in one Column
A-DATE|27SEP2021|TRANSACTION_NUMBER|0001|USERID|1000
B-THE|QUICK|BROWN|123467899|OVER|THE|1234567899|DOG
C-50000000001
WHT639281234567
B-THE|123456789|BROWN|FOX|OVER|THE|LAZY|DOG
C-50000000002
WHT639281234568
B-THE|1234567899|BROWN|123457899|OVER|THE|LAZY|DOG
C-50000000003
B-THE|QUICK|BROWN|1234567899|OVER|1234567899|LAZY|DOG
WHT639281234569
B-THE|QUICK|BROWN|FOX|OVER|THE|LAZY|DOG
C-50000000004
WHT639281234570
B-THE|1234567899|BROWN|123467899|OVER|THE|1234567899|DOG
DESIRED RESULT:
CHARGE_NUMBER
50000000001
639281234567
50000000002
639281234568
50000000003
639281234569
50000000004
639281234570
Here's one option: select values you need (using regex), then remove superfluous characters (C- and WHT), looping through the string as many times as there are WHTs times 2 (because you have C- and WHTs to look for).
SQL> with test (col) as
2 (select 'A-DATE|27SEP2021|TRANSACTION_NUMBER|0001|USERID|1000
3 B-THE|QUICK|BROWN|FOX|OVER|THE|LAZY|DOG
4 C-50000000001
5 WHT639281234567
6 B-THE|QUICK|BROWN|FOX|OVER|THE|LAZY|DOG
7 C-50000000002
8 WHT639281234568
9 B-THE|QUICK|BROWN|FOX|OVER|THE|LAZY|DOG
10 C-50000000003
11 B-THE|QUICK|BROWN|FOX|OVER|THE|LAZY|DOG
12 WHT639281234569
13 B-THE|QUICK|BROWN|FOX|OVER|THE|LAZY|DOG
14 C-50000000004
15 WHT639281234570
16 B-THE|QUICK|BROWN|FOX|OVER|THE|LAZY|DOG' from dual
17 )
18 select replace(replace(regexp_substr(col, 'C-\d+|WHT\d+', 1, level), 'C-', ''), 'WHT', '') charge_number
19 from test
20 connect by level <= regexp_count(col, 'WHT') * 2;
CHARGE_NUMBER
--------------------------------------------------------------------------------
50000000001
639281234567
50000000002
639281234568
50000000003
639281234569
50000000004
639281234570
8 rows selected.
SQL>

SQL Rounding to nearest between 2 numbers

Example
Value entered by the user :34,26 (a+)
Values in the table:
34,14........A+
34,42........A++
34,52........A++
.
.
.
The value entered by the user is between a+ and a++. In order to find the value of user, take the average of the closest upper and closest lower value, rounding up to the upper value if the value entered by the user is greater than the average, if the value entered by the user is lower than the average. Can you help with the sql query that will round to the lower value?
There are probably many ways to do that; as far as I understood, you want to find value (stored in the table) which is the closest to value you enter as a "parameter". If that's so, Oracle-based example (you originally tagged the question as such).
With sample data:
SQL> set ver off
SQL> select * from test;
COL
----------
34,14
34,42
34,52
Query:
SQL> with
2 minimax as
3 -- find values in the TEST table that represent boundaries around entered value
4 (select (select max(t.col) from test t where &&val >= t.col) mincol,
5 (select min(t.col) from test t where &&val <= t.col) maxcol,
6 (select max(t.col) from test t) maxx,
7 (select min(t.col) from test t) minn,
8 v.val
9 from dual v
10 )
11 -- return the closest boundary
12 select
13 case when mincol is null then minn
14 when maxcol is null then maxx
15 when abs(&&val - mincol) < abs(&&val - maxcol) then mincol
16 else maxcol
17 end as result
18 from minimax;
Enter value for val: 34.26
RESULT
----------
34,14
Some more testing:
SQL> undefine val
SQL> /
Enter value for val: 31
RESULT
----------
34,14
SQL> undefine val
SQL> /
Enter value for val: 36
RESULT
----------
34,52
SQL> undefine val
SQL> /
Enter value for val: 34.42
RESULT
----------
34,42
SQL>

Oracle Insert Value Into Middle of Existing Value

This is similar to my previous question. I have the following partial docket numbers:
docket_number
-------------
2012JV592
2016DR138
2018JV84
If the docket number is less than 10 digits, then I need to insert 0's after the second letter until the lenth is 10 digits. Updated docket numbers would look like this.
docket_number
-------------
2012JV0592
2016DR0138
2018JV0084
One option might be to
split docket_number into two parts: first part contains digits and letters, the second contains the trailing number
result is concatenation of the first part and the second part left padded with zeroes up to total length of 10 characters
SQL> with test (docket_number) as
2 (select '2012JV592' from dual union all
3 select '2016DR138' from dual union all
4 select '2018JV84' from dual
5 ),
6 temp as
7 (select docket_number,
8 regexp_substr(docket_number, '[[:digit:]]+[[:alpha:]]+') part1,
9 regexp_substr(docket_number, '[[:digit:]]+$') part2
10 from test
11 )
12 select case when length(docket_number) < 10 then
13 part1 || lpad(part2, 10 - length(part1), '0')
14 else docket_number
15 end result
16 from temp;
RESULT
--------------------------------------------------------------------------------
2012JV0592
2016DR0138
2018JV0084
SQL>
How to update rows in a table? By using such a SELECT in UPDATE, e.g.
SQL> select * from test;
DOCKET_NUM
----------
2012JV592
2016DR138
2018JV84
SQL> update test a set
2 a.docket_number =
3 (with temp as
4 (select b.docket_number,
5 regexp_substr(b.docket_number, '[[:digit:]]+[[:alpha:]]+') part1,
6 regexp_substr(b.docket_number, '[[:digit:]]+$') part2
7 from test b
8 )
9 select case when length(t.docket_number) < 10 then
10 t.part1 || lpad(t.part2, 10 - length(t.part1), '0')
11 else docket_number
12 end
13 from temp t
14 where t.docket_number = a.docket_number
15 );
3 rows updated.
SQL> select * from test;
DOCKET_NUM
----------
2012JV0592
2016DR0138
2018JV0084
SQL>
You can split the data into three parts as digit group1, letters group, and digit group2 by using regexp_substr() functions, and lpad() function in order to add zeroes just before the second digit group, and then concatenate them directly by using || operators, assuming that you have the same data model for the whole table,
UPDATE t
SET docket_number = regexp_substr(docket_number,'[[:digit:]]+')||
regexp_substr(docket_number,'[[:alpha:]]+')||
lpad('0',10-length(docket_number),'0')||
regexp_substr(docket_number,'[[:digit:]]+$')
Demo
Is it the case that your docket_number should always follow the format 4 digits (year?) followed by 2 letters followed by 4 digits. Then simple sub-string of the docket_number and subsequent re-concatenation is sufficient.
select docket_number
, substr(docket_number,1,6) || lpad(nvl(substr(docket_number,7),'0'),4,'0')
from test_dn
where length(docket_number) < 10
order by docket_number;
and for update:
update test_dn
set docket_number = substr(docket_number,1,6) || lpad(nvl(substr(docket_number,7),'0'),4,'0')
where length(docket_number) < 10;
If the format holds true then, depending on table size, this could be significantly faster as regular expressions are relative slow.

SQL Inserting in loop using value (sysguid) multiple times

I currently have the following script for inserting some dummy data into one of my tables for testing purposes.
I can call it and pass the number of entries I would like to have. That's working well.
Now the problem is that some of the data is not working.
The CRecordID should have in all three lines (12, 17, 19) the same value.
Is there a way to archive this?
WHENEVER SQLERROR EXIT
PROMPT inserting
INSERT INTO MySchema.MyTable (MYRECORD, TMSTAMP, SHORTRECORD, CRecordID) (
SELECT
'<MYRECORD>
<TimeStamp>'||TO_CHAR(SYSDATE,'DD.MM.YY')||' '||to_char(to_date('2000-01-01', 'yyyy-mm-dd')+dbms_random.value(1,1000), 'HH24:MI:SS')||'</TimeStamp>
<User>Test_User_1</User>
<Application>Application1</Application>
<Action>Dummy action text</Action>
<INFO>dummy info text</INFO>
<CRecordID>'||'CID_'||sys_guid()||'</CRecordID> -- line 12
</MYRECORD>',
to_date(SYSDATE,'DD.MM.YY'),
'<SHORTRECORD>
<User>Test_User_1</User>
<CRecordID>'||'CID_'||sys_guid()||'</CRecordID> -- line 17
</SHORTRECORD>',
'CID_'||sys_guid() -- line 19
FROM DUAL connect by level <= &1
);
COMMIT;
PROMPT inserting done
Note: The Database is an Oracle DB.
Select SYS_GUID() separately (using a CTE, for example, as in my example) and concatenate its value with your columns (I used L12 and L17 to indicate lines 12 and 17 you mentioned).
SQL> with gujda as
2 (select sys_guid() guj
3 from dual
4 connect by level <= 4
5 )
6 select 'L12_' || g.guj l12,
7 'L17_' || g.guj l17
8 from gujda g;
L12 L17
------------------------------------ ------------------------------------
L12_EFB5A4947D2E4B7BBE6017E57C673ABF L17_EFB5A4947D2E4B7BBE6017E57C673ABF
L12_3E2D5B50D7C44C7FA6073A9F739687CF L17_3E2D5B50D7C44C7FA6073A9F739687CF
L12_724C21F7914B423B8CBDDC6A44AD2016 L17_724C21F7914B423B8CBDDC6A44AD2016
L12_F15D6C9865424E5C8FFFEA9C09DD6D37 L17_F15D6C9865424E5C8FFFEA9C09DD6D37
SQL>

Oracle Command not properly ended

I am doing the following:
SELECT * FROM word_utils.substring_matches('abac');
Which from what I read should get the out parameter to display from this stored procedure.
It is declared as so
procedure substring_matches( str in varchar2, validwords out charcollection)
Why am I getting an error of Command not properly ended when I try doing this?
I can't figure out how I am supposed to be able to SELECT from this so I can test my results
charcollection is defined as such type charcollection is table of varchar(12);
You cannot select from procedures. Try PIPELINED functions.
10:59:12 SYSTEM#dwal> create type tt as table of number
10:59:15 2 /
Type created.
Elapsed: 00:00:00.01
10:59:16 SYSTEM#dwal> create or replace function f
10:59:23 2 return tt pipelined as
10:59:30 3 begin
10:59:31 4 for i in 1 .. 10 loop
10:59:35 5 pipe row (i);
10:59:42 6 end loop;
10:59:44 7 end f;
10:59:46 8 /
Function created.
Elapsed: 00:00:00.16
10:59:47 SYSTEM#dwal> select * from table(f);
COLUMN_VALUE
------------
1
2
3
4
5
6
7
8
9
10
10 rows selected.
Elapsed: 00:00:00.02