Oracle Loop Rule 4809 - oracle

I need to write a procedure that will insert thousands of rows in a table and use the auto generated id resulted from these rows and use it in other inserts.
I used a for loop in which I save the sequence id in a variable then use it in my inserts.
declare
first_id integer;
BEGIN
FOR texts in (select distinct text from table_texts )
LOOP
first_id := SEQ_IDS_OBJECTID.NEXTVAL;
INSERT INTO table_1(id,some_fields)
VALUES (first_id, 'blablabla');
insert into table_2 (id,text_field)
VALUES (first_id, texts.text);
END LOOP;
commit;
END;
I think that this is not the ideal way to achieve what I need. Also when I enter the code in TOAD , I get the following warning :
Rule 4809 (A loop that contains DML statements should be refactored to use BULK COLLECT and FORALL)
Is there better way to do it?
EDIT:
the above code was simplified. But I think I have to expose more of it to explain the case :
declare
first_id integer;
second_id integer;
BEGIN
FOR texts in (select distinct text1 , text2 from mdf )
LOOP
first_id := XAKTA.SEQ_IDS_OBJECTID.NEXTVAL;
select id_1 into second_id from table_3 where field_1 =texts.text1 ;
INSERT INTO table_1(id_1,id_2,some_fields)
VALUES (first_id ,second_id ,'blablabla');
insert into table_2 (id,text1,text2)
VALUES (first_id, texts.text1,texts.text2);
END LOOP;
commit;
END;

You can use FORALL to insert batches of items from your cursor:
DECLARE
TYPE texts_tab IS TABLE OF table_texts.text%TYPE;
TYPE ids_tab IS TABLE OF table_2.id%TYPE;
p_texts texts_tab;
p_ids ids_tab;
CURSOR c IS
SELECT DISTINCT text FROM table_texts;
BEGIN
OPEN c;
LOOP
FETCH c BULK COLLECT INTO p_texts LIMIT 100;
FORALL i IN 1 .. p_texts.COUNT
INSERT INTO table_2 ( id, text_field )
VALUES ( SEQ_IDS_OBJECTID.NEXTVAL, p_texts(i) )
RETURNING id BULK COLLECT INTO p_ids;
FORALL i IN 1 .. p_ids.COUNT
INSERT INTO table_1( id, some_fields )
VALUES ( p_ids(i), 'blablabla' );
EXIT WHEN c%NOTFOUND;
END LOOP;
CLOSE c;
COMMIT;
END;
/
db<>fiddle here

Related

how to add a list of names in a insert into?

On Oracle, I have a list of name such as 'a','b','c' and I would like to insert them in a table. The dimension of this list can change. so cannot do it like :
insert all
into TEMP (name) values ('a')
into TEMP (name) values ('b')
into BBU (name) values ('b')
into BBU (name) values ('c')
select * from TEMP;
How can I make the insert into more dynamic ?
Thanks
Use a VARRAY or a collection:
INSERT INTO TEMP ( name )
SELECT COLUMN_VALUE
FROM TABLE( SYS.ODCIVARCHAR2LIST( 'a', 'b', 'c', 'd', 'e' ) );
Or use a delimited list and then split it (there are multiple solutions for splitting delimited strings at that link but one is):
INSERT INTO TEMP ( name )
SELECT x.item.getStringVal()
FROM XMLTABLE(
( '"a","b","c","d","e"' )
COLUMNS item XMLTYPE PATH '.'
) x;
Use PL/SQL instead. And loop through.
DECLARE
CURSOR myCur IS
SELECT name FROM myTable;
TYPE xTab IS TABLE OF myCur%ROWTYPE;
L_tab xTab;
BEGIN
OPEN myCur;
FETCH myCur INTO L_tab;
CLOSE myCur;
FORALL i IN 1..L_tab.COUNT
INSERT INTO myTab2 (name) VALUES (L_tab(i).name);
COMMIT;
END;
/
Now if you are worried to exceed your tempspace (redo logs) coz the list is millions of records, then use limit:
DECLARE
myLimit NUMBER := 32000;
myFlag BOOLEAN := FALSE;
CURSOR myCur IS
SELECT name FROM myTable;
TYPE xTab IS TABLE OF myCur%ROWTYPE;
L_tab xTab;
BEGIN
OPEN myCur;
LOOP
FETCH myCur INTO L_tab LIMIT myLimit;
IF myCur%NOTFOUND
THEN
IF L_tab.COUNT != 0
THEN
myFlag := TRUE;
ELSE
EXIT;
END IF;
END IF;
FORALL i IN 1..L_tab.COUNT
INSERT INTO myTab2 (name) VALUES (L_tab(i).name);
COMMIT;
EXIT WHEN myFlag = TRUE;
END LOOP;
CLOSE myCur;
END;
/
If your data is saved in some column of a given table, use this
insert into TEMP (name)
select name_column from given_table;
commit;

I am using cursor with a dynamic sql but when using further with bulk binding results are not coming

With PL/SQL oracle, I am using cursor with a dynamic sql but when using further with bulk binding results are not coming, but when using without bulk binding it works. Please suggest what am i missing here, sample below is a code snippet for your reference, although it is not the exact code but will give you an overview what i am trying to do.
cur_data SYS_REFCURSOR;
TYPE sample_data IS RECORD
(
col1 VSAMPLEDATA.COL1%TYPE,
col2 VSAMPLEDATA.COL2%TYPE
);
TYPE reclist IS TABLE OF sample_data ;
rec reclist;
Begin
p_query_string:='SELECT * from VSAMPLEDATA where COL2=:pVal';
OPEN cur_data FOR p_query_string USING 'DATA1';
LOOP
FETCH cur_data
BULK COLLECT INTO rec LIMIT 1000;
EXIT WHEN
rec.COUNT = 0;
FOR indx IN 1 .. rec.COUNT
LOOP
doing something;
END LOOP
END LOOP
CLOSE cur_data ;
You can avoid dynamic SQL and build a simpler, safer parametric cursor; for example, reflecting the structure of your code:
DECLARE
TYPE sample_data IS RECORD(col1 VSAMPLEDATA.COL1%TYPE, col2 VSAMPLEDATA.COL2%TYPE);
TYPE reclist IS TABLE OF sample_data;
--
rec reclist;
--
CURSOR cur_data(param IN VARCHAR2) IS
SELECT *
FROM VSAMPLEDATA
WHERE COL2 = param;
BEGIN
OPEN cur_data('DATA1');
LOOP
FETCH cur_data BULK COLLECT INTO rec LIMIT 1000;
EXIT WHEN rec.COUNT = 0;
FOR indx IN 1 .. rec.COUNT
LOOP
DBMS_OUTPUT.put_line(rec(indx).col1 || ' - ' || rec(indx).col2);
END LOOP;
END LOOP;
CLOSE cur_data;
END;
Test case:
create table VSAMPLEDATA(col1, col2) as
select 1, 'DATA1' from dual union all
select 2, 'DATA2' from dual
The result:
1 - DATA1
You are using sys_refcursor in wrong way.Also there is no need to use cursor in your case. sys_refcursor can be used to pass cursors from and to a stored procedure. SYS_REFCURSOR is a pre-declared weak ref cursor.
Also you are not executing the query so no result when using a bind variable. Execute Immediate is missing while using a binding the variable. See below example:
declare
TYPE sample_data IS RECORD
(
col1 EMPLOYEE.EMPLOYEE_ID%type,
col2 EMPLOYEE.FIRST_NAME%type
);
TYPE reclist IS TABLE OF sample_data;
rec reclist;
p_query_string varchar2(1000);
BEGIN
p_query_string := 'SELECT EMPLOYEE_ID,FIRST_NAME from EMPLOYEE where EMPLOYEE_ID=:pVal';
Execute immediate p_query_string BULK COLLECT INTO rec USING 1;
FOR indx IN 1 .. rec.COUNT
LOOP
--doing something;
dbms_output.put_line(rec(indx).col1);
END LOOP;
End;
Hi everyone this one has been resolved. It was always working, only problem was with the underlying query which had some conditional checks due to that results were not coming.

Oracle: Trying to loop thru insert statement using dynamic list of table names

I'm not quite understanding the solution found here:
Selecting Values from Oracle Table Variable / Array?
I have a list of table names. I would like to loop thru them as an array, using their values as tables to search from.
TMP_DORMANT_FILTERS physical table of table names. The array below is the same list.
LM_DORMANT_EMAIL is a list of email addresses.
I want to check the existence of the dormant email addresses in the list of tables. I realize I could write the same query 12 times to search each table. But that's not going to improve my SQL skills.
Here is my array attempt. In this attempt, Oracle doesn't like the way I'm calling the array value in my where not exists sql:
DECLARE
TYPE array_t IS VARRAY(12) OF VARCHAR2(25);
ARRAY array_t := array_t('BT_ABANDONED_HIST', 'BT_BROWSED_HIST', 'BT_PURCHASED_HIST', 'CM_ABANDONED_HIST', 'CM_BROWSED_HIST', 'CM_PURCHASED_HIST', 'CM_PAGE_VIEWS_HIST', 'MB_ABANDONED_HIST', 'MB_BROWSED_HIST', 'MB_CARTED_HIST', 'MB_PAGE_VIEWS_HIST', 'MB_PURCHASED_HIST');
BEGIN
FOR i IN 1..array.count LOOP
INSERT INTO TMP1_DORMANT_EMAIL
SELECT feed.EMAIL_ADDRESS
FROM LM_DORMANT_EMAIL feed
WHERE NOT EXISTS (
SELECT 1 FROM array(i) hist
WHERE ACTIVITY_DATE >= TRUNC(SYSDATE - 90)
AND hist.EMAIL = feed.EMAIL_ADDRESS
);
COMMIT;
END LOOP;
END;
/
Or using the solution found at the link above, I tried. Oracle doesn't recognize my inserting into dormant_filters under the begin part. It's telling me my physical table TMP_DORMANT_FILTERS does not exist:
CREATE GLOBAL TEMPORARY TABLE dormant_filters
( filters varchar2(100)
)
ON COMMIT DELETE ROWS;
BEGIN
INSERT INTO dormant_filters
( filters )
( SELECT TABLE_NAMES
FROM TMP_DORMANT_FILTERS
);
FOR j IN ( SELECT filters FROM dormant_filters ) LOOP
INSERT INTO TMP1_DORMANT_EMAIL
SELECT feed.EMAIL_ADDRESS, j as DORMANT_SOURCE
FROM LM_DORMANT_EMAIL feed
WHERE NOT EXISTS (
SELECT 1 FROM j hist
WHERE feed.ACTIVITY_DATE >= TRUNC(SYSDATE - 90)
AND hist.EMAIL = feed.EMAIL_ADDRESS
);
NULL;
END LOOP;
COMMIT;
END;
/
This problem requires dynamic SQL. Bind variables can be used for values but not for objects.
declare
type array_t is varray(12) of varchar2(25);
array array_t := array_t('BT_ABANDONED_HIST', 'BT_BROWSED_HIST', 'BT_PURCHASED_HIST', 'CM_ABANDONED_HIST', 'CM_BROWSED_HIST', 'CM_PURCHASED_HIST', 'CM_PAGE_VIEWS_HIST', 'MB_ABANDONED_HIST', 'MB_BROWSED_HIST', 'MB_CARTED_HIST', 'MB_PAGE_VIEWS_HIST', 'MB_PURCHASED_HIST');
begin
for i in 1 .. array.count loop
execute immediate '
INSERT INTO TMP1_DORMANT_EMAIL
SELECT feed.EMAIL_ADDRESS
FROM LM_DORMANT_EMAIL feed
WHERE NOT EXISTS (
SELECT 1 FROM '||array(i)||' hist
WHERE ACTIVITY_DATE >= TRUNC(SYSDATE - 90)
AND hist.EMAIL = feed.EMAIL_ADDRESS
)
';
commit;
end loop;
end;
/
UPDATE
If the column names are different for each table you can use the data dictionary to pick the correct column name.
declare
type array_t is varray(12) of varchar2(25);
array array_t := array_t('BT_ABANDONED_HIST', 'BT_BROWSED_HIST', 'BT_PURCHASED_HIST', 'CM_ABANDONED_HIST', 'CM_BROWSED_HIST', 'CM_PURCHASED_HIST', 'CM_PAGE_VIEWS_HIST', 'MB_ABANDONED_HIST', 'MB_BROWSED_HIST', 'MB_CARTED_HIST', 'MB_PAGE_VIEWS_HIST', 'MB_PURCHASED_HIST');
v_column_name varchar2(30);
begin
for i in 1 .. array.count loop
select column_name
into v_column_name
from all_tab_columns
where owner = 'SCHEMA NAME'
and table_name = array(i)
and column_name in ('ACTIVITY_TIME','DATE_ABANDONED');
execute immediate '
INSERT INTO TMP1_DORMANT_EMAIL
SELECT feed.EMAIL_ADDRESS
FROM LM_DORMANT_EMAIL feed
WHERE NOT EXISTS (
SELECT 1 FROM '||array(i)||' hist
WHERE '||v_column_name||' >= TRUNC(SYSDATE - 90)
AND hist.EMAIL = feed.EMAIL_ADDRESS
)
';
commit;
end loop;
end;
/

Bulk Collect Oracle

Can bulk collect be done if data is getting inserted into table A from Table B and while selecting data, substr, instr, trunc functions has been used on columns fetched?
INSERT INTO A
SELECT
DISTINCT
SUBSTR(b.component, 1, INSTR(b.component, ':', 1) - 1),
TRUNC(c.end_dt, 'DDD'),
FROM
B b,
C c
WHERE
TRUNC(c.end_dt)=TRUNC(b.last_Date, 'DDD') ;
How can I insert data into table A using bulk collect?
The only reason you'd use Bulk Collect and FORALL to insert rows is when you absolutely need to process/insert rows in chunks. Otherwise always use SQL.
DECLARE
CURSOR c_data IS
SELECT * FROM source_tab;
--
TYPE t_source_tab IS TABLE OF source_tab%ROWTYPE;
l_tab t_source_tab;
v_limit NUMBER:= 1000;
BEGIN
OPEN c_data;
LOOP
FETCH c_data BULK COLLECT INTO l_tab LIMIT v_limit;
EXIT WHEN l_tab.count = 0;
-- Insert --
FORALL i IN l_tab.first .. l_tab.last
INSERT INTO destination_tab VALUES l_tab(i);
COMMIT;
-- prints number of rows processed --
DBMS_OUTPUT.PUT_LINE ('Inserted ' || SQL%ROWCOUNT || ' rows:');
-- Print nested table of records - optional.
-- May overflow the buffer and slow down the performance if you process many rows.
-- Use for testing only and limit the rows with Rownum or Row_Number() in cursor query:
FOR i IN l_tab.FIRST..l_tab.LAST -- or l_tab.COUNT
LOOP
DBMS_OUTPUT.PUT_LINE (l_tab(i).hire_date ||chr(9)||l_tab(i).last_name ||chr(9)||l_tab(i).first_name);
END LOOP;
END LOOP;
CLOSE c_data;
END
/

SELECT DISTINCT CLOB_COLUMN FROM TABLE;

I would like to find the distinct CLOB values that can assume the column called CLOB_COLUMN (of type CLOB) contained in the table called COPIA.
I have selected a PROCEDURAL WAY to solve this problem, but I would prefer to give a simple SELECT as the following: SELECT DISTINCT CLOB_COLUMN FROM TABLE avoiding the error "ORA-00932: inconsistent datatypes: expected - got CLOB"
How can I achieve this?
Thank you in advance for your kind cooperation. This is the procedural way I've thought:
-- Find the distinct CLOB values that can assume the column called CLOB_COLUMN (of type CLOB)
-- contained in the table called COPIA
-- Before the execution of the following PL/SQL script, the CLOB values (including duplicates)
-- are contained in the source table, called S1
-- At the end of the excecution of the PL/SQL script, the distinct values of the column called CLOB_COLUMN
-- can be find in the target table called S2
BEGIN
EXECUTE IMMEDIATE 'TRUNCATE TABLE S1 DROP STORAGE';
EXECUTE IMMEDIATE 'DROP TABLE S1 CASCADE CONSTRAINTS PURGE';
EXCEPTION
WHEN OTHERS
THEN
BEGIN
NULL;
END;
END;
BEGIN
EXECUTE IMMEDIATE 'TRUNCATE TABLE S2 DROP STORAGE';
EXECUTE IMMEDIATE 'DROP TABLE S2 CASCADE CONSTRAINTS PURGE';
EXCEPTION
WHEN OTHERS
THEN
BEGIN
NULL;
END;
END;
CREATE GLOBAL TEMPORARY TABLE S1
ON COMMIT PRESERVE ROWS
AS
SELECT CLOB_COLUMN FROM COPIA;
CREATE GLOBAL TEMPORARY TABLE S2
ON COMMIT PRESERVE ROWS
AS
SELECT *
FROM S1
WHERE 3 = 9;
BEGIN
DECLARE
CONTEGGIO NUMBER;
CURSOR C1
IS
SELECT CLOB_COLUMN FROM S1;
C1_REC C1%ROWTYPE;
BEGIN
FOR C1_REC IN C1
LOOP
-- How many records, in S2 table, are equal to c1_rec.clob_column?
SELECT COUNT (*)
INTO CONTEGGIO
FROM S2 BETA
WHERE DBMS_LOB.
COMPARE (BETA.CLOB_COLUMN,
C1_REC.CLOB_COLUMN) = 0;
-- If it does not exist, in S2, a record equal to c1_rec.clob_column,
-- insert c1_rec.clob_column in the table called S2
IF CONTEGGIO = 0
THEN
BEGIN
INSERT INTO S2
VALUES (C1_REC.CLOB_COLUMN);
COMMIT;
END;
END IF;
END LOOP;
END;
END;
If it is acceptable to truncate your field to 32767 characters this works:
select distinct dbms_lob.substr(FIELD_CLOB,32767) from Table1
You could compare the hashes of the CLOB to determine if they are different:
SELECT your_clob
FROM your_table
WHERE ROWID IN (SELECT MIN(ROWID)
FROM your_table
GROUP BY dbms_crypto.HASH(your_clob, dbms_crypto.HASH_SH1))
Edit:
The HASH function doesn't guarantee that there will be no collision. By design however, it is really unlikely that you will get any collision. Still, if the collision risk (<2^80?) is not acceptable, you could improve the query by comparing (with dbms_lob.compare) the subset of rows that have the same hashes.
add TO_CHAR after distinct keyword to convert CLOB to CHAR
SELECT DISTINCT TO_CHAR(CLOB_FIELD) from table1; //This will return distinct values in CLOB_FIELD
Use this approach. In table profile column content is NCLOB. I added the where clause to reduce the time it takes to run which is high,
with
r as (select rownum i, content from profile where package = 'intl'),
s as (select distinct (select min(i) from r where dbms_lob.compare(r.content, t.content) = 0) min_i from profile t where t.package = 'intl')
select (select content from r where r.i = s.min_i) content from s
;
It is not about to win any prizes for efficiency but should work.
select distinct DBMS_LOB.substr(column_name, 3000) from table_name;
If truncating the clob to the size of a varchar2 won't work, and you're worried about hash collisions, you can:
Add a row number to every row;
Use DBMS_lob.compare in a not exists subquery. Exclude duplicates (this means: compare = 0) with a higher rownum.
For example:
create table t (
c1 clob
);
insert into t values ( 'xxx' );
insert into t values ( 'xxx' );
insert into t values ( 'yyy' );
commit;
with rws as (
select row_number () over ( order by rowid ) rn,
t.*
from t
)
select c1 from rws r1
where not exists (
select * from rws r2
where dbms_lob.compare ( r1.c1, r2.c1 ) = 0
and r1.rn > r2.rn
);
C1
xxx
yyy
To bypass the oracle error, you have to do something like this :
SELECT CLOB_COLUMN FROM TABLE COPIA C1
WHERE C1.ID IN (SELECT DISTINCT C2.ID FROM COPIA C2 WHERE ....)
I know this is an old question but I believe I've figure out a better way to do what you are asking.
It is kind of like a cheat really...The idea behind it is that You can't do a DISTINCT of a Clob column but you can do a DISTINCT on a Listagg function of a Clob_Column...you just need to play with the partition clause of the Listagg function to make sure it will only return one value.
With that in mind...here is my solution.
SELECT DISTINCT listagg(clob_column,'| ') within GROUP (ORDER BY unique_id) over (PARTITION BY unique_id) clob_column
FROM copia;

Resources