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

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

Related

add values from other table using cursor in 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.

Select the C1 values having 0th line(C2) has value in C3 and all other lines(C2) having value as NULL - Oracle

CREATE TABLE TABLE1
(
C1 VARCHAR2(3), C2 VARCHAR2(3), C3 VARCHAR2(4)
);
INSERT INTO TABLE1 VALUES('A', '0', '1234');
INSERT INTO TABLE1 VALUES('A', '1', '4568');
INSERT INTO TABLE1 VALUES('A', '2', '5432');
INSERT INTO TABLE1 VALUES('B', '0', '3562');
INSERT INTO TABLE1 VALUES('B', '1', Null);
INSERT INTO TABLE1 VALUES('B', '2', Null);
INSERT INTO TABLE1 VALUES('C', '0', '2132');
INSERT INTO TABLE1 VALUES('C', '1', Null);
INSERT INTO TABLE1 VALUES('C', '2', '5431');
When you execute above query, we get the data into TABLE1 and each unique value of C1 column corresponds to 3 lines i.e. 0,1,2 (in C2 column). What is the query to select the data of column1 having its first line i.e. 0 in column2 has a value(which is not null) in C3 and all other lines of C2 have a value as Null in C3.
The answer for above example is
C1 C2 C3
------------------
B 0 3562
B 1 Null
B 2 Null
There are various rows. For each unique value of C1 can have multiple lines i.e. 0 to 100 etc. in C2 but I have taken above one as an example. In above you can see that A has values in all the 3 lines. B has value in 0th line but as Null in all other lines. C has values in 0th line and 2nd line but Null in 1st line. We need to select the unique value of C1 having value in 0th line and Null in other lines
Perhaps something like this:
select *
from table1
where c1 in (
select c1
from table1
group by c1
having count(case when c2 = 0 then c3 end) = 1
and count(c3) = 1
)
order by c1, c2
;
C1 C2 C3
--- --- ----
B 0 3562
B 1
B 2
This reads the base data twice. If you use analytic functions instead, you can have the base data read just once, but analytic functions themselves are slower than traditional aggregation. If this query works for you, but it is slow, you can try the analytic functions approach just to make sure, but I expect it will be slower, not faster.
Is column c2 supposed to be numeric? I treated it as such, but in your sample data you gave it as strings.

Oracle - Grouping Sets and Pipelined Table Functions (expected NUMBER got ROW)

I'm working on a report with summary logic using GROUPING SETS, but I'm getting this error:
SELECT c1, c2, c3, SUM(c4) AS MySum
FROM TABLE(get_data()) src
GROUP BY GROUPING SETS ((c1, c2, c3), (c1, c2), c1, c2, ());
-------------------------
ORA-00932: inconsistent datatypes: expected NUMBER got XXX.MYROW
00932. 00000 - "inconsistent datatypes: expected %s got %s"
It works fine when I only include c1 or c2 separately:
GROUP BY GROUPING SETS ((c1, c2, c3), (c1, c2), c1, ());
GROUP BY GROUPING SETS ((c1, c2, c3), (c1, c2), c2, ());
It also works fine when I source the query directly from the t1 table:
SELECT c1, c2, c3, SUM(c4) AS MySum
FROM t1 src
GROUP BY GROUPING SETS ((c1, c2, c3), (c1, c2), c1, c2, ());
What am I missing? I feel like it's something simple. Here's a simplified example of my setup:
-- Base table
CREATE TABLE t1 (c1 VARCHAR(10), c2 VARCHAR(10), c3 VARCHAR(10), c4 INTEGER);
-- Row type
CREATE TYPE myrow AS OBJECT (c1 VARCHAR(10), c2 VARCHAR(10), c3 VARCHAR(10), c4 INTEGER);
-- Table type
CREATE OR REPLACE TYPE mytable AS TABLE OF myrow;
-- Get data function
CREATE OR REPLACE FUNCTION get_mydata
RETURN mytable PIPELINED AS
BEGIN
FOR v_rec IN (
SELECT c1, c2, c3, c4
FROM t1
) LOOP
PIPE ROW (myrow(v_Rec.c1, v_Rec.c2, v_Rec.c3, v_Rec.c4));
END LOOP;
RETURN;
END;
DB version - 12.1.0
Update
Different error I get with my actual function (even with "materialize" hint):
ORA-22905: cannot access rows from a non-nested table item 22905.
00000 - "cannot access rows from a non-nested table item"
*Cause: attempt to access rows of an item whose type is not known
at parse time or that is not of a nested table type
*Action: use CAST to cast the item to a nested table type
I don't know why it won't work, but - see if this workaround helps (use a CTE and the materialize hint):
SQL> with test as
2 (select /*+ materialize */
3 c1, c2, c3, c4
4 from table(get_mydata()) src
5 )
6 select c1, c2, c3, sum(c4) as mysum
7 from test
8 group by grouping sets ((c1, c2, c3), (c1, c2), c1, c2, ());
C1 C2 C3 MYSUM
---------- ---------- ---------- ----------
1 2 3 4
1 4
2 4
4
1 2 4
SQL>
You can workaround the bug by using a non-pipelined function and BULK COLLECT:
CREATE OR REPLACE FUNCTION get_mydata2
RETURN mytable AS
v_data mytable;
BEGIN
SELECT myrow(c1, c2, c3, c4)
BULK COLLECT
INTO v_data
FROM t1;
RETURN v_data;
END;
/
I was able to reproduce your error in version 19c so it looks like you found a relatively big Oracle bug. In my experience, pipelined functions are generally buggier than regular functions and tend to be over-used. Pipelined functions can return data faster, but since your query is aggregating all the rows, you won't see that performance boost anyway.
The main problem with the non-pipelined function approach is that your session will need enough memory to store all of the results from the BULK COLLECT at once. That may not be feasible if you have to process millions of wide rows.

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

insert and select in one query default values

I use following query to insert a new row:
insert into table1 (c1, c2, c3) (select c1, c2, c3 from table2 where some_condition)
This works finely if there is a row in table2 that satisfies some_condition. But if there are no rows, nothing is inserted.
Are there any way to specify default values to insert if select returns empty result set? I want to do that in one sql query.
This isn't very pretty, but it does what you want, You'd need to test with your environment to see if it performs well enough
SQL> drop table so_tgt;
Table dropped.
SQL>
SQL> create table so_src (
2 c1 varchar2(6)
3 ,c2 varchar2(6)
4 ,c3 varchar2(6)
5 );
Table created.
SQL>
SQL> insert into so_src values ( 'foo','bar','moo' );
1 row created.
SQL>
SQL> create table so_tgt as select * from so_src where 1 = 0;
Table created.
SQL>
SQL> /* Test for existing row insert */
SQL> insert into so_tgt
2 with x as ( select s.*, 1 as r
3 from so_src s
4 where c1='foo'
5 union
6 select 'x','y','z',0 as r /* DEFAULT VALUES */
7 from dual )
8 select c1,c2,c3
9 from x
10 where r = ( select max(r) from x ) ;
1 row created.
SQL>
SQL> select * from so_tgt;
C1 C2 C3
------ ------ ------
foo bar moo
SQL> truncate table so_tgt;
Table truncated.
SQL>
SQL> /* Test for default row insert */
SQL> insert into so_tgt
2 with x as ( select s.*, 1 as r
3 from so_src s
4 where c1='far'
5 union
6 select 'x','y','z',0 as r /* DEFAULT VALUES */
7 from dual )
8 select c1,c2,c3
9 from x
10 where r = ( select max(r) from x ) ;
1 row created.
SQL>
SQL> select * from so_tgt;
C1 C2 C3
------ ------ ------
x y z
SQL> truncate table so_tgt ;
Table truncated.
The quick and dirty way, if you don't mind repeating some_condition and where some_condition doesn't depend on the values in table2 is:
insert into table1 (c1,c2,c3)
select c1, c2, c3 from table2 where some_condition
union select defaultvalue1, defaultvalue2, defaultvalue3 from dual where not (some_condition)
If some_condition does depend on values in table2, then you can do (untested):
insert into table1 (c1,c2,c3)
select nvl(t2.c1, defaultvalue1), nvl(t2.c2, defaultvalue2), nvl(t2.c2, defaultvalue3)
from dual left join (select c1,c2,c3 from table2 where some_condition) t2
on 1 = 1
If I'm right, this query will always return at least one row, but if no rows showed up on the right side, then the t2 values will all be returned as null and so the nvl can be used to provide your default values.
Edit: Small caveat. This assumes that the values returned from table2 will not be null or that if they are, you want the default values.

Resources