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
Related
Basically I need to use (for loop) in Oracle SQL Developer to show the average of the average for students whose age is 20 or below. Down Below is the students' table
I guess it is a homework (funny requirements for simple situations). Anyway, see if something like this helps.
SQL> set serveroutput on
SQL>
SQL> declare
2 l_avg number;
3 l_sum number := 0;
4 l_cnt number := 0;
5 begin
6 for cur_r in (select avg from test where age <= 20) loop
7 l_cnt := l_cnt + 1;
8 l_sum := l_sum + cur_r.avg;
9 end loop;
10
11 if l_cnt > 0 then
12 l_avg := l_sum / l_cnt;
13 end if;
14
15 dbms_output.put_line('Average is ' || nvl(to_char(l_avg), 'unknown'));
16 end;
17 /
Average is 68
PL/SQL procedure successfully completed.
SQL>
Try this.
SELECT AVG("AVG") FROM students WHERE age<=20
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>
Say I have the table XX_TABLE_SAMPLE with the records below:
TAB_ID BATCH_NAME EMP_NO EMP_NAME STATUS SALARY CATEGORY
------ ---------- ------ -------- -------- ------- ------------
1 BATCH_A 1 Jared Active 1000 (NULL)
2 BATCH_A 2 Siege Active 3000 (NULL)
3 BATCH_A 45 James Suspended 2000 (NULL)
4 BATCH_B 67 Harry Active 100 (NULL)
5 BATCH_B 99 Pikachu Active 10000 (NULL)
6 BATCH_x 100 Eren Suspended 4000 (NULL)
and i have the PL/SQL block like below (please note the comments):
declare
cursor emp_cur is
select *
from XX_TABLE_SAMPLE
where status = 'Active';
type emp_cur_type is table of XX_TABLE_SAMPLE%rowtype index by pls_integer;
emp_rec emp_cur_type;
begin
open emp_cur;
fetch emp_cur
bulk collect
into emp_rec;
close emp_cur;
/* do some pre-processing here via another stored procedure
but the problem is, it has a parameter of p_batch_name, not a type of associative array
for i in emp_rec.first..emp_rec.last loop
pay_pkg.validate_pay (p_batch_name => emp_rec(i).p_batch_name);
end;
-- the problem with this is that it will loop 4 times (twice each for BATCH_A and BATCH_B)
when it should only get the 2 batch names (BATCH_A and BATCH_B)
*/
-- then check the salary of the emp and update the associative array
for i in emp_rec.first..emp_rec.last loop
if emp_rec(i).salary > 200 and emp_rec(i).salary < 3000 then
emp_rec(i).CATEGORY = 'Manager';
end if;
end loop;
forall i in emp_rec.first..emp_rec.last
update XX_TABLE_SAMPLE
set CATEGORY = emp_rec(i).CATEGORY
where TAB_ID = emp_rec(i).TAB_ID;
end;
With that, I would like to get the distinct values of the Element Batch_Name in an Associative Array
and then pass it to the Stored Procedure pay_pkg.validate_pay.
Any thoughts on how i can achive this without declaring another Explicit Cursor?
To me it seems that you are thinking in unnecessary complex solution. I think your example can be simplified to the following solution that requires zero PL/SQL data structures (r is an implicit record type but the compiler makes it for you!).
Btw, there is no need to be afraid of cursors in PL/SQL.
declare
-- a dummy placeholder for the "tricky" subprogram
procedure validate_pay(p_batch_name in varchar2) is begin null; end;
begin
for r in (select distinct batch_name
from xx_sample_data
where status = 'Active')
loop
validate_pay(p_batch_name => r.batch_name);
end loop;
update xx_sample_data set
category = 'Manager'
where status = 'Active'
and salary between 201 and 2999
;
end;
/
Maybe is something else you aren't saying, but if you need {"BATCH_A", "BATCH_B"}
Why dont just use:
SELECT DISTINCT BATCH_NAME
FROM XX_TABLE_SAMPLE
WHERE status = 'Active'
AND salary > 200
AND salary < 3000
if you are on oracle 12 there is another way.
but it involves selecting from your associative array
see
Select from PLSQL Associative array?
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.
I am putting a bit of SQL into an Oracle script, if I run the Vanilla SQL i get the correct output of one single returned value/record. However in my custom function I get the value I am looking for returned as many times as there are records. Here is an example of what I have.
create function EXAMPLE_FUNCTION (passedID in NUMBER)
return NUMBER
IS
returnValue NUMBER;
BEGIN
SELECT "TABLE1"."ID" INTO returnValue
FROM "TABLE1" WHERE "TABLE1"."ID" = passedID;
RETURN returnValue;
END;
So if TABLE1 has 20 records I will get the record with ID 1 returned 20 times,
I am not sure where its going wrong, but I'm sure its simple!
You are probably calling the function like this:
select EXAMPLE_FUNCTION (1)
from my_table
Call it without the from:
select EXAMPLE_FUNCTION (1)
EDIT:
As pointed by Shannon Oracle requires the from clause so I searched and found examples using the dual table:
select EXAMPLE_FUNCTION (1)
from dual
Just do something like: SELECT EXAMPLE_FUNCTION(1) FROM dual;
Would something like the below work in your function?
Select COUNT(*) into returnValue
from TABLE1
where Table1.ID = passedID;
What value are you using for passedID? If you want to get soemthing different out for each row you need to pass something different in.
Compare these two function calls. First a fixed value:
SQL> select example_function(1) from table1
2 /
EXAMPLE_FUNCTION(1)
-------------------
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
20 rows selected.
SQL>
Then using the table column to supply the parameter:
SQL> select example_function(id) from table1
2 /
EXAMPLE_FUNCTION(ID)
--------------------
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
20 rows selected.
SQL>