SQL delete records in order - sql

Given the table structure:
Comment
-------------
ID (PK)
ParentCommentID (FK)
I want to run DELETE FROM Comments to remove all records.
However, the relationship with the parent comment record creates a FK conflict if the parent comment is deleted before the child comments.
To solve this, deleting in reverse ID order would work. How do I delete all records in a table in reverse ID order?

The following will delete all rows that are not themselves parents. If the table is big and there's no index on ParentCommentID, it might take a while to run...
DELETE Comment
from Comment co
where not exists (-- Correlated subquery
select 1
from Comment
where ParentCommentID = co.ID)
If the table is truly large, a big delete can do bad things to your system, such as locking the table and bloating the transaction log file. The following will limit just how many rows will be deleted:
DELETE top (1000) Comment -- (1000 is not very many)
from Comment co
where not exists (-- Correlated subquery
select 1
from Comment
where ParentCommentID = co.ID)
As deleting some but not all might not be so useful, here's a looping structure that will keep going until everything's gone:
DECLARE #Done int = 1
--BEGIN TRANSACTION
WHILE #Done > 0
BEGIN
-- Loop until nothing left to delete
DELETE top (1000) Comment
from Comment co
where not exists (-- Correlated subquery
select 1
from Comment
where ParentCommentID = co.ID)
SET #Done = ##Rowcount
END
--ROLLBACK
This last, of course, is dangerous (note the begin/end transaction used for testing!) You'll want WHERE clauses to limit what gets deleted, and something or to ensure you don't somehow hit an infinite loop--all details that depend on your data and circumstances.

With separate Parent and Child tables, ON DELETE CASCADE would ensure that deleting the parent also deletes the children. Does it work when both sets of data are within the same table? Maybe, and I'd love to find out!
How do I use cascade delete with SQL server.

this works (you can try replacing the subquery with top...)
create table #a1 (i1 int identity, b1 char(5))
insert into #a1 values('abc')
go 5
while ( (select count(*) from #a1 ) > 0)
begin
delete from #a1 where i1=(select top 1 i1 from #a1 order by i1 desc)
end

Related

Remove row from table (cascade) that have 2 foreign keys

I have next table structure :
RoomId and HouseId are nullable, one field is always null. I got next exception while attempt to delete row from Houses table.
The DELETE statement conflicted with the REFERENCE constraint
"FK_dbo.Images_dbo.Rooms_RoomId"
Why the cascade delete fails? I have used EF code first.
EDIT
Working trigger based on #Juan Carlos example:
BEGIN TRANSACTION
delete from images
where houseId in ( select id from deleted )
delete from images
where roomId in (
select rooms.Id
from rooms
where rooms.HouseId in (select id from deleted)
)
delete from Rooms
where houseId in ( select id from deleted )
delete from houses
where ID in ( select ID from deleted )
COMMIT
The problem is the cascade order. You have follow this sequence:
Delete rooms
Delete images
Delete houses
You need create a trigger to solve that. Now the problem is I dont know how create that using Code First.
create trigger house_cascade
on Houses
instead of delete
as
set nocount on
delete from rooms
where room.id in (select i.ID
from images i
inner join deleted d
on i.house_id = d.id)
delete from images
where house_id in ( select id from deleted )
delete from houses
where ID in ( select ID from deleted )
More info here
Especially this comment
But SQL Server doesn't support this. It's supper annoying, no other serious DB engine has this problem, people complained about it in 2005, Microsfot agreed that it was a "desirable feature" in 2008, but still here in 2014 they don't have it. – Shavais Aug 5 '14 at 21:28

Database trigger to update a row on parent table after child table has been updated

First time creating a Database trigger. I have a child table that when its cost column is updated, I need its parent table to also update its cost column to reflect the change.
Here is my sorry attempt so far. That's obviously not working. I am having a problem figuring out how to extract the total cost as a variable and store it in the parent table.
My current approach assumes a static id vaule at the moment. I am not entirely sure how to dynamically determine the id value of the row that was updated.
CREATE TRIGGER ParentCost_Update
ON ChildTable
AFTER INSERT, UPDATE
AS
SELECT SUM(Cost) AS TotalCost FROM ChildTable where parent_id=2080
UPDATE ParentTable
SET Cost=TotalCost
where id=parent_id;
GO
This current script is returning an error
Msg 207, Level 16, State 1, Procedure ParentCost_Update, Line 9
Invalid column name 'TotalCost'.
You need to be careful in triggers, in that there could be more than one row updated. Therefore, you need to do row-based handling.
To obtain the newly inserted / updated row, use the inserted and deleted pseudo rows.
You will almost certainly also going to need to implement a deleted trigger as well, viz, if a row is removed from a child table that the parent will need to be recalculated.
Here's a row based take, using a CTE to map your two-step process above , as follows:
CREATE TRIGGER ParentCost_Update
ON ChildTable
AFTER INSERT, UPDATE, DELETE
AS
SET NOCOUNT ON;
WITH cteParentsAffected AS
(
SELECT ins.parent_id
FROM inserted ins
UNION
SELECT del.parent_id
FROM deleted del
)
, cteTotal AS
(
SELECT ct.parent_id, SUM(ct.Cost) AS TotalCost
FROM ChildTable ct
INNER JOIN cteParentsAffected par
ON ct.parent_id = par.parent_id
GROUP BY ct.parent_id
)
UPDATE pt
SET Cost=cte.TotalCost
FROM ParentTable pt
INNER JOIN cteTotal cte
ON id=cte.parent_id;
GO
With a SqlFiddle here
Try this
Update parenttable set total= (select sum(total) from childtable c where c.id= parent table.id)
Where id in (select id from inserted)
Change the table and column names.

SQL Server - Cascading DELETE with Recursive Foreign Keys

I've spent a good amount of time trying to figure out how to implement a CASCADE ON DELETE for recursive primary keys on SQL Server for some time now. I've read about triggers, creating temporary tables, etc but have yet to find an answer that will work with my database design.
Here is a Boss/Employee database example that will work for demonstration purposes:
TABLE employee
id|name |boss_id
--|---------|-------
1 |John |1
2 |Hillary |1
3 |Hamilton |1
4 |Scott |2
5 |Susan |2
6 |Seth |2
7 |Rick |5
8 |Rachael |5
As you can see, each employee has a boss that is also an employee. So, there is a PK/FK relationship on id/boss_id.
Here is an (abbreviated) table with their information:
TABLE information
emp_id|street |phone
------|-----------|-----
2 |blah blah |blah
6 |blah blah |blah
7 |blah blah |blah
There is a PK/FK on employee.id/information.emp_id with a CASCADE ON DELETE.
For example, if Rick was fired, we would do this:
DELETE FROM employee WHERE id=7
This should delete Rick's rows from both employee and information. Yay cascade!
Now, say we've hit hard times and we need to lay of Hamilton and his entire department. This means that we would need to remove
Hamilton
Scott
Susan
Seth
Rick
Rachael
From both the employee and information tables when we run:
DELETE FROM employee WHERE id=3
I tried a simple CASCADE ON DELETE for id/emp_id, but SQL Server wasn't having it:
Introducing FOREIGN KEY constraint 'fk_boss_employee' on table 'employee' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
I was able to use CASCADE ON DELETE on a test database in Access, and it behaved exactly as I wanted it to. Again, I want every possible child, grandchild, great-grandchild, etc of a parent to be deleted if their parent, grandparent, great-grandparent, etc is deleted.
When I tried using triggers, I couldn't seem to get it to trigger itself (eg. when you try to delete Hamilton's employee Susan, first see if Susan has any employees, etc) let alone going down N-number of employees.
So! I think I've provided every detail I can think of. If something still isn't clear, I'll try to improve this description.
Necromancing.
There's 2 simple solutions.
You can either read Microsoft's sorry-excuse(s) of why they didn't
implement this (because it is difficult and time-consuming - and time is money), and explanation of why you don't/shouldn't need it (although you do), and implement the delete-function with a cursor in a stored procedure
because you don't really need delete cascade, because you always have the time to change ALL your and ALL of OTHER people's code (like interfaces to other systems) everywhere, anytime, that deletes an employee (or employees, note: plural) (including all superordinate and subordinate objects [including when a or several new ones are added]) in this database (and any other copies of this database for other customers, especially in production when you don't have access to the database [oh, and on the test system, and the integration system, and local copies of production, test, and integration]
or
you can use a proper DBMS that actually supports recursive cascaded deletes, like PostGreSQL (as long as the graph is directed, and non-cyclic; else ERROR on delete).
PS: That's sarcasm.
Note:
As long as your delete does not stem from a cascade, and you just want to perform a delete on a self-referencing table, you can delete any entry, as long as you remove all subordinate objects as well in the in-clause.
So to delete such an object, do the following:
;WITH CTE AS
(
SELECT id, boss_id, [name] FROM employee
-- WHERE boss_id IS NULL
WHERE id = 2 -- <== this here is the id you want to delete !
UNION ALL
SELECT employee.id, employee.boss_id, employee.[name] FROM employee
INNER JOIN CTE ON CTE.id = employee.boss_id
)
DELETE FROM employee
WHERE employee.id IN (SELECT id FROM CTE)
Assuming you have the following table structure:
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'dbo.employee') AND type in (N'U'))
BEGIN
CREATE TABLE dbo.employee
(
id int NOT NULL,
boss_id int NULL,
[name] varchar(50) NULL,
CONSTRAINT PK_employee PRIMARY KEY ( id )
);
END
GO
IF NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'dbo.FK_employee_employee') AND boss_id_object_id = OBJECT_ID(N'dbo.employee'))
ALTER TABLE dbo.employee WITH CHECK ADD CONSTRAINT FK_employee_employee FOREIGN KEY(boss_id)
REFERENCES dbo.employee (id)
GO
IF EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'dbo.FK_employee_employee') AND boss_id_object_id = OBJECT_ID(N'dbo.employee'))
ALTER TABLE dbo.employee CHECK CONSTRAINT FK_employee_employee
GO
The below might work for you (I haven't tested it so it may require some tweaking). Seems like all you have to do is delete the employees from the bottom of the hierarchy before you delete the ones higher-up. Use a CTE to build the delete hierarchy recursively and order the CTE output descending by the hierarchy level of the employee. Then delete in order.
CREATE PROC usp_DeleteEmployeeAndSubordinates (#empId INT)
AS
;WITH employeesToDelete AS (
SELECT id, CAST(1 AS INT) AS empLevel
FROM employee
WHERE id = #empId
UNION ALL
SELECT e.id, etd.empLevel + 1
FROM employee e
JOIN employeesToDelete etd ON e.boss_id = etd.id AND e.boss_id != e.id
)
SELECT id, ROW_NUMBER() OVER (ORDER BY empLevel DESC) Ord
INTO #employeesToDelete
FROM employeesToDelete;
DECLARE #current INT = 1, #max INT = ##ROWCOUNT;
WHILE #current <= #max
BEGIN
DELETE employee WHERE id = (SELECT id FROM #employeesToDelete WHERE Ord = #current);
SET #current = #current + 1;
END;
GO
This may sound extreme, but I don't think there is a simple baked in option for what you are looking to do. I would suggest creating a proc that would do the following:
Disable FK constraints
get a list of employees to be deleted using a recursive CTE (save this in a temp table)
Delete the rows from the parent / child table
Delete rows from the employee information table
Enable FK Constraints
Wrap the whole thing in a transaction to maintain consistency

How to delete two records from two tables?

Hello I need to be able to search for a record that is a year old and then delete it. I have this script which allows me to delete the record from one table, based on a date given by another table,however I need to add code to this so that I am able to delete a record from a different table relating to CardID. The table that I need to delete from is table11 and Primary key is CardID.
I think I need a left join, but I am not to sure on how to go about it.
DECLARE #deleted TABLE (Card INT)
INSERT INTO #deleted
SELECT Card FROM table9
WHERE recordstatus = 4
DELETE table9
FROM #deleted d, table51
WHERE table51.ActionString LIKE '%' + CAST(d.card AS VARCHAR(20))+ '%'
AND table51.AuditDate <= (SELECT CONVERT(VARCHAR(8),today,112) FROM(SELECT DATEADD(YEAR,-1,GETDATE()) AS today)aa)
AND table09.Card = d.card
Thanks in advance, Hope you can help.
same as this question
edit: as #HLGEM mentioned, the WHERE clause goes where you expect it to go, after the join.

Unique column pairs as A,B or B,A

If I have a joining table with two columns, TeamA and TeamB, how can I ensure that each pair is unique?
Obviously I can put a unique composite index on these columns but that will only ensure uniqueness in the order A,B but not B,A correct?
TeamA | TeamB
-------------
red | blue
pink | blue
blue | red
As you can see, Red vs. Blue has already been specified as the first record and then it is specified again as the last. This should be illegal since they will already be facing each other.
Edit: Also, is there a way to handle the SELECT case as well? Or the UPDATE? DELETE? Etc..
Also, the idea of Home or Away team has been brought up which may be important here. This initial concept came to me while thinking about how to build a bracketing system on the DB side.
Define a constraint such that, for example, the value in the A column must be (alphabetically or numerically) smaller than the value in the B column: thus you'd be allowed to insert {blue,red} but not {red,blue} because blue is less than red.
If your RDBMS (you didn't specify) supports Triggers, then create a trigger on that table to enforce your constraint.
Create a trigger that fires on INSERT, that checks if a pair already exists with order reversed. If it does ROLLBACK, else allow the insert.
Here is some sample code for use with the trigger method that Mitch described.
I have not tested this code, and it's late at night here :-)
CREATE TRIGGER trig_addTeam
ON Teams
FOR INSERT, UPDATE
AS
DECLARE #TeamA VARCHAR(100)
DECLARE #TeamB VARCHAR(100)
DECLARE #Count INT
SELECT #TeamA = (SELECT TeamA FROM Inserted)
SELECT #TeamB = (SELECT TeamB FROM Inserted)
SELECT #Count = (SELECT COUNT(*) FROM TEAMS WHERE (TeamA = #TeamA AND TeamB = #TeamB)
OR (TeamA = #TeamB AND TeamB = #TeamA))
IF #Count > 0 THEN
BEGIN
ROLLBACK TRANSACTION
END
What this is doing is looking to see if either sequence of A|B or B|A exists in the current table. If it does then the count returned is greater than zero, and the transaction is rolled back and not committed to the database.
If the same pair (reversed) exists take the one where TeamA>TeamB.
SELECT DISTINCT TeamA, TeamB
FROM table t1
WHERE t1.TeamA > t1.TeamB
OR NOT EXISTS (
SELECT * FROM table t2
WHERE t2.TeamA = t1.TeamB AND t2.TeamB = t1.TeamA
)