Can I update the result of query easily?
Assume I have big query which returns salary column and I need update salaries based on this query results.
ID- is primary key for my table
Now I,m doing it like this:
STEP 1
select id from mytable ...... where something
STEP 2
update mytable set salary=1000 where id in (select id from mytable ...... where something)
Is there exists alternative to do that easily?
Try for update and current of. You said that you are looking for something like "updating data on grid"
create table my_table( id number, a varchar2(10), b varchar2(10));
insert into my_table select level, 'a', 'b' from dual connect by level <=10;
select * from my_table;
declare
rec my_table%rowtype;
cursor c_cursor is select * from my_table for update;
begin
open c_cursor;
loop
fetch c_cursor into rec;
exit when c_cursor%notfound;
if rec.id in (1,3,5) then
rec.a := rec.a||'x';
rec.b := rec.b||'+';
update my_table set row = rec where current of c_cursor;
else
delete from my_table where current of c_cursor;
end if;
end loop;
commit;
end;
select * from my_table;
Yes , you can directly update the result easily.
Here is example :
update
(
select salary from mytable ...... where something
) set salary=1000
Related
I need to create an update using sql dynamic and all the updated rows have to be sent in a log table.
In microsoft, i can use OUTPUT clause and it inserts the updated rows in a table, but how can i do this in db2, using sql dynamic?
I have the following tables:
AllCustomers - contains all customers from a db
Id
Name
1
John
2
Test
gdpr_id. - contains all customers which should be updated
Id
Name
1
John
gdpr_log - should contain the output of the update stmt
Id
Name
1
John
I found the below syntax , but it just displays the results.
SELECT fields FROM FINAL TABLE
(update table set field = 'value' where id ='xyz')
I tried to create another dynamic stmt as
INSERT INTO
SELECT fields FROM FINAL TABLE
(update table set field = 'value' where id ='xyz')
and the syntax is not recognized.
How can i replace it to insert all the updated values in a log table?
I have to use sql dynamic because the tables which need to be updated are stored in a metadata table and with a cursor, i create the update script for each line from the metadata table.
UPDATE:
Metadata table looks like this:
table
column
AllCustom
Name
AllCustom
Lastname
CREATE OR REPLACE PROCEDURE sp_test ()
DYNAMIC RESULT SETS 1
P1: BEGIN
--*****************VARIABLES *****************
DECLARE EOF INT DEFAULT 0;
declare v_table nvarchar(50);
declare v_column nvarchar(50);
declare v_rowid nvarchar(50);
declare v_stmt nvarchar(8000);
declare s1 statement;
--*****************UPDATE STEP *****************
-- Declare cursor
DECLARE cursor1 CURSOR WITH HOLD WITH RETURN FOR
SELECT table,column FROM metadata_tbl;
declare c1 cursor for s1;
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET EOF = 1;
OPEN cursor1;
WHILE EOF = 0 DO
FETCH FROM cursor1 INTO v_table,v_column;
SET v_stmt = 'WITH A AS
(
SELECT name
FROM FINAL TABLE
(
UPDATE ' || v_table || ' set ' || v_column || ' = ''some name'' where id in (select ID from gdpr_id )
)
)
SELECT COUNT (1) as tst
FROM FINAL TABLE
(
INSERT INTO GDPR_LOG (table,name, LOGDATE)
SELECT ''' || v_table || ''', name, current_timestamp from A
) B';
PREPARE s1 FROM v_stmt ;
open c1 using v_table,v_column;
close c1;
END WHILE;
CLOSE cursor1;
END P1
Update step works fine, insert step duplicates the rows inserted.
What should I do to have the insert step ok?
You have to use SELECT as an outermost statement and keep inner SELECTs in distinct CTEs, if you have a number of them.
Try this:
WITH A AS
(
SELECT ID, NAME
FROM FINAL TABLE
(
UPDATE GDPR
SET NAME = 'Some name'
WHERE ID = 1
)
)
SELECT COUNT (1)
FROM FINAL TABLE
(
INSERT INTO GDPR_LOG (ID, NAME)
SELECT * FROM A
) B
Update:
Using dynamic SQL.
You must enclose the whole statement with some statement termination character (say, #) different from the default one (;) if you use some tool to run this compound statement and specify this statement terminator correctly there.
BEGIN
DECLARE C1 CURSOR FOR S1;
PREPARE S1 FROM
'
WITH A AS
(
SELECT ID, NAME
FROM FINAL TABLE
(
UPDATE GDPR
SET NAME = ?
WHERE ID = ?
)
)
SELECT COUNT (1)
FROM FINAL TABLE
(
INSERT INTO GDPR_LOG (ID, NAME)
SELECT * FROM A
) B
';
OPEN C1 USING 'Name', 1;
CLOSE C1;
END
Our IT team loads couple of tables every month. The new load should have more records than the previous load, with at least 2% more records.
It's a truncate and load process, I'm collecting the num of records from each table before the truncate, and I'm checking the difference in excel every month to make sure the data load is correct.
Is there anyway to automate this in Oracle.
eg:
Table_name Before_cnt After_cnt
XX_TEST1 4,606,619,326 4,983,759,822
XX_TEST2 121,973,005 123,161,581
You can apply the steps just like below :
SQL> create table XX_TEST1( id int primary key );
SQL> insert into XX_TEST1 select level from dual connect by level <= 100;
SQL> begin -- if table exists, then drop it!
for c in (select table_name from cat where table_name = 'XX_TEST1_OLD' )
loop
execute immediate 'drop table '||c.table_name;
end loop;
end;
/
SQL> create table XX_TEST1_old as select count(*) as cnt from XX_TEST1;
SQL> begin
execute immediate 'truncate table XX_TEST1';
end;
/
SQL> insert into XX_TEST1 select level from dual connect by level <= 103;
SQL> with xt1_new(cnt_new) as
(
select count(id) from XX_TEST1
)
select case when sign( (100 * ( cnt_new - cnt) / cnt)-2 ) = 1 then 1
else 0 end as "Rate Satisfaction"
from XX_TEST1_old
cross join xt1_new;
If this SELECT statement retuns 1, then we're successful to reach the target, else returns 0 and means we're unsuccessful.
Demo
Below trigger code(converted from MSSQL) in oracle is not working.
The two columns should not have duplicate row in the table. I'm creating a trigger for accomplishing this.
Can anyone help in updating/correcting the above code to be used in my trigger?
/*
**Unique Constraint for TestOracle - TestTinyInt.
*/
if (Update(UpdOperation) or Update(TestTinyInt)) THEN
IF Exists(
SELECT * FROM inserted i INNER LOOP JOIN TestOracle x ON
(i.TestTinyInt=x.TestTinyInt)
WHERE i.updoperation IN (0, 1) AND x.updoperation IN (0, 1) GROUP BY x.TestTinyInt
HAVING COUNT(*) > 1)
BEGIN
RAISERROR( 'Invalid attempt to enter duplicate TestTinyInt in TestOracle', 16, -1 )
ROLLBACK TRAN
RETURN
END
END
The best way is to create 2 unique index on each of columns. By doing this you are eliminating duplication in particual column(like #a_horse_with_no_name mentioned).
For other case you don't need to use triger, you need only simple where condition
where Column_A not in (select Column_B from table) and Column_B not in (Select Column_A in table).
EDIT:
It if have to be done in trigger THEN :
create or replace trigger ... instead of insert or update on ...
Declare
dummy number;
Begin
select 1 into dummy from dual where :new.Column_A in (select Column_B from table) or new:.Column_B in (Select Column_A in table);
if dummy <> 1 THEN
INSERT
END IF;
END;
EDIT2: IF you don't want unique index and tirgger here is solution :
create or replace trigger ... instead of insert or update on ...
Declare
dummy number;
Begin
select count(*) into dummy from(
SELECT COL1 FROM (
(select :new.Column_A col1 from dual
UNION
select :new.Column_B from dual))
INTERSECT
SELECT COL2 FROM (
( SELECT COLUMN_A COL2 from table
UNION
SELECT COLUMN_B from table));
if dummy = 0 THEN
INSERT
END IF;
END;
I want to fill the value of product_id. If article_code is not in the table, it executes the insert, but if record exists I don't know how to select the id of that record and assign to product_id.
The table "core_product" looks like that:
id
article_code
Here the code (inside of a function):
DECLARE
product_id int;
BEGIN
INSERT INTO core_product(article_code)
SELECT NEW.article_code
WHERE NOT EXISTS (
SELECT id INTO product_id
FROM core_product
WHERE article_code = NEW.article_code
)
RETURNING id INTO product_id;
END
Use a special variable FOUND:
DECLARE
product_id int;
BEGIN
SELECT id INTO product_id
FROM core_product
WHERE article_code = NEW.article_code;
IF NOT FOUND THEN
INSERT INTO core_product(article_code)
SELECT NEW.article_code
RETURNING id INTO product_id;
END IF;
END
If there is an unique constraint on article_code, you can harden the function against a race condition using retry loop (as Craig suggested in a comment):
BEGIN
LOOP
SELECT id INTO product_id
FROM core_product
WHERE article_code = NEW.article_code;
IF FOUND THEN
EXIT; -- exit loop
END IF;
BEGIN
INSERT INTO core_product(article_code)
SELECT NEW.article_code
RETURNING id INTO product_id;
EXIT; -- exit loop
EXCEPTION WHEN unique_violation THEN
-- do nothing, go to the beginning of the loop
-- and check once more if article_code exists
END;
END LOOP;
-- do something with product_id
END;
I have 2 tables- student and studLoad both having 2 fields studID and studName. I want to load data from student table into stuLoad table.
If the data already exists in the studLoad table, then it should be updated else it should be inserted. following is my code to do so:
create or replace procedure studentLoad is
v_id student.studID%type;
v_name student.studName%type;
v_sn studLoad.studName%type;
cursor cur_load is
select * from student;
begin
open cur_load;
loop
fetch cur_load into v_id,v_name;
exit when cur_load%notfound;
select studName into v_sn from studLoad where studID = v_id;
if(v_sn!= v_name) then
update studLoad set studName= v_name where studID= v_id;
else
insert into studLoad values(v_id,v_name);
dbms_output.put_line(v_id || ' ' || v_name);
end if;
end loop;
close cur_load;
end;
It's not working. the rows in studLoad table are noT updated. How do I solve this? In SQL server we use IF EXISTS(select...from stuLoad..) to check if the record exists in the table, is there a way to do the same in Oracle? if yes then please let me know the same.
This is a highly inefficient way of doing it. You can use the merge statement and then there's no need for cursors, looping or (if you can do without) PL/SQL.
MERGE INTO studLoad l
USING ( SELECT studId, studName FROM student ) s
ON (l.studId = s.studId)
WHEN MATCHED THEN
UPDATE SET l.studName = s.studName
WHERE l.studName != s.studName
WHEN NOT MATCHED THEN
INSERT (l.studID, l.studName)
VALUES (s.studId, s.studName)
Make sure you commit, once completed, in order to be able to see this in the database.
To actually answer your question I would do it something like as follows. This has the benefit of doing most of the work in SQL and only updating based on the rowid, a unique address in the table.
It declares a type, which you place the data within in bulk, 10,000 rows at a time. Then processes these rows individually.
However, as I say this will not be as efficient as merge.
declare
cursor c_data is
select b.rowid as rid, a.studId, a.studName
from student a
left outer join studLoad b
on a.studId = b.studId
and a.studName <> b.studName
;
type t__data is table of c_data%rowtype index by binary_integer;
t_data t__data;
begin
open c_data;
loop
fetch c_data bulk collect into t_data limit 10000;
exit when t_data.count = 0;
for idx in t_data.first .. t_data.last loop
if t_data(idx).rid is null then
insert into studLoad (studId, studName)
values (t_data(idx).studId, t_data(idx).studName);
else
update studLoad
set studName = t_data(idx).studName
where rowid = t_data(idx).rid
;
end if;
end loop;
end loop;
close c_data;
end;
/
If you would like to use your procedure, consider to change some lines:
create or replace procedure studentLoad is
v_id student.studID%type;
v_name student.studName%type;
v_sn studLoad.studName%type;
cursor cur_load is
select * from student;
begin
open cur_load;
loop
fetch cur_load into v_id,v_name;
exit when cur_load%notfound;
begin
select studName into v_sn from studLoad where studID = v_id;
if(v_sn!= v_name) then
update studLoad set studName= v_name where studID= v_id;
end if;
exception
when no_data_found then
insert into studLoad values(v_id,v_name);
end;
dbms_output.put_line(v_id || ' ' || v_name);
end loop;
close cur_load;
end;
I think it should work, didn't test it.