HOW TO FILL A TYPE OR COLLECTION WITH NUMBERS FROM 10M TO 30M IN PLSQL ORACLE - oracle

Hello stackoverflow friends!
Please help, i have to assign a value since 100 to 300 in an cursor or a type or a table (i don't know which of them are better for this project). First i want to fill the type or cursor with those values (10M to 30M) and then choose randomly one of them but just once, i mean, it can't pick 102 twice, for example. There are millions of entries and i wouldn't want to affect the performance of the database. I tried do it with a cursor and n+1 but its so slow...
Thanks my Oracle's friends for your help and suggestions.

I believe n + 1 is slow; if you do it that way, it certainly takes time to insert 20 million rows one by one. Another option is to use row generator.
I have 21XE on laptop (nothing special; Intel i5, 8GB RAM) which isn't enough memory to do it in one go:
SQL> create table test (id number);
Table created.
SQL> insert into test
2 select 10e6 + level - 1
3 from dual
4 connect by level <= 20e6;
insert into test_20mil
*
ERROR at line 1:
ORA-30009: Not enough memory for CONNECT BY operation
SQL>
If you ask whether it really works?, the answer is yes - see for a small sample:
SQL> select level
2 from dual
3 connect by level <= 5;
LEVEL
----------
1
2
3
4
5
SQL>
Therefore, I used a loop to do it 20 times, each of them inserting 1 million rows at a time into a table (not row-by-row). Why not collection? Memory issues!
SQL> create table test (id number primary key, cb_picked number(1));
Table created.
SQL> set serveroutput on
SQL> set timing on
SQL> declare
2 l_mil number := 10e6;
3 begin
4 for i in 1 .. 20 loop
5 insert into test (id)
6 select l_mil + level - 1
7 from dual
8 connect by level <= 1e6;
9 dbms_output.put_Line('inserted ' || sql%rowcount);
10 l_mil := l_mil + 1e6;
11 end loop;
12 end;
13 /
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
PL/SQL procedure successfully completed.
Elapsed: 00:01:25.77
As you can see, it took slightly less than minute and a half.
SQL> set timing off
SQL> select count(*) from test;
COUNT(*)
----------------
20,000,000
SQL> select min(id), max(id) from test;
MIN(ID) MAX(ID)
---------------- ----------------
10,000,000 29,999,999
SQL>
That was inserting; what about fetching randomly picked rows? Use dbms_random.value function. To avoid selecting already picked values twice, update table's cb_picked column. To do that, create an autonomous transaction function (why? So that you could perform DML - update - and return value).
SQL> create or replace function f_rnd
2 return number
3 is
4 pragma autonomous_transaction;
5 retval number;
6 begin
7 select id
8 into retval
9 from test
10 where cb_picked is null
11 and id = round(dbms_random.value(10e6, 20e6));
12 update test set cb_picked = 1
13 where id = retval;
14 commit;
15 return retval;
16 end;
17 /
Function created.
Let's try it:
SQL> select f_rnd from dual;
F_RND
----------
19191411
SQL> select f_rnd from dual;
F_RND
----------
16411522
SQL> select * from test where cb_picked = 1;
ID CB_PICKED
---------- ----------
16411522 1
19191411 1
SQL>
That's it, I guess.

Related

limit the amount of trigger updates for a specific row in oracle

I want to audit update changes in a specific table, and for that I've created a trigger that tracks every time a row is updated, it then write the updated changes into a new historical table:
create table test (id number generated always as identity,name varchar2(10) default null, school varchar2(10) null);
insert into test (name,school) values ('John','MIT');
insert into test (name,school) values ('Max','Oxford');
create table test_history (id int,name varchar2(10), school varchar2(10));
create or replace trigger test_trigger
after update
of name,school
on test
for each row
begin
insert into test_history
values
(
:old.id,
:new.name,
:new.school
);
end;
/
What I would like to do is to limit the amount a specific row is updated to a certain value. For example, the following update statement can only be executed 10 times:
update test
set
name = 'Jason'
where id = 1;
In this way if I execute the above statement 10 times it should work, but if the execution happens the 11th time it should fail. So the maximum amount of rows of a specific unique id is 10.
Count number of rows in the history table and raise an error if it exceeds value you find appropriate.
SQL> create or replace trigger test_trigger
2 after update
3 of name,school
4 on test
5 for each row
6 declare
7 l_cnt number;
8 begin
9 select count(*) into l_cnt
10 from test_history
11 where id = :new.id;
12
13 if l_cnt <= 10 then
14 insert into test_history
15 values
16 (
17 :old.id,
18 :new.name,
19 :new.school
20 );
21 else
22 raise_application_error(-20000, 'Too many updates');
23 end if;
24 end;
25 /
Trigger created.
Update:
SQL> update test set name = 'Jason' where id = 1;
1 row updated.
<snip>
SQL> update test set name = 'Jason' where id = 1;
1 row updated.
SQL> update test set name = 'Jason' where id = 1;
update test set name = 'Jason' where id = 1
*
ERROR at line 1:
ORA-20000: Too many updates
ORA-06512: at "SCOTT.TEST_TRIGGER", line 17
ORA-04088: error during execution of trigger 'SCOTT.TEST_TRIGGER'
SQL>

Need to create a trigger that updates without adding same number

I need to create a trigger that activates after i make an update in Table A, registering in an audit log the number that i updated in Table A, but if the number has already been added (example the trigger tries to add 1 when there is a 1 already)it must ignore it and only let the first one.
Example:
Table A updates with 5,5,6,8,4,4
Then the audit log must save 5,6,8,4
The trigger i have already:
CREATE OR REPLACE TRIGGER registro_aeropuerto
AFTER UPDATE ON AEROPUERTO
FOR EACH ROW
DECLARE
A INT;
B INT;
BEGIN
A := table_A_updated_column_value;
SELECT CASE
WHEN EXISTS(SELECT * FROM Audit_log WHERE A = Coordinator)
THEN 1
ELSE 0
END INTO B FROM DUAL;
IF B = 0
THEN
INSERT INTO Audit_log(Coodinator, Date) VALUES (A, trunc(sysdate));
END;
Whenever i try to execute the trigger it gives me the next error:
The symbol ";" has been found when the it was expected:
Sample tables (upd_col_value is column you're updating; you named it "table_A_updated_column_value")
SQL> create table aeropuerto (upd_col_value number);
Table created.
SQL> create table audit_log (coordinator number, datum date);
Table created.
Trigger can be simplified; no need to declare any additional variables nor to check first and insert next; do it in the same select statement:
SQL> create or replace trigger registro_aeropuerto
2 after update on aeropuerto
3 for each row
4 begin
5 insert into audit_log (coordinator, datum)
6 select :new.upd_col_value, sysdate
7 from dual
8 where not exists (select null
9 from audit_log a
10 where a.coordinator = :new.upd_col_value
11 );
12 end;
13 /
Trigger created.
Testing:
SQL> insert into aeropuerto (upd_col_value) values (1);
1 row created.
SQL> select * from audit_log;
no rows selected
There's nothing in the log because nothing was updated. So, let's update it:
SQL> update aeropuerto set upd_col_value = 5;
1 row updated.
SQL> select * from audit_log;
COORDINATOR DATUM
----------- -------------------
5 15.09.2021 07:14:46
SQL>
OK; log now contains a row. Another update:
SQL> update aeropuerto set upd_col_value = 6;
1 row updated.
SQL> select * from audit_log;
COORDINATOR DATUM
----------- -------------------
5 15.09.2021 07:14:46
6 15.09.2021 07:15:37
SQL>
Right; two rows, as 5 was updated to 6. What happens if we update 6 back to 5?
SQL> update aeropuerto set upd_col_value = 5;
1 row updated.
SQL> select * from audit_log;
COORDINATOR DATUM
----------- -------------------
5 15.09.2021 07:14:46
6 15.09.2021 07:15:37
SQL>
Nothing happened; row with coordinator = 5 was in the table already so new row wasn't added.

Create Fucntion returning Table in pl/sql

TEMP table:
Node
Curr_Cnt
Prev_Cnt
Diff
First
20
40
20
Second
30
70
40
CREATE OR REPLACE FUNCTION NEW_FUNCTION
RETURNS table
IS
c_rec TEMP%ROWTYPE;
TYPE c_tab IS TABLE OF c_rec%TYPE INDEX BY PLS_INTEGER;
l_c_tab c_tab;
BEGIN
SELECT * INTO c_tab FROM
--**The below with clause starting from with returns the same table structure as above temp table**
( WITH batch_id_dtl AS
(SELECT a.*,
rownum rnum
FROM
(SELECT MIN(creation_date) min_cr,
MAX(creation_date) max_cr,
batch_id
FROM oalterr.q_audit_results
GROUP BY batch_id
ORDER BY 1 DESC
)a
WHERE BATCH_ID <= 251940
),
curr_cnt AS
......rest of the code......
);
RETURN C_TAB;
END NEW_FUNCTION;
The above function returns the following error:
expression 'C_TAB' is inappropriate as the left hand side of an assignment statement.
Can anyone please tell me what type should I add in return part and what am I doing wrong in the execution part between begin and end.
For sample table (the one you posted)
SQL> select * from temp;
NODE CURR_CNT PREV_CNT DIFF
------ ---------- ---------- ----------
First 20 40 20
Second 30 70 40
create type at SQL level so that function recognizes it:
SQL> create or replace type t_row as object
2 (node varchar2(10),
3 curr_cnt number,
4 prev_cnt number,
5 diff number);
6 /
Type created.
SQL> create or replace type t_tab as table of t_row;
2 /
Type created.
Function returns a type; for example, number, varchar2 or - in your case - t_tab:
SQL> create or replace function new_function
2 return t_tab
3 is
4 l_tab t_tab;
5 begin
6 select t_row(node, curr_cnt, prev_cnt, diff)
7 bulk collect
8 into l_tab
9 from temp;
10 return l_tab;
11 end new_function;
12 /
Function created.
So far so good. Now, call it. One way is straightforward, just like selecting sysdate (which is also a function):
SQL> select new_function from dual;
NEW_FUNCTION(NODE, CURR_CNT, PREV_CNT, DIFF)
--------------------------------------------------------------------------------
T_TAB(T_ROW('First', 20, 40, 20), T_ROW('Second', 30, 70, 40))
but that looks kind of ugly. Better way is
SQL> select * from table(new_function);
NODE CURR_CNT PREV_CNT DIFF
---------- ---------- ---------- ----------
First 20 40 20
Second 30 70 40
SQL>
The easiest way is to create the function with the PIPELINED clause and then, in body, using the native PL/SQL approach, in a loop iterate trough your select as cursor and return each row with PIPE ROW.
Aditionaly, it would be better to declare the return type and the table function in a package and to avoid generic return types.
Ofc. you then use such a function as
select * from table(package.function())
See also: https://docs.oracle.com/cd/B19306_01/appdev.102/b14289/dcitblfns.htm

inconsistent output between sql developer and stored procedure

When I execute the below query it will return C507,
but when I use it as sub query in one of my stored procedure it only returns C.
SELECT *
FROM ( SELECT MAX (REGEXP_SUBSTR ('C507|C507',
'[^|]+',
1,
LEVEL))
serial
FROM DUAL
CONNECT BY LEVEL <=
LENGTH ('C507|C507')
- LENGTH (REPLACE ('C507|C507', '|', ''))
+ 1);
I couldn't see any problem( tell us if your way is different ) :
SQL> Create or Replace Procedure Get_Serial( o_value out varchar2 ) Is
2 Begin
3 Select *
4 Into o_value
5 From (Select Max(Regexp_Substr('C507|C507', '[^|]+', 1, Level)) serial
6 From Dual
7 Connect By Level <= Length('C507|C507') -
8 Length(Replace('C507|C507', '|', '')) + 1);
9 End;
10 /
Procedure created
SQL> var serial varchar2;
SQL> exec Get_Serial(:serial);
PL/SQL procedure successfully completed
serial
---------
C507
It is a little bit difficult to guess what you really are doing (as you posted different queries with different input data, unformatted within the comment) so - have a look a this example.
I've modified your last query so that it returns DEP_ID and MAX piece of the NAME column.
SQL> CREATE TABLE dept
2 (
3 dep_id NUMBER,
4 name VARCHAR2 (50)
5 );
Table created.
SQL> INSERT INTO dept VALUES (1, 'mirko|mirko');
1 row created.
SQL> INSERT INTO dept VALUES (2, 'angelo|angelo');
1 row created.
SQL> INSERT INTO dept VALUES (3, 'angelo|mirko');
1 row created.
SQL> SELECT * FROM dept;
DEP_ID NAME
---------- --------------------------------------------------
1 mirko|mirko
2 angelo|angelo
3 angelo|mirko
SQL>
SQL> SELECT d.dep_id,
2 MAX (REGEXP_SUBSTR (d.name,
3 '[^|]+',
4 1,
5 LEVEL))
6 serial
7 FROM dept d
8 WHERE dep_id = &par_dep_id
9 CONNECT BY LEVEL <= LENGTH (d.name) - LENGTH (REPLACE (d.name, '|', '')) + 1
10 GROUP BY d.dep_id;
Enter value for par_dep_id: 1
DEP_ID SERIAL
---------- --------------------------------------------------
1 mirko
SQL> /
Enter value for par_dep_id: 2
DEP_ID SERIAL
---------- --------------------------------------------------
2 angelo
SQL> /
Enter value for par_dep_id: 3
DEP_ID SERIAL
---------- --------------------------------------------------
3 mirko
SQL>
Saying that you have problems with a stored procedure: you should post (by editing your initial message) its code. Is it really a procedure, or a function? Query returns 2 values - does that procedure have 2 OUT parameters? If it is a function, what does it return (what datatype)?
From my point of view, query is OK as is, but - you wouldn't be posting a question here if there's no problem, but it is unclear which problem you have.
Once again: I suggest you post copy/paste of your own SQL*Plus session so that we could see what you did and how Oracle responded. Without it, we're just blindly guessing.
I created a function for this query and call it on my SP instead of using it as a sub query on my stored procedure.

Trying to make a job to be executed after each 1 minute,but its not working?

I have made this job,that should be executed in an interval of 1 minute,but it's not working. When I use execute dbms_job.run(2); it gets executed. printe is a procedure
Please suggest!
BEGIN
DBMS_JOB.SUBMIT (
job =>:job_no,
WHAT=>'printe;',--Procedure
next_date=>sysdate+1/24*60,
interval=>'sysdate+1/24*60'
);
commit;
END;
Try next_date = sysdate + (1/24/60) and interval = (1/24/60)...
Here is a simple job.
SQL> create table log1 (ts timestamp)
2 /
Table created.
SQL> create or replace procedure printe as
2 begin
3 insert into log1 values (systimestamp);
4 commit;
5 end;
6 /
Procedure created.
SQL>
So the first thing is to submit it with both the start time and interval correctly specified. If you cannot remember how many minutes there are in a day (1440) it is a good idea to use brackets. Let's compare submitting the job with your date specifications ...
SQL> var job_no number
SQL> BEGIN
2 DBMS_JOB.SUBMIT
3 (
4 job =>:job_no,
5 WHAT=>'printe;',--Procedure
6 next_date=>sysdate+1/24*60,
7 interval=>'sysdate+1/24*60'
8 );
9 commit;
10 END;
11 /
PL/SQL procedure successfully completed.
SQL> print job_no
JOB_NO
----------
71
SQL>
... with brackets to assert precedence ...
SQL> BEGIN
2 DBMS_JOB.SUBMIT
3 (
4 job =>:job_no,
5 WHAT=>'printe;',--Procedure
6 next_date=>sysdate+1/(24*60),
7 interval=>'sysdate+1/(24*60)'
8 );
9 commit;
10 END;
11 /
PL/SQL procedure successfully completed.
SQL> print job_no
JOB_NO
----------
72
SQL>
Clearly job 71 has not run and isn't going to run for some time yet:
SQL> select job, what, last_date, next_date, interval
2 from user_jobs
3 where job in (71,72)
4 /
JOB WHAT LAST_DATE NEXT_DATE INTERVAL
------ ------------ -------------------- -------------------- -----------------
71 printe; 05-MAY-2010 17:35:34 sysdate+1/24*60
72 printe; 03-MAY-2010 05:44:42 03-MAY-2010 05:45:34 sysdate+1/(24*60)
SQL>
Monitoring job 72 ....
SQL> select * from log1
2 /
TS
-------------------------------------------------------------------
03-MAY-10 05:43:39.250000
03-MAY-10 05:44:42.296000
SQL>
So, if this still isn't working for you, what should you be doing? The first thing is to check whether the database is configured to run jobs at all. You will need DBA access for this.
SQL> select value
2 from v$parameter
3 where name='job_queue_processes'
4 /
VALUE
-------------------------
1000
SQL>
If I remember correctly, in Oracle 9i the default value for this parameter is 0. It needs to be set to some non-zero value for jobs to run.
And if that isn't the problem you need to check for error messages in the alert log. The background_dump_dest directory may also have some .trc files produced by a failing job.

Resources