add values from other table using cursor in oracle - oracle

I am trying to execute the following steps:
Let's say we have a table t1
CREATE TABLE t1 (i1 varchar2,d1 varchar2,l1 varchar2, h1 number(1),q1 number, s1 date );
insert into t1 values('123','1','123-1',0,0,sysdate);
insert into t1 values('123','1','234-1',0,0,sysdate+2);
insert into t1 values('456','2','345-1',0,0,sysdate);
insert into t1 values('456','2','456-1',0,0,sysdate+2);
CREATE TABLE t2 (i2 varchar2,d2 varchar2,q2 number,a2 date );
insert into t2 values('123','1','1230',sysdate);
insert into t2 values('123','1','2340',sysdate+1);
insert into t2 values('456','2','3450',sysdate);
insert into t2 values('456','2','4560',sysdate+1);
Now I have to order t1 data in ascending order of s1 for same i1 and d1 and then check with a2 column of t2. If a2>=s1 but a2< next greater value of s1 then add q2 and assign it to q1.
I am trying to write a cursor which will take values from t1 in ascending order of s1,
for t2.a2>=s1 and t2.a2<s2 --s2 is next greater date value of t1.s1 column
q1 = q1+q2;
I am unable to find an efficient way to do this.
my cursor is:
cursor c1 is (select * from(select i1,d1,s1,min(s1) from t1 where h1=0
group by i1,d1,s1 order by i1,d1,s1));
declare
v_s
v_i
v_d
v_s1
v1 date;
v_q number;
begin
open c1;
fetch c1 into v_i,v_d,v_s,v1;
loop
select s1 into v_s1
from t1 t
where t.i1 = v_i and t.d1=v_d and t.s1>v_s;
select sum(t2.q2) into v_q
from t2 t
where t2.i1=v_i and t2.d2=v_d and t2.a2>=v_s and t2.a2<v_s1;
update t1 set q1 = v_q;
end loop;
close c1;
end;
I want q1 to be populated with sum(q2) for all a2 dates if a2 falls between min and max(s1).
I am unable to execute this. I either keep getting no data found or too many rows fetched or some other error. How can I do this efficiently?

Since you have not shared the end result, I think you want rows 2 & 4 to be null for column Q1. You can try the below co-related sub query to achieve the desired result -
UPDATE t1
SET Q1 = (SELECT SUM(q2)
FROM (SELECT t2.*, a2 - lag(a2) over(partition by i2, d2 order by a2) date_diff
FROM t2) t2
WHERE t1.i1 = t2.i2
AND t1.d1 = t2.d2
AND t1.s1 <= t2.a2
AND NVL(date_diff, 1) = 1);
Demo.

Related

How to update a table with a function using the same table but which uses only columns not modified by this update

CREATE TABLE A (
A1 integer,
A2 integer,
A3 integer
);
create function f(a1_ in integer) return integer
is
ret integer;
begin
select a3+1 into ret from A where a1=a1_;
return ret;
end;
INSERT INTO A SELECT 1 a1,1 a2,1 a3 FROM dual union all select 2 a1, 2 a2, 2 a3 from dual
update A t1
set a3 =
(select f(a1) from A t2 where t1.a1=t2.a1);
A is mutating, trigger/function may not see it
This code doesn't work. contrary to
update A t1
set a3=
(select f(a1) from A t2 where t1.a1=t2.a1);
The problem is the following. I'm using a function using the same table to update the table.
But the columns that I'm using in these table are not modified by f.
I'm using only a1 to return the next value of a3.
And a1 isn't modified in this statement
Is there a way to do these operation. (perhaps using a keyword or something else)
code
You can use PL/SQL:
DECLARE
v_a1s SYS.ODCINUMBERLIST;
v_fs SYS.ODCINUMBERLIST;
BEGIN
SELECT a1, f(a1)
BULK COLLECT INTO v_a1s, v_fs
FROM A
FOR UPDATE OF a3;
FORALL i IN 1 .. v_a1s.COUNT
UPDATE A
SET a3 = v_fs(i)
WHERE a1 = v_a1s(i);
END;
/
db<>fiddle here

oracle, find duplicate in one column based on another

create table stest1(x number,y number);
insert into stest1 values(1,10);
insert into stest1 values(1,11);
insert into stest1 values(1,10);
insert into stest1 values(2,9);
insert into stest1 values(2,9);
insert into stest1 values(1,9);
commit;
I would like to know all the values in column x that have more than one values of y
so from above, 1 has more than one value stored in y (10,11 and 9) where as 2 has only one value which is 9, so the result of my query should give 1
how to write such a query ?
If all you are after is the x, we can self join like this:
select distinct s1.x
from stest1 s1
inner join stest1 s2
on s1.x = s2.x
and s1.y <> s2.y
EDIT:
To get both x and y, you could move the above into a CTE and then only select the existing x:
with dupes as
(
select distinct s1.x
from stest1 s1
inner join stest1 s2
on s1.x = s2.x
and s1.y <> s2.y
)
select stest1.*
from stest1
where stest1.x in (select x from dupes)

PL SQL fuzzy search with clause

I am working on call center customer search query. If user enter last name and driving license I have to find out the most close matching customer(s) from database. I am using fuzzy search, soundex() to compare customer last name. Base on different conditions I have to keep using soundex function, it is effecting performance. I try to use With Clause but since I have many IF and ELSE parts it is giving table or view not found exception.
I tried to google to find how to use with the clause and with clause with function, none of them are helpful.
PROCEDURE RetrieveCustomer
(
i_lastname IN varchar,
i_DL IN varchar,
o_cursor OUT T_CURSOR
)
IS
v_DLAndExactLastNameCnt Number;
v_DLAndLastNameCnt Number;
v_DLCnt Number;
BEGIN
SELECT COUNT(*) INTO v_DLAndExactLastNameCnt
FROM t1, t2
WHERE t1.DL = i_DL AND (upper(t2.last_name) like upper(i_lastname));
IF(v_DLAndExactLastNameCnt > 0) THEN
OPEN o_cursor FOR
select
a,
b,
'DL + ELN' as search_result_baseon --exact last name match
from t1, t2, t3, t4
where t1.DL = i_DL AND (upper(t2.last_name) like upper(i_lastname));
ELSE
SELECT COUNT(*) INTO v_DLAndLastNameCnt
FROM t1, t2
WHERE t1.DL= i_DL
AND p.last_name in (select last_name from t2 where soundex(last_name) = soundex(i_lastname));
IF(v_DLAndLastNameCnt > 0) THEN
OPEN o_cursor FOR
select
a,
b,
'DL + LN' as search_result_baseon -- last name not exact match
from t1, t2, t3, t4
where
t1.DL= i_DL
AND p.last_name in (
select last_name from t2 where soundex(last_name) = soundex(i_lastname)
);
ELSE
SELECT COUNT(*) INTO v_DLCnt
FROM t1
WHERE t1.DL = i_DL;
IF(v_DLCnt > 0) THEN
OPEN o_cursor FOR
select a
b,
'DL' as search_result_baseon
from t1, t2, t3, t4
where t1.DL = i_DL;
ELSE
OPEN o_cursor FOR
select
a,
b,
'LN' as search_result_baseon
from t1, t2, t3, t4
where t2.last_name IN (
select last_name from t2 where soundex(last_name) = soundex(i_lastname)
)
END IF;
END IF;
END IF;
END RetrieveCustomer;
Is there any way I can store matching last name record in some in-memory table and use it as and when require in that procedure? Can anyone of you please suggest me better way to write my procedure:

How to Update value in one table by coping it from another table in plsql ?

This is a simple example of what I need to do. In fact I want to Update value in one table by coping it from another table by using cursors in plsql.
I take table f and table b as two examples:
f=
1|Thom
2|Bob
3|Steven
5|Arthur
b=
7|Nataly
9|Alfred
, where I need to insert the b 's tow lines in first f two lines:
create table f (a number, b varchar2(10));
insert into f values (1,'Thom');
insert into f values (2,'Bob');
insert into f values (3,'Steven');
insert into f values (5,'Arthur');
commit;
create table b (c number, d varchar2(10));
insert into b values (7,'Nataly');
insert into b values (9,'Alfred');
commit;
create or replace procedure wco as
cursor c_f is
select a,b from f for update;
v_a f.a%type;
v_b f.b%type;
cursor c_b is
select c,d from b;
v_c b.c%type;
v_d b.d%type;
begin
open c_f;
open c_b
loop
fetch c_f into v_a, v_b;
exit when c_f%ROWCOUNT=c_b%RROWCOUNT;
update f set a=v_c and b=v_d where current of c_f;
end loop;
close c_d:
close c_f;
end;
/
exec wco;
select * from f;
drop table f;
The expected result (What i hope to have):
7|Nataly
9|Alfred
3|Steven
5|Arthur
But what I have now (as a result)is:
1|Thom
2|Bob
3|Steven
5|Arthur
How do I resolve this problem, I am a beginner with PLSQL, I would be very grateful if you could help me please.
Your procedure compiles, but with errors;
In order to see compilation errors
run show errors; after compilation.
Change open c_b to open c_b; (missing semicolon at the end)
Change close c_d: to close c_b; (correct name is c_b and semicolon instead of colon)
Change exit when c_f%ROWCOUNT=c_b%RROWCOUNT; to exit when c_f%ROWCOUNT=c_b%ROWCOUNT; (wrong %RROWCOUNT;)
update f set a=v_c and b=v_d where current of c_f; This is wrong SQL syntax.
Should be update table_name set column=value, column=value;
It is important to provide descriptive names to your variables, so it would be easier to understand your logic.
As I understood you want to copy only second two rows from source table.
create table source_table (
id number,
name varchar2(10));
/
insert into source_table values (1,'Thom');
insert into source_table values (2,'Bob');
insert into source_table values (3,'Steven');
insert into source_table values (5,'Arthur');
/
create table target_table (
id number,
name varchar2(10));
/
insert into target_table values (7,'Nataly');
insert into target_table values (9,'Alfred');
/
create or replace procedure copy_tables
AS
begin
FOR source_row IN (select id, name
from source_table
offset 2 rows)
LOOP
insert into target_table
values(source_row.id, source_row.name);
END LOOP;
end;
/
exec copy_tables;
/
select id, name from target_table;
/
You don't need PL/SQL for this; it can be achieved in a single MERGE statement:
merge into f tgt
using (select coalesce(b1.id, f1.id) id,
coalesce(b1.name, f1.name) name,
f1.f_rowid
from (select id,
name,
rowid f_rowid,
row_number() over (order by id) rn
from f) f1
full outer join (select id,
name,
row_number() over (order by id) rn
from b) b1
on f1.rn = b1.rn) src
on (tgt.rowid = src.f_rowid)
when matched then
update set tgt.id = src.id,
tgt.name = src.name
where tgt.id != src.id
or tgt.name != src.name
when not matched then
insert (tgt.id, tgt.name)
values (src.id, src.name);
See https://livesql.oracle.com/apex/livesql/file/content_FBPR7YCLFVWO7NGDXTLSP1R97.html for the test details (two examples; the target table has more and has fewer rows than the source table).
If you need to, you could add the above insert into a procedure, e.g.:
create procedure populate_target_table as
begin
merge into f tgt
using (select coalesce(b1.id, f1.id) id,
coalesce(b1.name, f1.name) name,
f1.f_rowid
from (select id,
name,
rowid f_rowid,
row_number() over (order by id) rn
from f) f1
full outer join (select id,
name,
row_number() over (order by id) rn
from b) b1
on f1.rn = b1.rn) src
on (tgt.rowid = src.f_rowid)
when matched then
update set tgt.id = src.id,
tgt.name = src.name
where tgt.id != src.id
or tgt.name != src.name
when not matched then
insert (tgt.id, tgt.name)
values (src.id, src.name);
end;
/

plsql: inserting Multiple rows from select and ignore duplicates

I am using oracle 10g with TOAD.
I need to insert lacs of records using INSERT FROM SELECT.
BEGIN
INSERT INTO MYTABLE(C1,C2,C3)
SELECT C1,C2,C3 FROM MYTABLE2 WHERE C1>100;
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN NULL;
COMMIT;
END;
Here, the problem , i am facing is , if this select queries return rows which is already exists in MYTABLE, THEN all transaction will be rolledback.
Is there a way to insert all non-existent rows ,ignoring duplicate rows and continuing with insertion of non-existent rows and then committing the transaction?
Use the Distinct Keyword
BEGIN
INSERT INTO MYTABLE(C1,C2,C3)
SELECT distinct C1,C2,C3 FROM MYTABLE2 WHERE C1>100;
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN NULL;
COMMIT;
END;
Instead of trying to handle the exception, you can avoid these rows in the first place, e.g., by using the minus operator:
INSERT INTO mytable (c1, c2, c3)
SELECT c1, c2, c3
FROM mytable2
WHERE c1 > 100;
MINUS
SELECT c1, c2, c3
FROM mytable
WHERE c1 > 100 -- not necessary, but should improve performance
There are many ways to do it. First of all, you can try something like this:
insert into mytable(c1, c2, c3)
select distinct c1, c2, c3 from mytable2 where c1 > 100
minus
select c1, c2, c3 from mytable;
Otherwise, you can use something like
insert into mytable(c1, c2, c3)
select c1, c2, c3 from mytable2 where c1 > 100
log errors into myerrtable reject limit unlimited;
And so on...
More detailed about error logging. Feauture introduced since 10g release 2.
SQL> create table garbage(id integer);
Table created
SQL> insert into garbage select rownum from dual connect by level <= 10;
10 rows inserted
SQL> insert into garbage values (3);
1 row inserted
SQL> insert into garbage values (5);
1 row inserted
SQL> create table uniq(id integer not null primary key);
Table created
SQL> insert into uniq select * from garbage;
ORA-00001: unique constraint (TEST.SYS_C0010568) violated
SQL> select count(*) from uniq;
COUNT(*)
----------
0
SQL> exec dbms_errlog.create_error_log('uniq', 'uniq_err');
PL/SQL procedure successfully completed
SQL> insert into uniq select * from garbage
2 log errors into uniq_err reject limit unlimited;
10 rows inserted
SQL> select * from uniq;
ID
---------------------------------------
1
2
3
4
5
6
7
8
9
10
10 rows selected
SQL> select ora_err_mesg$, id from uniq_err;
ORA_ERR_MESG$ ID
---------------------------------------------------------------------- --
ORA-00001: unique constraint (TEST.SYS_C0010568) violated 3
ORA-00001: unique constraint (TEST.SYS_C0010568) violated 5
Thought I would make this an answer, but it really depends on what your trying to achieve.
You can check to see if the data is already in table2 using:
INSERT INTO mytable2 (c1, c2, c3)
SELECT DISTINCT c1,c2,c3 FROM mytable t1
WHERE c1 > 100
AND NOT EXISTS
(select 1 from mytable2 t2 where t2.c1 = t1.c1 and t2.c2 = t1.c2 and t2.c3 = t1.c3);
or you can use a merge like this:
MERGE INTO mytable2 m2
USING (SELECT DISTINCT c1, c2, c3 FROM mytable) m1
ON (m1.c1 = m2.c1 and m1.c2 = m2.c2 and m1.c3 = m2.c3)
WHEN NOT MATCHED THEN INSERT (c1, c2, c3) VALUES (m1.c1, m1.c2, m1.c3)
where m1.c1 > 100;
In both examples, you will only insert unique rows into mytable2

Resources