Ensure Successful INSERT before triggering DELETE - sql

Am currently working on archiving a database, I have come up with a simple approach. However, executing the script can run into errors. Leading to a case where the insert is not successfully executed but the delete. Which means I delete records from production before inserting them into archive. Is there a way to ensure delete statement would not be executed unless insert is run successfully?
INSERT INTO [archive].[dbo].[Table]
SELECT *
FROM [Production].[dbo].[Table]
WHERE TimeStamp < DATEADD(year,-2,SYSDATETIME())
DELETE FROM [Production].[dbo].[table]
WHERE TimeStamp < DATEADD(year,-2,SYSDATETIME())

As an alternative to an explict transaction, one can specify an OUTPUT clause on the DELETE to perform the operation as a single autocommit transaction. This will ensure all-or-none behavior.
DELETE [Production].[dbo].[Table]
OUTPUT DELETED.*
INTO [archive].[dbo].[Table]
WHERE TimeStamp < DATEADD(year,-2,SYSDATETIME());
Also, consider an explict column list instead of *.

Typically, you would create a transaction, making the operation into a single update.
BEGIN TRAN
BEGIN TRY
INSERT INTO [archive].[dbo].[Table]
SELECT * FROM [Production].[dbo].[Table] WHERE TimeStamp <
DATEADD(year,-2,SYSDATETIME())
DELETE FROM [Production].[dbo].[table]
WHERE TimeStamp < DATEADD(year,-2,SYSDATETIME())
COMMIT
END TRY
BEGIN CATCH
ROLLBACK
END CATCH
Basically, the code says.
BEGIN TRAN - Start a group of commands that either all complete or
none complete
BEGIN TRY - Commands to try
COMMIT - If I reach here, everything worked OK
BEGIN CATCH
ROLLBACK If an error occurs, roll back the commands (basically ignore
them)
You should probably add some status to indicate success or failure, and maybe capture the error in the BEGIN CATCH block, but this should give you enough to get started
A second approach is to modify your DELETE statement a bit
DELETE FROM [Production].[dbo].[table]
WHERE TimeStamp IN (SELECT TimeStamp FROM [archive].[dbo].[Table])
Good luck

Related

How to use multiple triggers?

DROP TRIGGER IF EXISTS N2Trigger
CREATE TRIGGER N2Trigger
ON dbo.Date
FOR INSERT, DELETE
AS
BEGIN
SELECT 'Inserted Datebase' as MESSAGE
SELECT 'Deleted Database' as MESSAGE
END
DELETE FROM dbo.[Date] WHERE ID = 1
Here is my code I just want when I use insert statement return 'Inserted Datebase' as MESSAGE
When I use delete statement return 'Deleted Database' as MESSAGE
The easiest way to check what action fired the trigger is to inspect the inserted and deleted pseudo-tables. If the trigger is only on DELETE/INSERT and not on update, then the logic is simply:
CREATE TRIGGER dbo.trFlarb ON dbo.flarb
FOR INSERT, DELETE
AS
BEGIN
IF EXISTS (SELECT 1 FROM inserted)
BEGIN
SELECT 'Inserted.';
END
IF EXISTS (SELECT 1 FROM deleted)
BEGIN
SELECT 'Deleted.';
END
END
Example db<>fiddle
Now, of course, Marc is right: triggers aren't for returning or printing output. This is just a demonstration that you can use those checks to then perform whatever logic you need to perform in the event of either action.
That said, if you have two distinctly different things you want to do depending on whether it's an insert or a delete, why not just create two separate triggers?
CREATE TRIGGER dbo.tr_I_Flarb ON dbo.flarb
FOR INSERT
AS
BEGIN
SELECT 'Inserted.';
END
GO
CREATE TRIGGER dbo.tr_D_Flarb ON dbo.flarb
FOR DELETE
AS
BEGIN
SELECT 'Deleted.';
END
GO
Note that SELECT will only "work" on your system if you haven't turned on the disallow results from triggers Server Configuration Option. Again, you should try to explain what you really want to do in the event of an insert or update, because the end goal can't be to print or return "Inserted" or "Deleted."

SELECT after WITH returns no rows if in an explicit transaction

WITH generated_id AS (
INSERT INTO ... RETURNING id
)
SELECT id FROM generated_id;
returns 42, whereas
BEGIN;
WITH generated_id AS (
INSERT INTO ... RETURNING id
)
SELECT id FROM generated_id;
COMMIT;
returns nothing. Why?
Update:
I just found that WITH is irrelevant, because even a single select doesn't work:
SELECT something FROM some_table;
returns rows.
BEGIN;
SELECT something FROM some_table;
COMMIT;
returns no rows.
Update 2:
I thought BEGIN and START TRANSACTION are the exact same things. Anyways, I tried all possible combinations but none of them works for me.
I'm using this free postgres service but now I tested it with SQL Fiddle and it is not complaining.
It is strange however that if I don't put a ; at the end of the SELECT line, my DB engine gives me a syntax error and if I put it there using SQL Fiddle, it tells me that Explicit commits are not allowed.
So it's still very unclear for me what is exactly happening. Whether it only works in SQL Fiddle because it really isn't running my query in an explicit transaction and if it would then the results would be the same: no rows, just as how my DB engine behaves.
I'm unfortunately not able to test it on other servers, but if someone has a reliable postgres config, maybe they could try it for me whether it runs and tell me.
This will return nothing in most clients because you only see what the last command returned:
BEGIN;
SELECT something FROM some_table;
COMMIT;
Try instead:
BEGIN;
SELECT something FROM some_table;
But don't forget to COMMIT or ROLLBACK later to terminate the open transaction.
SQL Fiddle does not allow explicit transaction wrappers. I quote:
All SQL queries are run within a transaction that gets immediately rolled-back after the SQL executes.
BEGIN; issued inside an open transaction only issues a WARNING - which is not displayed in SQL Fiddle (you see a result with 0 rows).
COMMIT; raises the error you saw.
And in your attempt with omitting the semicolon after the SELECT, COMMIT is interpreted as table alias:
BEGIN;
SELECT something FROM some_table
COMMIT;
... is equivalent to:
BEGIN;
SELECT something FROM some_table AS commit;
So that's another misunderstanding.
Did you try saying BEGIN WORK?
I think you need to start with that, then you can end it with COMMIT
Try to start with:
BEGIN TRANSACTION;
Then end with:
END TRANSACTION;

How can I check whether a stored procedure that deletes all rows from multiple tables succeeded?

I want a stored procedure that does the equivalent of the following
CREATE PROCEDURE Reset
AS
BEGIN
DELETE * FROM SomeTable;
DELETE * FROM SomeOtherTable;
END
and also returns some indicator of success or failure. How would I do that? Only way I can think of is pre-calculating the number of rows that should be affected, but that seems so shoddy.
The following will produce a success/failure indicator based on the question, 'did the table reset complete without failure?'. It's wrapped in a transaction so that both deletes happen or none happen, which keeps your data clean.
BEGIN TRY
BEGIN TRANSACTION;
DELETE FROM SomeTable;
DELETE FROM SomeOtherTable;
COMMIT TRANSACTION;
-- success indicator
SELECT 1 AS Result
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION;
-- failure indicator
SELECT 0 AS Result
END CATCH

Why should we use rollback in sql explicitly?

I'm using PostgreSQL 9.3
I have one misunderstanding about transactions and how they work. Suppose we wrapped some SQL operator within a transaction like the following:
BEGIN;
insert into tbl (name, val) VALUES('John', 'Doe');
insert into tbl (name, val) VALUES('John', 'Doee');
COMMIT;
If something goes wrong the transaction will automatically be rolled back. Taking that into account I can't get when should we use ROLLBACK explicitly? Could you get an example when it's necessary?
In PostgreSQL the transaction is not automatically rolled back on error.
It is set to the aborted state, where further commands will fail with an error until you roll the transaction back.
Observe:
regress=> BEGIN;
BEGIN
regress=> LOCK TABLE nosuchtable;
ERROR: relation "nosuchtable" does not exist
regress=> SELECT 1;
ERROR: current transaction is aborted, commands ignored until end of transaction block
regress=> ROLLBACK;
ROLLBACK
This is important, because it prevents you from accidentally executing half a transaction. Imagine if PostgreSQL automatically rolled back, allowing new implicit transactions to occur, and you tried to run the following sequence of statements:
BEGIN;
INSERT INTO archive_table SELECT * FROM current_tabble;
DELETE FROM current_table;
COMMIT;
PostgreSQL will abort the transaction when it sees the typo current_tabble. So the DELETE will never happen - all statements get ignored after the error, and the COMMIT is treated as a ROLLBACK for an aborted transaction:
regress=> BEGIN;
BEGIN
regress=> SELECT typo;
ERROR: column "typo" does not exist
regress=> COMMIT;
ROLLBACK
If it instead automatically rolled the transaction back, it'd be like you ran:
BEGIN;
INSERT INTO archive_table SELECT * FROM current_tabble;
ROLLBACK; -- automatic
BEGIN; -- automatic
DELETE FROM current_table;
COMMIT; -- automatic
... which, needless to say, would probably make you quite upset.
Other uses for explicit ROLLBACK are manual modification and test cases:
Do some changes to the data (UPDATE, DELETE ...).
Run SELECT statements to check results of data modification.
Do ROLLBACK if results are not as expected.
In Postgres DB you can do this even with DDL statements (CREATE TABLE, ...)

PL/SQL Oracle Stored Procedure loop structure

Just wondering if the way I put COMMIT in the code block is appropriate or not? Should I put them when it finished loop or after each insert statement or after the if else statement?
FOR VAL1 IN (SELECT A.* FROM TABLE_A A) LOOP
IF VAL1.QTY >= 0 THEN
INSERT INTO TEMP_TABLE VALUES('MORE OR EQUAL THAN 0');
COMMIT; /*<-- Should I put this here?*/
INSERT INTO AUDIT_TABLE VALUE('DATA INSERTED >= 0');
COMMIT; /*<-- Should I put this here too?*/
ELSE
INSERT INTO TEMP_TABLE VALUES ('0');
COMMIT; /*<-- Should I put this here too?*/
INSERT INTO AUDIT_TABLE('DATA INSERTED IS 0');
COMMIT; /*<-- Should I put this here too?*/
END IF;
/*Or put commit here?*/
END LOOP;
/*Or here??*/
Generally, committing in a loop is not a good idea, especially after every DML in that loop. Doing so you force oracle(LGWR) to write data in redo log files and may find yourself in a situation when other sessions hang because of log file sync wait event. Or facing ORA-1555 because undo segments will be cleared more often.
Divide your DMLs into logical units of work (transactions) and commit when that unit of work is done, not before and not too late or in a middle of a transaction. This will allow you to keep your database in a consistent state. If, for example, two insert statements form a one unit of work(one transaction), it makes sense to commit or rollback them altogether not separately.
So, generally, you should commit as less as possible. If you have to commit in a loop, introduce some threshold. For instance issue commit after, let say 150 rows:
declare
l_commit_rows number;
For i in (select * from some_table)
loop
l_commit_rows := l_commit_rows + 1;
insert into some_table(..) values(...);
if mode(l_commit_rows, 150) = 0
then
commit;
end if;
end loop;
-- commit the rest
commit;
It is rarely appropriate; say your insert into TEMP_TABLE succeeds but your insert into AUDIT_TABLE fails. You then don't know where you are at all. Additionally, commits will increase the amount of time it takes to perform an operation.
It would be more normal to do everything within a single transaction; that is remove the LOOP and perform your inserts in a single statement. This can be done by using a multi-table insert and would look something like this:
insert
when ( a.qty >= 0 ) then
into temp_table values ('MORE OR EQUAL THAN 0')
into audit_table values ('DATA INSERTED >= 0')
else
into temp_table values ('0')
into audit_table values ('DATA INSERTED IS 0')
select qty from table_a
A simple rule is to not commit in the middle of an action; you need to be able to tell exactly where you were if you have to restart an operation. This normally means, go back to the beginning but doesn't have to. For instance, if you were to place your COMMIT inside your loop but outside the IF statement then you know that that has completed. You'd have to write back somewhere to tell you that this operation has been completed though or use your SQL statement to determine whether you need to re-evaluate that row.
If you insert commit after each insert statement then the database will commit each row inserted. Same will happen if you insert commit after the IF statement ends. (So both will commit after each inserted row). If commit is given after loop then commit will happen after all rows are inserted.
Commit after the loop should work faster as it will commit bulk data but if your loop encounters any error (say after 50 rows are processed there is an error) then your 50 rows also won't be inserted.
So according to your requirement u can either use commit after if or after loop