Insert deleted rows to new table in stored procedure - sql

How can I insert the rows deleted in a delete statement into a new table within a stored procedure in DB2 SQL?
DB2 allows the following syntax to return the deleted rows:
select * from old table (
delete from my_table where foo > 1
)
For some reason, you can't just do an insert based on the results returned from that statement. However, you can use common table expressions as a kludgy workaround:
with deleted as (
select * from old table (delete from my_table where foo > 1)
)
select * from new table (insert into archive_table select * from deleted)
This has an unnecessary extra select statement that I don't want, but at least it works. Deleted rows get inserted into another table.
However, how can I do this within a stored procedure?
A stored procedure doesn't allow a bare select statement. I thought of putting it within a set statement:
set my_var = (
with deleted as (
select * from old table (delete from my_table where foo > 1)
)
select * from new table (insert into archive_table select * from deleted)
);
However, this fails because a common table expression is not allowed within such a statement.
Is there any way to do this within a stored procedure?
(The task can be done using some other method as a work-around. But I want to find out if it is possible to do it this way. If this is not possible, it seems like quite a dumb restriction.)
Update: I'm using DB2 9.7 LUW.

If you issue a select, you have to consume the result set somehow, whether it is in a procedure or another application. You can either run a dummy loop within the procedure, like:
for t in (with deleted as (
select * from old table (delete from my_table where foo > 1)
)
select * from new table (insert into archive_table select * from deleted)
) loop
null;
end loop;
or use an explicit cursor:
declare c1 cursor for with deleted as (
select * from old table (delete from my_table where foo > 1)
)
select * from new table (insert into archive_table select * from deleted);
...
open c1;
close c1;
Note that neither of these is tested.

Why dont you flip it around? You can INSERT from a SELECT, and you can SELECT the rows from a DELETE.

Related

Condition in SQLite trigger for inheritance

I'm working with SQLite,I am using XOR single table inheritance, I want to create a trigger that enables me to:
Check before insertion if the InstructionRefs.id is already created in the table RideHeightRefs
Ckeck before insertion that the InstructionRefs.id does not exist in the other inherited table StrappingRefs.
I took some oracle PL/SQL code and changed it, I guess I am writing it wrong starting from IF NOT EXISTS (SELECT id...):
CREATE TRIGGER IF NOT EXISTS insert_instructionRefs_trigger BEFORE INSERT ON InstructionRefs
BEGIN
IF NOT EXISTS (SELECT id FROM RideHeightRefs AS RHR INNER JOIN InstructionRefs
IR ON RHR.id = IR.id)
BEGIN
SELECT RAISE(FAIL, '"RideHeightRefs" key is unknown. Insertion in
"instructionRefs" is impossible.')
END'
IF NOT EXISTS (SELECT *
FROM (SELECT RideHeightRefs
FROM StrappingRefs
UNION ALL
SELECT RideHeightRefs
FROM InstructionRefs) T
WHERE RideHeightRefs IN (SELECT RideHeightRefs
FROM NEW))
BEGIN
SELECT RAISE(FAIL, '"RideHeightRefs" key is used in another table. Insertion in "StrappingRefs" is impossible.')
END
END
How can I modify the code to make it compatible with sqlite syntax ?
To check that the corresponding row in the base table exists, just use a foreign key constraint.
SQLite has no IF statement. To check for something, add a WHERE clause to the SELECT FAIL, or use the trigger's WHEN clause:
CREATE TRIGGER IF NOT EXISTS insert_instructionRefs_trigger
BEFORE INSERT ON InstructionRefs
WHEN EXISTS (SELECT *
FROM StrappingRefs
WHERE id = NEW.id)
BEGIN
SELECT RAISE(FAIL, '"RideHeightRefs" key is used in another table. Insertion in "StrappingRefs" is impossible.');
END;

Create a global temporary table in Oracle SQL

I am new to SQL. I want to create a (global or not) temporary table in Oracle SQL which will include a simple selection of data of the form SELECT * FROM tbl_NAME WHERE... and which after the end of my session will be deleted (just like the MSFT SQL temporary tables of the form ##tbl_NAME).
I found online that one way to do it is:
CREATE GLOBAL TEMPORARY TABLE tmp_table
SELECT * FROM tbl_NAME WHERE conditions.
ON COMMIT PRESERVE ROWS;
although I get the error ORA-00904: invalid identifier
I also found that another alternative is
CREATE PRIVATE TEMPORARY TABLE tmp_table AS
SELECT * FROM tbl_NAME WHERE conditions;
which gives the error ORA-00905: missing keyword.
Please note that I already know that one alternative that works is:
DROP TABLE tmp_table;
CREATE TABLE tmp_table AS
SELECT * FROM tbl_NAME;
DROP TABLE tmp_table;
What you want to do with rows comes first; SELECT comes next:
SQL> create global temporary table gtt_dept
2 on commit preserve rows --> first
3 as
4 select * from dept; --> next
Table created.
SQL>
The below format you had shown works in TERADATA
CREATE GLOBAL TEMPORARY TABLE tmp_table
SELECT * FROM tbl_NAME WHERE conditions.
ON COMMIT PRESERVE ROWS;
In Oracle it works like this
create global temporary table gtt_dept
on commit preserve rows
as
select * from dept;

Delete all data from a table after selecting all data from the same table

All i want is to select all rows from a table and once it is selected and displayed, the data residing in table must get completely deleted. The main concern is that this must be done using sql only and not plsql. Is there a way we can do this inside a package and call that package in a select statement? Please enlighten me here.
Dummy Table is as follows:
ID NAME SALARY DEPT
==================================
1 Sam 50000 HR
2 Max 45000 SALES
3 Lex 51000 HR
4 Nate 66000 DEV
Any help would be greatly appreciated.
select * from Table_Name;
Delete from Table_Name
To select the data from a SQL query try using a pipelined function.
The function can define a cursor for the data you want (or all the data in the table), loop through the cursor piping each row as it goes.
When the cursor loop ends, i.e. all data has been consumed by your query, the function can perform a TRUNCATE table.
To select from the function use the following syntax;
SELECT *
FROM TABLE(my_function)
See the following Oracle documentation for information pipelined functions - https://docs.oracle.com/cd/B28359_01/appdev.111/b28425/pipe_paral_tbl.htm
This cannot be done inside a package, because " this must be done using sql only and not plsql". A package is PL/SQL.
However it is very simple. You want two things: select the table data and delete it. Two things, two commands.
select * from mytable;
truncate mytable;
(You could replace truncate mytable; with delete from mytable;, but this is slower and needs to be followed by commit; to confirm the deletion and end the transaction.)
Without pl/sql it's not possible.
Using pl/sql you can create a function which will populate a row, and then delete
Here is example :
drop table tempdate;
create table tempdate as
select '1' id from dual
UNION
select '2' id from dual
CREATE TYPE t_tf_row AS OBJECT (
id NUMBER
);
CREATE TYPE t_tf_tab IS TABLE OF t_tf_row;
CREATE OR REPLACE FUNCTION get_tab_tf RETURN t_tf_tab PIPELINED AS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
FOR rec in (select * from tempdate) LOOP
PIPE ROW(t_tf_row(rec.id));
END LOOP;
delete from tempdate ; commit;
END;
select * from table(get_tab_tf) -- it will populate and then delete
select * from tempdate --you can check here result of deleting
you can use below query
select * from Table_demo delete from Table_demo
The feature you seek is SERIALIZABLE ISOLATION LEVEL. This feature enables repeatable reads, which in particular guarantee that both SELECTand DELETEwill read and process the same identical data.
Example
Alter session set isolation_level=serializable;
select * from tempdate;
--- now insert from other session a new record
delete from tempdate ;
commit;
-- re-query the table old records are deleted, new recor preserved.

Is it possible to move a record from one table to another using a single SQL statement?

I need a query to move a record from one table to another without using multiple statements?
No, you cannot move records in one SQL statement. You have to use an INSERT followed by a DELETE statement. You should wrap these statements into a transaction, to make sure that the copy operation remains atomic.
START TRANSACTION;
INSERT INTO
new_table
SELECT
*
FROM
old_table
WHERE
some_field = 'your_criteria';
DELETE FROM old_table WHERE some_field = 'your_criteria';
COMMIT;
If you really want to do this in a single SQL statement, one way to accomplish this would be to create an "after delete" trigger on the source table that inserts the row into the target table. This way you can move the row from the source table to the target table simply by deleting it from the source table. Of course this will only work if you want to insert into target table for every delete on the source table.
DELIMITER $$
DROP TRIGGER IF EXISTS TR_A_DEL_SOURCE_TABLE $$
CREATE TRIGGER TR_A_DEL_SOURCE_TABLE AFTER DELETE ON SOURCE_TABLE FOR EACH ROW BEGIN
INSERT IGNORE INTO TARGET_TABLE(id,val1,val2) VALUES(old.id,old.va1,old.val2);
END $$
DELIMITER ;
So to move the row with id 42 from source table to target table:
delete from source_table where id = 42;
No - you might be able to do the INSERT in one nested statement, but you still need to remove the record.
There is no way to make it a single query, but if you HAVE to do it in a single query within an application you can create a Stored Procedure to do it for you.
DELIMITER $$
DROP PROCEDURE IF EXISTS `copydelete` $$
CREATE PROCEDURE `copydelete` (id INT)
BEGIN
INSERT INTO New_Table SELECT * from Old_Table WHERE Old_Table.IdField=id;
DELETE FROM Old_Table where IdField=id;
END $$
DELIMITER ;
Then you're new query is just
CALL copydelete(4);
Which will delete WHERE IdField=4;
Please note that the time delay between insert-select and delete can cause you to delete to much.
For a safe route you could use an update field:
update old_table set move_flag=1 where your_criteria
insert into ... select from ... where move_flag = 1
delete from old_table where move_flag=1
Or use a transaction which locks the old_table so no data can be added between insert... select and delete.

Oracle: how to UPSERT (update or insert into a table?)

The UPSERT operation either updates or inserts a row in a table, depending if the table already has a row that matches the data:
if table t has a row exists that has key X:
update t set mystuff... where mykey=X
else
insert into t mystuff...
Since Oracle doesn't have a specific UPSERT statement, what's the best way to do this?
The MERGE statement merges data between two tables. Using DUAL
allows us to use this command. Note that this is not protected against concurrent access.
create or replace
procedure ups(xa number)
as
begin
merge into mergetest m using dual on (a = xa)
when not matched then insert (a,b) values (xa,1)
when matched then update set b = b+1;
end ups;
/
drop table mergetest;
create table mergetest(a number, b number);
call ups(10);
call ups(10);
call ups(20);
select * from mergetest;
A B
---------------------- ----------------------
10 2
20 1
The dual example above which is in PL/SQL was great becuase I wanted to do something similar, but I wanted it client side...so here is the SQL I used to send a similar statement direct from some C#
MERGE INTO Employee USING dual ON ( "id"=2097153 )
WHEN MATCHED THEN UPDATE SET "last"="smith" , "name"="john"
WHEN NOT MATCHED THEN INSERT ("id","last","name")
VALUES ( 2097153,"smith", "john" )
However from a C# perspective this provide to be slower than doing the update and seeing if the rows affected was 0 and doing the insert if it was.
An alternative to MERGE (the "old fashioned way"):
begin
insert into t (mykey, mystuff)
values ('X', 123);
exception
when dup_val_on_index then
update t
set mystuff = 123
where mykey = 'X';
end;
Another alternative without the exception check:
UPDATE tablename
SET val1 = in_val1,
val2 = in_val2
WHERE val3 = in_val3;
IF ( sql%rowcount = 0 )
THEN
INSERT INTO tablename
VALUES (in_val1, in_val2, in_val3);
END IF;
insert if not exists
update:
INSERT INTO mytable (id1, t1)
SELECT 11, 'x1' FROM DUAL
WHERE NOT EXISTS (SELECT id1 FROM mytble WHERE id1 = 11);
UPDATE mytable SET t1 = 'x1' WHERE id1 = 11;
None of the answers given so far is safe in the face of concurrent accesses, as pointed out in Tim Sylvester's comment, and will raise exceptions in case of races. To fix that, the insert/update combo must be wrapped in some kind of loop statement, so that in case of an exception the whole thing is retried.
As an example, here's how Grommit's code can be wrapped in a loop to make it safe when run concurrently:
PROCEDURE MyProc (
...
) IS
BEGIN
LOOP
BEGIN
MERGE INTO Employee USING dual ON ( "id"=2097153 )
WHEN MATCHED THEN UPDATE SET "last"="smith" , "name"="john"
WHEN NOT MATCHED THEN INSERT ("id","last","name")
VALUES ( 2097153,"smith", "john" );
EXIT; -- success? -> exit loop
EXCEPTION
WHEN NO_DATA_FOUND THEN -- the entry was concurrently deleted
NULL; -- exception? -> no op, i.e. continue looping
WHEN DUP_VAL_ON_INDEX THEN -- an entry was concurrently inserted
NULL; -- exception? -> no op, i.e. continue looping
END;
END LOOP;
END;
N.B. In transaction mode SERIALIZABLE, which I don't recommend btw, you might run into
ORA-08177: can't serialize access for this transaction exceptions instead.
I'd like Grommit answer, except it require dupe values. I found solution where it may appear once: http://forums.devshed.com/showpost.php?p=1182653&postcount=2
MERGE INTO KBS.NUFUS_MUHTARLIK B
USING (
SELECT '028-01' CILT, '25' SAYFA, '6' KUTUK, '46603404838' MERNIS_NO
FROM DUAL
) E
ON (B.MERNIS_NO = E.MERNIS_NO)
WHEN MATCHED THEN
UPDATE SET B.CILT = E.CILT, B.SAYFA = E.SAYFA, B.KUTUK = E.KUTUK
WHEN NOT MATCHED THEN
INSERT ( CILT, SAYFA, KUTUK, MERNIS_NO)
VALUES (E.CILT, E.SAYFA, E.KUTUK, E.MERNIS_NO);
I've been using the first code sample for years. Notice notfound rather than count.
UPDATE tablename SET val1 = in_val1, val2 = in_val2
WHERE val3 = in_val3;
IF ( sql%notfound ) THEN
INSERT INTO tablename
VALUES (in_val1, in_val2, in_val3);
END IF;
The code below is the possibly new and improved code
MERGE INTO tablename USING dual ON ( val3 = in_val3 )
WHEN MATCHED THEN UPDATE SET val1 = in_val1, val2 = in_val2
WHEN NOT MATCHED THEN INSERT
VALUES (in_val1, in_val2, in_val3)
In the first example the update does an index lookup. It has to, in order to update the right row. Oracle opens an implicit cursor, and we use it to wrap a corresponding insert so we know that the insert will only happen when the key does not exist. But the insert is an independent command and it has to do a second lookup. I don't know the inner workings of the merge command but since the command is a single unit, Oracle could execute the correct insert or update with a single index lookup.
I think merge is better when you do have some processing to be done that means taking data from some tables and updating a table, possibly inserting or deleting rows. But for the single row case, you may consider the first case since the syntax is more common.
A note regarding the two solutions that suggest:
1) Insert, if exception then update,
or
2) Update, if sql%rowcount = 0 then insert
The question of whether to insert or update first is also application dependent. Are you expecting more inserts or more updates? The one that is most likely to succeed should go first.
If you pick the wrong one you will get a bunch of unnecessary index reads. Not a huge deal but still something to consider.
Try this,
insert into b_building_property (
select
'AREA_IN_COMMON_USE_DOUBLE','Area in Common Use','DOUBLE', null, 9000, 9
from dual
)
minus
(
select * from b_building_property where id = 9
)
;
From http://www.praetoriate.com/oracle_tips_upserts.htm:
"In Oracle9i, an UPSERT can accomplish this task in a single statement:"
INSERT
FIRST WHEN
credit_limit >=100000
THEN INTO
rich_customers
VALUES(cust_id,cust_credit_limit)
INTO customers
ELSE
INTO customers SELECT * FROM new_customers;