Issue with the performance of the query - sql

I have two tables tab1 and tab2 each are having two columns acc_num and prod_code. I need to update prod_code in tab2 from tab1. Below is the sample data in both the tables:
TAB1
acnum Prod
-------------------
1 A
2 B
2 C
3 X
3 X
Tab2
acnum Prod
-------------------
1 null
2 null
2 null
3 null
3 null
And for the 2nd table after update all the distinct codes should be concatenated. Below is the sample output.
Tab2
acnum Prod
-------------------
1 A
2 B|C
2 B|C
3 X
3 X
I am able to achieve this through PL/SQL, but it's taking ages to complete. (Actual tables are having millions of records). Below is the code I am using.
DECLARE
l_acnum dbms_sql.varchar2a;
l_prod dbms_sql.varchar2a;
l_prod2 VARCHAR2(10):= NULL;
l_count NUMBER := 0;
CURSOR cr_acnum
IS
SELECT DISTINCT(acnum) FROM tab1;
CURSOR cr_prod(l_acnum_dum IN VARCHAR2)
IS
SELECT prod FROM tab1 WHERE acnum = l_acnum_dum;
BEGIN
OPEN cr_acnum;
FETCH cr_acnum bulk collect INTO l_acnum;
CLOSE cr_acnum;
FOR i IN l_acnum.first .. l_acnum.last
LOOP
OPEN cr_prod(l_acnum(i));
FETCH cr_prod bulk collect INTO l_prod;
CLOSE cr_prod;
FOR m IN l_prod.first .. l_prod.last
LOOP
IF m <> 1 THEN
IF l_prod(m) = l_prod(m-1) THEN
l_prod2 := l_prod(m);
ELSE
l_prod2 := l_prod2||'|'||l_prod(m);
END IF;
ELSE
l_prod2 := l_prod(m);
END IF;
END LOOP;
UPDATE tab2 SET prod = l_prod2 WHERE acnum = l_acnum(i);
END LOOP;
END;
This pl/sql block is taking ages to complete. Is there anyway I can achieve the same through query rather than PL/SQL or may be by efficient PL/SQL. I tried BULK COLLECT also but of no use. Data is in Oracle DB. Thanks a lot for your time.

This will concatenate those values as long as they don't exceed a certain total length. You may also want to do a subquery to dedupe them if there are any dupes.
Update: here is with LISTAGG
update table2 set Prod = (
SELECT LISTAGG(t1.Prod, ', ') WITHIN GROUP (ORDER BY t1.Prod) "Prod"
FROM Table1 t1
where t1.acnum = table2.acnum)

Thanks everyone for your input. I achived the same using your inputs. I used 2 step solution:
Step 1) Create a lookup table for product and account no.
create table lkup_tbl
as SELECT acnum, LISTAGG(Prod, '|') WITHIN GROUP (ORDER BY Prod) product
FROM (select distinct acnum, Prod from tab1) tab
GROUP BY acnum;
Step 2) Now update all the tables by joining this lookup table.
update tab2 t1
set (t1.Prod) = (select product from lkup_tbl t2
where t2.acnum = t1.acnum
);

Related

How to query a club column in Oracle [duplicate]

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;

Exact fetch returns more than requested number of rows while executing Stored Procedure

I am trying to write a stored procedure that will accept Item Number as input parameter and will return some data against it. I am not able to call the procedure as it keeps on giving error that number of rows are more than requested. I am new to Oracle and it not making sense that why it is working if I am returning 1 row only and not when the data is in multiple rows ?
I am pasting my code below to see if someone can point out where I am not going right.
CREATE OR REPLACE PROCEDURE AGILE.SELECT_ITEM
(
ITEM_NO IN VARCHAR2, ITEM_NUMBER OUT VARCHAR2, REV_NUMBER OUT VARCHAR2, CHANGE_NUMBER OUT VARCHAR2, SRC_FILENAME OUT VARCHAR2, DST_FILENAME OUT VARCHAR2,
FILEPATH OUT VARCHAR2, DESCRIPTION OUT VARCHAR2
)
IS
BEGIN
WITH CTE AS(
SELECT
RANK () OVER (PARTITION BY ITEM_NUMBER ORDER BY NVL(c.id, -1) DESC) RN,
CTE.ITEM_NUMBER, NVL(REV.REV_NUMBER,'Introductory') REV_NUMBER, NVL(C.CHANGE_NUMBER,'NOCHANGE') CHANGE_NUMBER,
CASE WHEN AM.FILE_ID = 0 THEN VM.FILE_ID ELSE AM.FILE_ID END AMVMFILE_ID, A.DESCRIPTION
FROM ITEM CTE
LEFT JOIN AGILE.REV REV ON REV.ITEM = CTE.ID
LEFT JOIN AGILE.CHANGE C ON C.ID = REV."CHANGE"
LEFT JOIN AGILE.ATTACHMENT_MAP AM ON AM.PARENT_ID = CTE.ID AND C.ID = AM.PARENT_ID2
LEFT OUTER JOIN AGILE.ATTACHMENT A ON AM.ATTACH_ID = A.ID AND AM.LATEST_VSN = a.LATEST_VSN
LEFT OUTER JOIN AGILE.VERSION_FILE_MAP VM ON (AM.VERSION_ID = VM.VERSION_ID OR AM.LATEST_VSN = VM.VERSION_ID)
LEFT OUTER JOIN AGILE."VERSION" V1 ON V1.id = VM.VERSION_ID
WHERE CTE.ITEM_NUMBER = 'AGY-731946-0000'
AND NVL(REV.EFFECTIVE_DATE, TO_DATE('2020-04-17', 'YYYY-MM-dd')) <= TO_DATE('2020-04-17', 'YYYY-MM-dd')
AND (C.SUBCLASS IS NULL OR C.SUBCLASS NOT IN
(
11141
,434284
,1455
,313304
,190161
,435727
,43556
,181524
,181518
,434124
,435796
,8141
,7141
,341469
,434038
,435834
,408376
))
), FINALCTE AS(
SELECT ITEM_NUMBER, REV_NUMBER, CHANGE_NUMBER,
'agile'||f.id||'.'||f.file_type src_filename,
f.filename dst_filename,
NVL(replace(fi.ifs_filepath,'\','/'),
SUBSTR(LPAD(to_char(f.id),11,'0'),0,3)||'/'||
SUBSTR(LPAD(to_char(f.id),11,'0'),4,3)||'/'||
SUBSTR(LPAD(to_char(f.id),11,'0'),7,3)||'/'||
'agile'||f.id||'.'||f.file_type) filepath, DESCRIPTION
FROM CTE DC
LEFT OUTER JOIN FILES F ON DC.AMVMFILE_ID = F.ID
LEFT OUTER JOIN FILE_INFO FI ON FI.FILE_ID = DC.AMVMFILE_ID
WHERE RN = 1 AND AMVMFILE_ID IS NOT NULL
ORDER BY ITEM_NUMBER, RN
)
SELECT DISTINCT
LTRIM(RTRIM(ITEM_NUMBER)) ITEM_NUMBER, LTRIM(RTRIM(REV_NUMBER)) REV_NUMBER, LTRIM(RTRIM(CHANGE_NUMBER)) CHANGE_NUMBER,
LTRIM(RTRIM(src_filename)) src_filename, LTRIM(RTRIM(dst_filename)) dst_filename, LTRIM(RTRIM(filepath)) filepath,
LTRIM(RTRIM(DESCRIPTION)) DESCRIPTION
INTO ITEM_NUMBER, REV_NUMBER, CHANGE_NUMBER, SRC_FILENAME, DST_FILENAME, FILEPATH, DESCRIPTION
FROM FINALCTE
--SELECT ITEM_NUMBER INTO ITEM_NUMBER FROM ITEM
WHERE ITEM_NUMBER = ITEM_NO;
END;
The query when run separately returns 2 rows of data but when I call this procedure it is not retunrning data but an error.
This is how I am calling my procedure.
CALL AGILE.SELECT_ITEM('AGY-731946-0000',?,?,?,?,?,?,?);
Please help me out as I am stuck badly.
Expected output.
ITEM_NUMBER |REV_NUMBER|CHANGE_NUMBER|SRC_FILENAME |DST_FILENAME |FILEPATH |DESCRIPTION |
---------------|----------|-------------|----------------|-----------------------------------------------------------------------------------------|----------------------------|-------------------------------|
AGY-731946-0000|A |DL000208 |agile1820829.pdf|Cert_BSMI_S-LK5.pdf |000/018/208/agile1820829.pdf|AGNCY CERTI BSMI 3892A547 S-LK5|
AGY-731946-0000|A |DL000208 |agile1820830.url|HTTP://AgileArchive.logitech.com/WWDocLib2/WWDL002.nsf/0/C125679800434FCCC12568F100392326|000/018/208/agile1820830.url|AGNCY CERTI BSMI 3892A547 S-LK5|
Error:
SQL Error [1422] [21000]: ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at "AGILE.SELECT_ITEM", line 9
Root cause it that your top level SQL query returns 2 rows.
For your code to work it must only return 1 row.
Here is short demo:
SQL> create table t(x int);
Table created.
SQL> insert into t values(1);
1 row created.
SQL> insert into t values(2);
1 row created.
SQL> commit;
Commit complete.
SQL> --
SQL> create or replace procedure myproc
2 is
3 l number;
4 begin
5 select x into l from t;
6 end;
7 /
Procedure created.
SQL> show errors
No errors.
SQL> exec myproc
BEGIN myproc; END;
*
ERROR at line 1:
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at "TESTDBA.MYPROC", line 5
ORA-06512: at line 1
SQL> --
SQL> delete t where x=2;
1 row deleted.
SQL> select * from t;
X
----------
1
SQL> exec myproc;
PL/SQL procedure successfully completed.
SQL>
I am new to Oracle and it not making sense that why it is working if I am returning 1 row only and not when the data is in multiple rows ? The query when run separately returns 2 rows of data but when I call this procedure it is not retunrning data but an error.
You cannot assign multiple values to a scalar variable. The SELECT..INTO clause that you are using to assign multiple rows will error out. You could do it in following ways:
Use a CURSOR FOR LOOP. But it would be slow as it is row-by-row operation.
Use record/collection, see documentation for more details https://docs.oracle.com/en/database/oracle/oracle-database/19/lnpls/plsql-collections-and-records.html#GUID-7115C8B6-62F9-496D-BEC3-F7441DFE148A

Break down an oracle script into smaller scripts

I am trying to run a script which changes 200,000 records. But I want to change each 10000 record separately, so the first 10000 then the second 10000 and so on.
FOR d IN ids -- ids is a cursor which is filled by 200,000 record
LOOP
UPDATE doctor
SET name =
(SELECT name
FROM source
WHERE id = d.id
AND type =
(SELECT MAX(type)
FROM Da
WHERE i = id
)
)
WHERE pat = d.pat;
COMMIT;
END LOOP;
What are you implying by saying change 10000 records separately. Do you mean you want to commit in blocks of 10000s?
You can do that using a counter
record_count := 0
FOR d IN ids -- ids is a cursor which is filled by 200,000 record
LOOP
UPDATE doctor
SET name =
(SELECT name
FROM source
WHERE id = d.id
AND type =
(SELECT MAX(type)
FROM Da
WHERE i = id
)
)
WHERE pat = d.pat;
record_count := record_count +1;
IF mod(record_count,10000) = 0
THEN
COMMIT;
END IF;
END LOOP;
COMMIT; -- COMMIT for the last time if you have residual records

Assigning a unique id to the first instance of a string in PL/SQL

I have a set of data that has multiple rows with the same unique_string_identifier. I want to assign a new unique ID from a sequence to the first instance of a row with that unique_string_identifier then give any following rows with the same unique_string_identifier the ID times -1. I've tried it three different ways, but I always get
ORA-30483: window functions are not allowed here
Here are my attempts:
UPDATE my_table
set my_id =
CASE WHEN LAG(unique_string_identifier, 1, '-') OVER (order by unique_string_identifier) <> unique_string_identifier THEN my_id_seq.nextval
ELSE LAG(-1 * my_id, 1, '-') OVER (order by unique_string_identifier) END CASE
where import_run_id = a_run_id;
I've also tried this:
UPDATE my_table
set my_id = my_id_seq.nextval
where row_number() over (partition by unique_string_identifier order by line_id) = 1;
//another update statement to make the rows with null ID's equal to the negative id joined on unique_string_identifier
And this:
UPDATE my_Table
set my_id =
decode(unique_string_identifier, LAG(unique_string_identifier, 1, '-') OVER (order by unique_string_identifier), LAG( my_id, 1, '-') OVER (order by unique_string_identifier), my_id_seq.nextval)
where import_run_id = a_run_id;
How can I make this work?
EDIT: Also for my own enrichment, if anyone can explain why these 3 statements (which all seem pretty different to me) end up getting the exact same ORA error, I'd appreciate it.
I couldn't work out a simple MERGE or set of UPDATEs, but here is a potential solution that might work fine, tested on Oracle 11g, using PL/SQL:
Test scenario:
create table my_table (unique_string varchar2(100));
insert into my_table values ('aaa');
insert into my_table values ('aaa');
insert into my_table values ('aaa');
insert into my_table values ('bbb');
insert into my_table values ('bbb');
insert into my_table values ('ccc');
alter table my_table add (id number);
create sequence my_seq;
Here's the PL/SQL to do the update:
declare
cursor c is
select unique_string
,row_number()
over (partition by unique_string order by 1)
as rn
from my_table
order by unique_string
for update of id;
r c%rowtype;
begin
open c;
loop
fetch c into r;
exit when c%notfound;
if r.rn = 1 then
update my_table
set id = my_seq.nextval
where current of c;
else
update my_table
set id = my_seq.currval * -1
where current of c;
end if;
end loop;
close c;
end;
/
Results from my test (note that the sequence had advanced a little by this stage):
select * from my_table;
UNIQUE_STRING ID
============= ==
aaa 7
aaa -7
aaa -7
bbb 8
bbb -8
ccc 9
P.S. I've been a bit sneaky and taken advantage of Oracle's tendency to return ROW_NUMBER in the order that the rows are returned; to be more robust and correct, I'd put the query in a subquery and ORDER BY unique_string, rn.

Dynamic Cross Tab Query in Oracle

i need to create a dynamic cross tab query where columns will not always be fixed in number so cant hard code using case when. i ve googled, it did find a blog about doing the same in SQL Server but i was wondering if there is any such article blog on doing the same in Oracle. Have not worked in SQL Server. Fol is the info about my problem.
the hard coded cross tab query i wrote
SELECT
LU_CITY.CITY_NAME as "City",
count(CASE WHEN emp.emp_category='Admin' THEN emp.emp_id END) As "Admins",
count(CASE WHEN emp.emp_category='Executive' THEN emp.emp_id END) As "Executive",
count(CASE WHEN emp.emp_category='Staff' THEN emp.emp_id END) As "Staff",
count(emp.emp_id) As "Total"
FROM emp, LU_CITY
where
LU_CITY.CITY_ID = EMP.CITY_ID(+)
group by
LU_CITY.CITY_NAME, LU_CITY.CITY_ID
order by
LU_CITY.CITY_ID
tables
emp (emp_id, emp_name, city_id, emp_category)
lu_city(city_id,city_name)
query result
------------------------------------------
City | Admins | Executive | Staff . . . .
------------------------------------------
A | 1 | 2 | 3
B | 0 | 0 | 4
. | . | . | .
.
.
The emp_category can be added by the user as per their need. the query should be such that it should generate all such categories dynamically.
Any guidance in this regard would be highly appreciated.
Thanks in Advance
You can use dynamic cursors to execute dynamic SQL compiled from a VARCHAR2 variable:
DECLARE
w_sql VARCHAR2 (4000);
cursor_ INTEGER;
v_f1 NUMBER (6);
v_f2 NUMBER (2);
v_some_value_2_filter_4 NUMBER (2);
rc INTEGER DEFAULT 0;
BEGIN
-- join as many tables as you need and construct your where clause
w_sql :='SELECT f1, f2 from TABLE1 t1, TABLE2 t2, ... WHERE t1.f1 =' || v_some_value_2_filter_4 ;
-- Open your cursor
cursor_ := DBMS_SQL.open_cursor;
DBMS_SQL.parse (cursor_, w_sql, 1);
DBMS_SQL.define_column (cursor_, 1, v_f1);
DBMS_SQL.define_column (cursor_, 2, v_f2);
-- execute your SQL
rc := DBMS_SQL.EXECUTE (cursor_);
WHILE DBMS_SQL.fetch_rows (cursor_) > 0
LOOP
-- get values from record columns
DBMS_SQL.COLUMN_VALUE (cursor_, 1, v_f1);
DBMS_SQL.COLUMN_VALUE (cursor_, 2, v_f2);
-- do what you need with v_f1 and v_f2 variables
END LOOP;
END;
Or you can use execute immediate, easier to implement if you just need to check a value or execute and insert/update/delete query
w_sql :='select f1 from table where f1 = :variable';
execute immediate w_sql into v_f1 using 'valor1'
Here more info about dynamic cursors:
http://docs.oracle.com/cd/B10500_01/appdev.920/a96590/adg09dyn.htm