How to delete master detail records using one sql? - sql

Using Delphi 7 and interbase 7
Is it possible to delete a master detail record and all of its nested detail records in one SQL statement?
Example:
Table1
ID - Integer
TITLE - Varchar(80)
Table2
ID - Integer
Table1_ID - Integer
TITLE - Varchar(80)
Table3
ID - Integer
Table2_ID - Integer
TITLE - Varchar(80)
I would like to delete ID 10 from Table1, and all of its matching records (Table1_ID) in table 2, and all its matching records (Table2_ID) in table 3
If i can't do this in one sql, how do i do it in multiple sqls (correct sequence to call statements)?

Yo can do it witn some SQL's deleteing in order, records on Table3, table2 and table1. All in one transaction to do it like "unique operation".
One alternative is use Triggers for delete record related on table2 when you delete one record on table1, and equivalent in table2 to delete related records on table3.
Another (if DB let you) is use ON CASCADE DELETE (or similar) to delete related records on a tabla2 and table3, when you delete a record un table1 (see help or documentation on your SGBD/Database).
Excuse-me for mistakes with English. It's not my natural language.
Regards.

You can use foreign keys to cascade deletes to child records when the parent record is deleted.
The following command should create the foreign key to delete records in Table2 (Table1_ID) when the record in Table1 (ID) is deleted.
ALTER TABLE TABLE2 ADD CONSTRAINT FK_TABLE2_TABLE1
FOREIGN KEY (Table1_ID) REFERENCES TABLE1 (ID)
ON DELETE CASCADE;
This way the database engine takes care of deletes in child tables so all you need to do is
delete from TABLE1 where ID = :ID_VALUE
Regards,

DELETE FROM Table3 WHERE Table2_ID IN (SELECT ID FROM Table_2 WHERE Table1_ID=xxxx)
DELETE FROM Table2 WHERE Table1_ID=xxxx
DELETE FROM Table1 WHERE ID=xxxx

You cannot do that in a single statement but instead you can use transactions in Delphi make sure that either all or none of the statements are executed.
If you are using BDE, then drop TDatabase component and set the default properties and write the following code.
try
Database1.StartTransaction;
//Execute first query
//Execute second query
//Execute third query
Database1.Commit;
except on E: Exception do
Database1.Rollback;
end;
If you are using ADO then use ADOConnection1.BeginTrans; ADOConnection1.CommitTrans; ADOConnection1.RollbackTrans; statements

If you create foreign key references with cascade option, deleting the parent record will also delete all details (unless other restrictions prevent this).
SQL:
ALTER TABLE Table2 ADD CONSTRAINT Table2_Table1_ID
FOREIGN KEY(Table1_ID) REFERENCES Table1(ID) ON DELETE CASCADE
This solution does not require the cooperation of the client applications, the server will keep the data model consistent.

In addition to the answers already given with multiple SQL statements, foreign key relationships and triggers. In InterBase you can also write a stored procedure to delete the master and detail records. Then you will only need one SQL statement in your program.
Below two kind of stored procedures you could use in this situation.
The first one is almost the same as HeartWave answer, but then in a stored procedure.
CREATE PROCEDURE DELETEMASTERDETAIL_WITHOUTINFO(
pMasterID INTEGER)
RETURNS (
rResult INTEGER)
AS
declare variable vTable2ID integer;
begin
/* don't return information about deleted records */
rResult = 0;
for select id
from table2
where table1_id = :pMasterID
into :vTable2ID do
begin
delete from table3
where table2_id = :vTable2ID;
end
delete from table2
where table1_id = :pMasterID;
delete from table1
where id = :pMasterID;
rResult = rResult + 1;
suspend;
end
The SQL statement to call this stored procedure is:
select rresult
from deletemasterdetail_withoutinfo(:pMasterID)
The second one will return information about the amount of deleted records per table. I don't know if you need it, but perhaps it is helpfull for someone else. If the ID field in Table1 is the primary key the first for select statement is a bit overkill.
CREATE PROCEDURE DELETEMASTERDETAIL_WITHINFO(
pMasterID INTEGER)
RETURNS (
rTable1Deleted INTEGER,
rTable2Deleted INTEGER,
rTable3Deleted INTEGER)
AS
declare variable vTable1ID integer;
declare variable vTable2ID integer;
declare variable vTable3ID integer;
begin
/* return information about deleted records */
rTable1Deleted = 0;
rTable2Deleted = 0;
rTable3Deleted = 0;
for select id
from table1
where id = :pMasterID
into :vTable1ID do
begin
for select id
from table2
where table1_id = :vTable1ID
into :vTable2ID do
begin
for select id
from table3
where table2_id = :vTable2ID
into :vTable3ID do
begin
rTable3Deleted = rTable3Deleted + 1;
delete from table3
where id = :vTable3ID;
end
rTable2Deleted = rTable2Deleted + 1;
delete from table2
where id = :vTable2ID;
end
rTable1Deleted = rTable1Deleted + 1;
delete from table1
where id = :vTable1ID;
end
suspend;
end
The SQL statement to call this stored procedure is:
select rtable1deleted, rtable2deleted, rtable3deleted
from deletemasterdetail_withinfo(:pMasterID)
BTW. I almost always use at least one return parameter in SP's. This will allow to use a Query component to call the stored procedure.
If there are no result parameters a Stored Procedure component must be used to execute the SP.

Related

SQL Server : delete row if no constraint error

Much more complicated then this, but this is the basic
Person table (id, name, emailaddress)
Salesperson table (id, personID)
CustomerServiceRep table (id, personID)
Jeff is salesperson (id=4) and customerservicerep (id=5) with personID=1.
Simple
Trigger on SalesPerson Table
AFTER DELETE
AS
DECLARE #personID int = (SELECT personID FROM deleted);
IF #personID IS NOT NULL
BEGIN TRY
DELETE FROM Person
WHERE Person.id = #personID;
END TRY
BEGIN CATCH
END CATCH
DELETE FROM SalesPerson WHERE id=4;
Causes
Msg 3616, Level 16, State 1
An error was raised during trigger execution. The batch has been aborted and the user transaction, if any, has been rolled back.
I'm sure there's a much simpler way to not delete personID if it exists from some kind of constraint. Or catch the constraint. To go through every possible table that this could be in seems very repetitive and potentially more difficult when there are more tables/columns that may use this same table/constraint (foreign key).
You need an instead of delete trigger here rather than an after trigger.
CREATE Trigger tr_Delete_person
on Person
INSTEAD OF DELETE
AS
BEGIN
SET NOCOUNT ON;
-- Delete any child records
Delete FROM SalesPerson
WHERE EXISTS (SELECT 1 FROM deleted
WHERE personID = SalesPerson.personID)
Delete FROM CustomerServiceRep
WHERE EXISTS (SELECT 1 FROM deleted
WHERE personID = CustomerServiceRep .personID)
-- Finally delete from the person table
DELETE FROM Person
WHERE EXISTS (SELECT 1 FROM deleted
WHERE personID = Person .personID)
END
You also have a fundamental flaw in your trigger in that you seem to expect that the trigger will be fired once per row - this is NOT the case in SQL Server. Instead, the trigger fires once per statement, and the pseudo table Deleted might contain multiple rows.
Given that that table might contain multiple rows - which one do you expect will be selected here??
DECLARE #personID int = (SELECT personID FROM deleted);
It's undefined - you'll get the value from one, arbitrary row in Deleted, and all others are ignored - typically not what you want!
You need to rewrite your entire trigger with the knowledge the Deleted WILL contain multiple rows! You need to work with set-based operations - don't expect just a single row in Deleted !

SQL alter script - copying from one table to another

I'm trying to figure out how to implement the alter script described below. I'm familiar with the basics if insert/select already, but this is a lot more complex.
I have a legacy table and need to move its data to a new table with more columns. The new table has already been made public to some select users, who may have already manually moved the common data over.
So for each row in LegacyTable:
see if it already exists in NewImprovedTable (by checking for a match on a string field that exists in both tables)
if not, copy its over to NewImprovedTable
regardless of whether it had been copied to NewImprovedTable automatically just now, or previously by the user...
auto-populate a new Name field in NewImprovedTable (must be unique - e.g. "Legacy1", "Legacy2", etc.)
set an IsLegacy flag in NewImprovedTable
I need to implement this in both MS SQL and Oracle, but once I work out the logic on one I can figure out the syntax on the other.
The solution I settled on (in SQL Server - still need to port to Oracle):
IF NOT EXISTS(SELECT 1
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'NewImprovedTable'
AND COLUMN_NAME = 'legacyFlg')
BEGIN
ALTER TABLE [NewImprovedTable]
ADD legacyFlg TINYINT NULL
ALTER TABLE [LegacyTable]
ADD improvedId INT NULL
END
GO
IF NOT EXISTS(SELECT 1 FROM ImprovedTable WHERE legacyFlg = 1)
BEGIN
MERGE ImprovedTable AS TARGET
USING LegacyTable AS SOURCE
ON (TARGET.stringField = SOURCE.stringField)
WHEN NOT MATCHED THEN
INSERT (name, <other columns>, legacyFlg)
VALUES('Legacy' + SOURCE.stringField, <other column values>, 1)
WHEN MATCHED THEN
UPDATE SET TARGET.legacyFlg = 1;
END
GO
IF NOT EXISTS(SELECT 1 FROM LegacyTable WHERE improvedId <> 0)
BEGIN
MERGE LegacyTable AS TARGET
USING NewImprovedTable AS SOURCE
ON (SOURCE.stringField = TARGET.stringField)
WHEN MATCHED THEN
UPDATE SET TARGET.improvedId = SOURCE.pId;
END
GO
You could try using this, where 'input' is the string you are trying to confirm if already exists:
SELECT * FROM`NewImprovedTable` WHERE `Variable`='input'
This will return the whole row if found any match, if not it will return null, you can play with that
As for the unique ID field you need to create a primary key on your table with the auto increment option enable, for example
CREATE TABLE Persons
(
P_Id INT NOT NULL AUTO_INCREMENT,
LastName varchar(255) NOT NULL,
FirstName varchar(255),
Address varchar(255),
City varchar(255),
PRIMARY KEY (P_Id)
)
In this last example P_Id is set as an autoincrement variable, each time you crate a new row it will auto fill this column with a unique number.
You should check this page
http://www.w3schools.com/sql/sql_primarykey.asp

SQL Trigger update another table

I am newbie to triggers... can anybody help me with a trigger?
I have Table:
Name | Number
I want to write a trigger when my table receives a query like
update MyTable
set Number = Number + 1
where Name = 'myname'
When this query is running, the trigger should update another table for example:
Update MyTable 2
set Column = 'something'
where Name = 'myname (above name)
Thank you very much !
You will need to write an UPDATE trigger on table 1, to update table 2 accordingly.
Be aware: triggers in SQL Server are not called once per row that gets updated - they're called once per statement, and the internal "pseudo" tables Inserted and Deleted will contain multiple rows, so you need to take that into account when writing your trigger.
In your case, I'd write something like:
-- UPDATE trigger on "dbo.Table1"
CREATE TRIGGER Table1Updated
ON dbo.table1 FOR UPDATE
AS
BEGIN
-- update table2, using the same rows as were updated in table1
UPDATE t2
SET t2.Column = 'something'
FROM dbo.Table2 t2
INNER JOIN Inserted i ON t2.ID = i.ID
END
GO
The trick is to use the Inserted pseudo table (which contains the new values after the UPDATE - it has the exact same structure as your table the trigger is written for - here dbo.Table1) in a set-based fashion - join that to your dbo.Table2 on some column that they have in common (an ID or something).
create a trigger on table 1 for update:
CREATE TRIGGER dbo.update_trigger
ON table1
AFTER UPDATE
AS
BEGIN
DECLARE #Name VARCHAR(50)
SELECT #Name=Name FROM INSERTED
Update MyTable 2
SET Column = 'something'
WHERE Name = #Name
END
GO
try this ;)

SQLite renumber ID using cycle

Hello I have table with many inserted row. I need to renumber all row by id and order them.
I have found this code but it does not work for me.
SET #i = 100;
UPDATE "main"."Categories" SET ID = (#i := #i +1) WHERE "Name" = "White";
ALTER TABLE "main"."Categories" AUTO_INCREMENT = 1
So using code above I expected renumbered all records that have name - white and start insert them from 100 with increment 1. But it is not work for me. Maybe there is some problem in my code but maybe it is a difference between SQL and SQLite query.
This how I created table:
CREATE TABLE Categories (id INTEGER PRIMARY KEY, Name TEXT, Free NUMERIC)
I hope there is already made solution how to do it because I don't want to do it manually :)
That code is not standard SQL.
SQLite does not have many programming constructs because it is designed to be an embedded database where it is more natural to have the logic in the host language.
If you want to do this in SQL, try the following:
First, create a temporary table so that we have an autoincrement column that can be used for counting:
CREATE TEMPORARY TABLE new_ids(i INTEGER PRIMARY KEY, old_id INTEGER);
Insert a dummy record to ensure that the next new record starts at 100, then insert all the IDs of the Categories table that you want to change:
INSERT INTO new_ids VALUES(99, NULL);
INSERT INTO new_ids SELECT NULL, id FROM "Categories" WHERE "Name" = 'White';
DELETE FROM new_ids WHERE i = 99;
Then we can change all these IDs in the original table:
UPDATE "Categories"
SET id = (SELECT i FROM new_ids WHERE old_id = "Categories".id)
WHERE id IN (SELECT old_id FROM new_ids);
DROP TABLE new_ids;

Multiple delete operation in a stored procedure

I want to make some tables empty if a stored procedure runs but I couldn't do that. What I did is
create proc MakeEmpty ()
as
begin
delete from table1
delete from table2
delete from table3
end
Use truncate instead and remove the () like this:
create proc MakeEmpty
as
begin
truncate table table1
truncate table table2
truncate table table3
end
Two things:
1 - Those parentheses should only be used if you are declaring a parameter for the procedure. If there is no param they are unneeded.
2 - If you want to empty the tables, then you should use TRUNCATE - it's minimally logged and is basically a meta-operation instead of a row-by-row delete.
You will need to clarify about what's not working, but something like this should do the trick:
BEGIN
IF EXISTS (SELECT 1 from DatabaseName.sys.Tables WHERE Name = 'Table1')
TRUNCATE TABLE Databasename.dbo.Table1
...repeat for other tables...
END
Try to remove the ( and )
create proc MakeEmpty
as
begin
delete from table1;
delete from table2;
delete from table3;
end
but make sure table2 and table3 does not reference table1 and table3 does not reference table2