Getting all changed tables and rows using Change Tracking in SQL Server - sql-server

I need to get all the tables that were changed in a database, including the rows' IDs that were updated/added/removed.
So, if I have table table1 and table2, and rows with ID 15 and 16 were changed in table1, and rows with IDs 200 and 201 were changed in table2, I want to run a script that returns:
+-----------+-------+
| tableName | rowId |
+-----------+-------+
| table1 | 15 |
| table1 | 16 |
| table2 | 200 |
| table2 | 201 |
+-----------+-------+
I managed to find this script:
set nocount on;
-- We want to check for changes since the previous version
--declare #prevTrackingVersion int = INSERT_YOUR_PREV_VERSION_HERE
-- Comment out this line if you know the previous version
declare #prevTrackingVersion int = CHANGE_TRACKING_CURRENT_VERSION() - 1
-- Get a list of table with change tracking enabled
declare #trackedTables as table (name nvarchar(1000));
insert into #trackedTables (name)
select sys.tables.name from sys.change_tracking_tables
join sys.tables ON tables.object_id = change_tracking_tables.object_id
-- This will be the list of tables with changes
declare #changedTables as table (name nvarchar(1000));
-- For each table name in tracked tables
declare #tableName nvarchar(1000)
while exists(select top 1 * from #trackedTables)
begin
-- Set the current table name
set #tableName = (select top 1 name from #trackedTables order by name asc);
print #tableName
-- Determine if the table has changed since the previous version
declare #sql nvarchar(250)
declare #retVal int
set #sql = 'select #retVal = count(*) from changetable(changes ' + #tableName + ', ' + cast(#prevTrackingVersion as varchar) + ') as changedTable'
exec sp_executesql #sql, N'#retVal int output', #retVal output
print #retVal
if #retval > 0
begin
insert into #changedTables (name) select #tableName
end
-- Delete the current table name
delete from #trackedTables where name = #tableName;
end
select * from #changedTables;
However, it only outputs the names of the tables that were modified.
So, I know that this command:
SELECT * FROM CHANGETABLE(CHANGES table1, 0) as CT;
outputs this:
+----------------------+----+
| SYS_CHANGE_OPERATION | ID |
+----------------------+----+
| I | 15 |
| I | 16 |
+----------------------+----+
In the script, this same command is used:
set #sql = 'select #retVal = count(*) from changetable(changes ' + #tableName + ', ' + cast(#prevTrackingVersion as varchar) + ') as changedTable'
but it only gets the count, and then checks if it is greater than 0 (if there is any modifications for this table):
if #retval > 0
begin
insert into #changedTables (name) select #tableName
end
However, as mentioned, I need to not only get the tables modified, but the IDs of the rows that were modified.
I feel like I need to JOIN, but I'm not really sure how to work this out.
Thanks

What you need to do is -
Find out all the tracking tables and their primary keys.
The result returned from CHANGETABLE includes the primary key of the tracking table, NOT ROWID as you thought.
For example, if table t1's PK is defined on t1.id, then CHANGETABLE for t1 will return "id" column; if table t2's PK is defined on (id1, id2), then CHANGETABLE for t2 will return "id1" and "id2".
Try to run CHANGETABLE for one table can help you understand what I am saying -
select *
from CHANGETABLE(CHANGES your_table, 0 /*track version*/) chg
To keep things simple, I assume all your tracking tables have single-column PK.
You can find out tracking tables and their PKs by -
select c.table_name
,k.column_name pk
from sys.change_tracking_tables t
,information_schema.table_constraints c
,information_schema.key_column_usage k
where object_name(t.object_id) = c.table_name
and c.constraint_type = 'PRIMARY KEY'
and c.table_name = k.table_name
and c.constraint_name = k.constraint_name
Run CHANGETABLE for each tracking table and combine the results.
Here is the whole modified script -
set nocount on;
-- We want to check for changes since the previous version
--declare #prevTrackingVersion int = INSERT_YOUR_PREV_VERSION_HERE
-- Comment out this line if you know the previous version
declare #prevTrackingVersion int CHANGE_TRACKING_CURRENT_VERSION() - 1
-- Get a list of table with change tracking enabled
declare #trackedTables as table (table_name nvarchar(100), pk nvarchar(100));
insert into #trackedTables (table_name, pk)
select c.table_name
,k.column_name pk
from sys.change_tracking_tables t
,information_schema.table_constraints c
,information_schema.key_column_usage k
where object_name(t.object_id) = c.table_name
and c.constraint_type = 'PRIMARY KEY'
and c.table_name = k.table_name
and c.constraint_name = k.constraint_name
-- This will be the list of changes
declare #changes as table (table_name varchar(100), pk varchar(100))
-- For each table name in tracked tables
declare #table_name nvarchar(100)
,#pk nvarchar(100)
while exists(select top 1 * from #trackedTables)
begin
-- Set the current table name
select top 1 #table_name = table_name, #pk = pk from #trackedTables order by table_name asc;
insert into #changes (table_name, pk)
exec ('select ''' + #table_name + ''', ' + #pk + ' from CHANGETABLE(CHANGES ' + #table_name + ', ' + #prevTrackingVersion + ') chg')
-- Delete the current table name
delete from #trackedTables where table_name = #table_name;
end
select * from #changes;

You could create a trigger on each table in your schema (or rather the one you truly need) like this
CREATE TRIGGER TRX_TableName_ChangeLog ON TableName
AFTER INSERT,DELETE,UPDATE
AS
BEGIN
DECLARE #TABLE_NAME NVARCHAR(400)
SELECT #TABLE_NAME = OBJECT_NAME(parent_object_id)
FROM sys.objects
WHERE sys.objects.object_id = ##PROCID
INSERT INTO ChangeTableLog (TABLE_NAME, TABLE_ID)
SELECT #TABLE_NAME,INSERTED.ID
FROM INSERTED --or delete
END
The creation of the trigger could be dynamically something like this:
DECLARE #TABLE_NAME nvarchar(200),#Trigger nvarchar(max)
DECLARE CURS CURSOR LOCAL FAST_FORWARD FOR
SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
OPEN CURS
FETCH NEXT FROM CURS INTO #TABLE_NAME
WHILE ##FETCH_STATUS = 0
BEGIN
SET #Trigger =
'CREATE TRIGGER TRX_'+#TABLE_NAME+'_ChangeLog
ON '+#TABLE_NAME+'
AFTER INSERT,DELETE,UPDATE
AS
BEGIN
DECLARE #TABLE_NAME NVARCHAR(400)
SELECT #TABLE_NAME = OBJECT_NAME(parent_object_id)
FROM sys.objects
WHERE sys.objects.object_id = ##PROCID
INSERT INTO ChangeTableLog (TABLE_NAME, TABLE_ID)
SELECT #TABLE_NAME,INSERTED.ID
FROM INSERTED
END'
EXEC(#Trigger)
FETCH NEXT FROM CURS INTO #TABLE_NAME
END
CLOSE CURS
DEALLOCATE CURS
Personally, I dislike the use of triggers but there is no doubt they are easy to implement. I don't know the scale of your system, but the best scenario is that the service which makes the entries will also include a new column in each table to keep changes or just state the date of execution per entry.

Related

create a table with table and database statistics

There are around 200+ tables in our SQL DB and every table has 1 common field [updated_timestamp]
Is there a way to query the DB itself and list all tables, with the MAX value of [updated_timestamp] and the row count of each table?
i'm sorry if i've not explained that the best
are there secret/system tables that hold such info?
my desired output would be
table
updated_timestamp
row_count
Table A
2022-08-22
89,854
Table B
2022-08-18
103,55,166
if there is a table like this i could intergate that would be great, but i'm assuming not that simple.
i picked up some code from another stored procedure and was hoping this would be of use for the row count at least
SELECT
QUOTENAME(SCHEMA_NAME(sOBJ.schema_id)) AS [DB_Schema],
QUOTENAME(sOBJ.name) AS [TableName],
SUM(sPTN.Rows) AS [Row_Count]
INTO ##tmpRowCount2
FROM
sys.objects AS sOBJ
INNER JOIN sys.partitions AS sPTN
ON sOBJ.object_id = sPTN.object_id
WHERE
sOBJ.type = 'U'
AND sOBJ.is_ms_shipped = 0x0
AND index_id < 2
GROUP BY
sOBJ.schema_id
, sOBJ.name
ORDER BY [Row_Count]
GO
ALTER TABLE ##tmpRowCount2 ADD updated_timestamp datetime NULL;
-- keep only API rows
DELETE FROM ##tmpRowCount2
WHERE [DB_Schema] != '[api]'
DECLARE #Row_Count int
DECLARE #sql nvarchar(max)
DECLARE #TableName as VARCHAR(256)
DECLARE #DB_Schema as VARCHAR(256)
DECLARE #updated_timestamp as DATETIME
DECLARE tablenamefromcursor CURSOR FOR
SELECT TableName,
Row_Count,
DB_Schema
FROM ##tmpRowCount2
OPEN tablenamefromcursor
FETCH NEXT FROM tablenamefromcursor INTO #TableName, #Row_Count, #DB_Schema
WHILE ##FETCH_STATUS = 0
BEGIN
SET #sql = 'UPDATE ##tmpRowCount2 SET updated_timestamp = ' +
'(SELECT MAX([updated_timestamp]) FROM ' + #DB_Schema + '.' + #TableName +
') WHERE TableName = ''' + #TableName + ''''
EXEC(#sql)
FETCH NEXT FROM tablenamefromcursor INTO #TableName, #Row_Count, #DB_Schema
END
CLOSE tablenamefromcursor
DEALLOCATE tablenamefromcursor
Code editied above and i can confirm it works, by debugging with select #sql iwas able to parse the statement and edit the syntax unitl it worked by getting the parethesis and quotes in the right order

How to get a list of all changed tables from SQL Server Change Tracking

How to get a list of all tables (which already have Change Tracking enabled) which have any tracked changes after given version?
This will return a list of all the tables that have changed since the previous tracking version:
set nocount on;
-- We want to check for changes since the previous version
--declare #prevTrackingVersion int = INSERT_YOUR_PREV_VERSION_HERE
-- Comment out this line if you know the previous version
declare #prevTrackingVersion int = CHANGE_TRACKING_CURRENT_VERSION() - 1
-- Get a list of table with change tracking enabled
declare #trackedTables as table (name nvarchar(1000));
insert into #trackedTables (name)
select sys.tables.name from sys.change_tracking_tables
join sys.tables ON tables.object_id = change_tracking_tables.object_id
-- This will be the list of tables with changes
declare #changedTables as table (name nvarchar(1000));
-- For each table name in tracked tables
declare #tableName nvarchar(1000)
while exists(select top 1 * from #trackedTables)
begin
-- Set the current table name
set #tableName = (select top 1 name from #trackedTables order by name asc);
-- Determine if the table has changed since the previous version
declare #sql nvarchar(250)
declare #retVal int
set #sql = 'select #retVal = count(*) from changetable(changes ' + #tableName + ', ' + cast(#prevTrackingVersion as varchar) + ') as changedTable'
exec sp_executesql #sql, N'#retVal int output', #retVal output
if #retval > 0
begin
insert into #changedTables (name) select #tableName
end
-- Delete the current table name
delete from #trackedTables where name = #tableName;
end
select * from #changedTables;
Well to get a list of all tables that have change tracking enabled you would perform a query like
SELECT sys.tables.name FROM sys.change_tracking_tables
JOIN sys.tables ON tables.object_id = change_tracking_tables.object_id
Then you can add a where condition for the version if you'd like to. I believe that answers your question.
Also if you'd like to see some info on the change you can run a query like the one below for a specific table using the changetable function.
DECLARE #synchronization_version NVARCHAR(MAX),#last_synchronization_version NVARCHAR(MAX)
SET #synchronization_version = CHANGE_TRACKING_CURRENT_VERSION();
SELECT
CT.*
FROM
CHANGETABLE(CHANGES Sales.CreditCard, #last_synchronization_version) AS CT
UPDATE
I updated the original query to perform a look and print the results, you'l be able to review the tables before you exec the query since you have over 1000 tables per your comment you might want to remove some.
SET NOCOUNT ON;
DECLARE #Views as TABLE (name nvarchar(200));
INSERT INTO #Views (name)
SELECT sys.tables.name FROM sys.change_tracking_tables
JOIN sys.tables ON tables.object_id = change_tracking_tables.object_id
DECLARE #viewName nvarchar(200) = (select top 1 name from #Views);
DECLARE #sql nvarchar(max) = '';
DECLARE #union NVARCHAR(20)
DECLARE #sql1 NVARCHAR(max)
SET #sql1 = 'DECLARE #synchronization_version NVARCHAR(MAX),#last_synchronization_versionNVARCHAR(MAX)
SET #synchronization_version = CHANGE_TRACKING_CURRENT_VERSION();'
PRINT(#sql1)
WHILE(Exists(select 1 from #Views)) BEGIN
SET #union = '';
SET #sql = '
SELECT
CT.*
FROM
CHANGETABLE(CHANGES ' + #ViewName +', #last_synchronization_version) AS CT'
IF (SELECT COUNT(name) FROM #Views) > 2
BEGIN
SET #union = ' UNION'
END
Print (#sql+#union);
DELETE FROM #Views where name = #viewName;
SET #ViewName = (select top 1 name from #Views);
END;

Bulk modification of unique ids in SQL Server

[This is a bit of an unusual problem, I know...]
What I need is a script that will change every unique id value to new one in our database. The problem is that we have configuration tables that can be exported between instances of our software which is id-sensitive (clobbering existing ids). Years ago, we set up a "wide-enough" id gap between our development "standard configuration" and our client's instances, which is now not wide enough :( - e.g. we're getting id conflicts when clients import our standard configuration.
A SQL script to do the following is definitely the simplest/shortest-timeframe thing that we can do. e.g. fixing the code is far too complicated and error prone to consider. Note that we are not "eliminating" the problem here. Just changing the gap from 1000's to 1000000's or more (the existing gap took 5 years to fill).
I believe the simplest solution would be to:
change all our tables to UPDATE_CASCADE (none of them are - this will greatly simplify the script)
create an identity table with the new lowest id that we want
For each table, modify the id to the next one in the identity table (using identity insert modifier flags where necessary). Perhaps after each table is processed, we could reset the identity table.
turn off UPDATE_CASCADE, and delete the identity table.
I am seeking any (partial or full) scripts for this.
Unfortunately UPDATE_CASCADE doesn't exist in the world of Sql Server. I suggest for each table you to re-key you do the following (Pseudo Code)
BACKUP DATABASE
CHECK BACKUP WORKS!
FOR EACH TABLE TO BE RE-KEYED
DROP ALL FOREIGN KEY CONSTRAINTS, INDEXES ETC FROM TABLE
SELECT ID + Number, ALL_OTHER_FIELDS INTO TEMP_TABLE FROM TABLE
RENAME TABLE OLD_TABLE
RENAME TEMP_TABLE TABLE
FOR ALL TABLES REFERENCING THIS TABLE
UPDATE FOREIGN_KEY_TABLE SET FK_ID = FK_ID + new number
END FOR
RE-APPLY FOREIGN KEY CONSTRAINTS, INDEXES ETC FROM TABLE
END FOR
Check it all still works ...
This process could be automated through DMO/SMO objects, but depending on the number of tables involved I'd say using management studio to generate scripts that can then be edited is probably quicker. After all, you only need to do this once/5 years.
Here we go with the code for SQL 2005. It's huge, it's hacky, but it will work (except in the case where you have a primary key that is a composite of two other primary keys).
If someone can re-write this with MrTelly's faster id addition (which wouldn't require building sql from a cursor for each updated row), then I'll mark that as the accepted answer. (If I don't notice the new answer, upvote this - then I'll notice :))
BEGIN TRAN
SET NOCOUNT ON;
DECLARE #newLowId INT
SET #newLowId = 1000000
DECLARE #sql VARCHAR(4000)
--**** SELECT ALL TABLES WITH IDENTITY COLUMNS ****
DECLARE tables SCROLL CURSOR
FOR
SELECT '[' + SCHEMA_NAME(schema_id) + '].[' + t.name + ']', c.name
FROM sys.identity_columns c
INNER JOIN sys.objects t
on c.object_id = t.object_id
WHERE t.type_Desc = 'USER_TABLE'
OPEN tables
DECLARE #Table VARCHAR(100)
DECLARE #IdColumn VARCHAR(100)
CREATE Table #IdTable(
id INT IDENTITY(1,1),
s CHAR(1)
)
FETCH FIRST FROM tables
INTO #Table, #IdColumn
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT('
****************** '+#Table+' ******************
')
--Reset the idtable to the 'low' id mark - remove this line if you want all records to have distinct ids across the database
DELETE FROM #IdTable
DBCC CHECKIDENT('#IdTable', RESEED, #newLowId)
--**** GENERATE COLUMN SQL (for inserts and deletes - updating identities is not allowed) ****
DECLARE tableColumns CURSOR FOR
SELECT column_name FROM information_schema.columns
WHERE '[' + table_schema + '].[' + table_name + ']' = #Table
AND column_name <> #IdColumn
OPEN tableColumns
DECLARE #columnName VARCHAR(100)
DECLARE #columns VARCHAR(4000)
SET #columns = ''
FETCH NEXT FROM tableColumns INTO #columnName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #columns = #columns + #columnName
FETCH NEXT FROM tableColumns INTO #columnName
IF ##FETCH_STATUS = 0 SET #columns = #columns + ', '
END
CLOSE tableColumns
DEALLOCATE tableColumns
--**** GENERATE FOREIGN ROW UPDATE SQL ****
DECLARE foreignkeys SCROLL CURSOR
FOR
SELECT con.name,
'[' + SCHEMA_NAME(f.schema_id) + '].[' + f.name + ']' fTable, fc.column_name ,
'[' + SCHEMA_NAME(p.schema_id) + '].[' + p.name + ']' pTable, pc.column_name
FROM sys.foreign_keys con
INNER JOIN sysforeignkeys syscon
ON con.object_id = syscon.constid
INNER JOIN sys.objects f
ON con.parent_object_id = f.object_id
INNER JOIN information_schema.columns fc
ON fc.table_schema = SCHEMA_NAME(f.schema_id)
AND fc.table_name = f.name
AND fc.ordinal_position = syscon.fkey
INNER JOIN sys.objects p
ON con.referenced_object_id = p.object_id
INNER JOIN information_schema.columns pc
ON pc.table_schema = SCHEMA_NAME(p.schema_id)
AND pc.table_name = p.name
AND pc.ordinal_position = syscon.rkey
WHERE '[' + SCHEMA_NAME(p.schema_id) + '].[' + p.name + ']' = #Table
OPEN foreignkeys
DECLARE #FKeyName VARCHAR(100)
DECLARE #FTable VARCHAR(100)
DECLARE #FColumn VARCHAR(100)
DECLARE #PTable VARCHAR(100)
DECLARE #PColumn VARCHAR(100)
--**** RE-WRITE ALL IDS IN THE TABLE ****
SET #sql='DECLARE tablerows CURSOR FOR
SELECT CAST('+#IdColumn+' AS VARCHAR) FROM '+#Table+' ORDER BY '+#IdColumn
PRINT(#sql)
exec(#sql)
OPEN tablerows
DECLARE #rowid VARCHAR(100)
DECLARE #id VARCHAR(100)
FETCH NEXT FROM tablerows INTO #rowid
WHILE ##FETCH_STATUS = 0
BEGIN
--generate new id
INSERT INTO #IdTable VALUES ('')
SELECT #id = CAST(##IDENTITY AS VARCHAR)
IF #rowId <> #Id
BEGIN
PRINT('Modifying '+#Table+': changing '+#rowId+' to '+#id)
SET #sql='SET IDENTITY_INSERT ' + #Table + ' ON
INSERT INTO '+#Table+' ('+#IdColumn+','+#columns+') SELECT '+#id+','+#columns+' FROM '+#Table+' WHERE '+#IdColumn+'='+#rowId
--Updating all foreign rows...
FETCH FIRST FROM foreignkeys
INTO #FKeyName, #FTable, #FColumn, #PTable, #PColumn
WHILE ##FETCH_STATUS = 0
BEGIN
SET #sql = #sql + '
UPDATE '+#FTable+' SET '+#FColumn+'='+#id+' WHERE '+#FColumn+' ='+#rowId
FETCH NEXT FROM foreignkeys
INTO #FKeyName, #FTable, #FColumn, #PTable, #PColumn
END
SET #sql=#sql + '
DELETE FROM '+#Table+' WHERE '+#IdColumn+'='+#rowId
PRINT(#sql)
exec(#sql)
END
FETCH NEXT FROM tablerows INTO #rowid
END
CLOSE tablerows
DEALLOCATE tablerows
CLOSE foreignkeys
DEALLOCATE foreignkeys
--Revert to normal identity operation - update the identity to the latest id...
DBCC CHECKIDENT(#Table, RESEED, ##IDENTITY)
SET #sql='SET IDENTITY_INSERT ' + #Table + ' OFF'
PRINT(#sql)
exec(#sql)
FETCH NEXT FROM tables
INTO #Table, #IdColumn
END
CLOSE tables
DEALLOCATE tables
DROP TABLE #IdTable
--COMMIT
--ROLLBACK
Why don't you use negative numbers for your standard configuration values and continue to use positive numbers for other things?

Cannot truncate table because it is being referenced by a FOREIGN KEY constraint?

Using MSSQL2005, can I truncate a table with a foreign key constraint if I first truncate the child table (the table with the primary key of the FK relationship)?
I know that I can either
Use a DELETE without a where clause and then RESEED the identity (or)
Remove the FK, truncate the table, and recreate the FK.
I thought that as long as I truncated the child table before the parent, I'd be okay without doing either of the options above, but I'm getting this error:
Cannot truncate table 'TableName' because it is being referenced by a FOREIGN KEY constraint.
DELETE FROM TABLENAME
DBCC CHECKIDENT ('DATABASENAME.dbo.TABLENAME', RESEED, 0)
Note that this isn't probably what you'd want if you have millions+ of records, as it's very slow.
Correct; you cannot truncate a table which has an FK constraint on it.
Typically my process for this is:
Drop the constraints
Trunc the table
Recreate the constraints.
(All in a transaction, of course.)
Of course, this only applies if the child has already been truncated. Otherwise I go a different route, dependent entirely on what my data looks like. (Too many variables to get into here.)
The original poster determined WHY this is the case; see this answer for more details.
Because TRUNCATE TABLE is a DDL command, it cannot check to see whether the records in the table are being referenced by a record in the child table.
This is why DELETE works and TRUNCATE TABLE doesn't: because the database is able to make sure that it isn't being referenced by another record.
Without ALTER TABLE
-- Delete all records
DELETE FROM [TableName]
-- Set current ID to "1"
-- If table already contains data, use "0"
-- If table is empty and never insert data, use "1"
-- Use SP https://github.com/reduardo7/TableTruncate
DBCC CHECKIDENT ([TableName], RESEED, 0)
As Stored Procedure
https://github.com/reduardo7/TableTruncate
Note that this isn't probably what you'd want if you have millions+ of records, as it's very slow.
The solution #denver_citizen provided above did not work for me, but I liked the spirit of it so I modified a few things :
made it a stored procedure
changed the way the foreign keys are populated and recreated
the original script truncates all referenced tables, this can cause foreign key violation error when the referenced table has other foreign key references. This script truncates only the table specified as parameter. It is up to the user, to call this stored procedure multiple times on all tables in the correct order
For the benefit of the public here is the updated script :
CREATE PROCEDURE [dbo].[truncate_non_empty_table]
#TableToTruncate VARCHAR(64)
AS
BEGIN
SET NOCOUNT ON
-- GLOBAL VARIABLES
DECLARE #i int
DECLARE #Debug bit
DECLARE #Recycle bit
DECLARE #Verbose bit
DECLARE #TableName varchar(80)
DECLARE #ColumnName varchar(80)
DECLARE #ReferencedTableName varchar(80)
DECLARE #ReferencedColumnName varchar(80)
DECLARE #ConstraintName varchar(250)
DECLARE #CreateStatement varchar(max)
DECLARE #DropStatement varchar(max)
DECLARE #TruncateStatement varchar(max)
DECLARE #CreateStatementTemp varchar(max)
DECLARE #DropStatementTemp varchar(max)
DECLARE #TruncateStatementTemp varchar(max)
DECLARE #Statement varchar(max)
-- 1 = Will not execute statements
SET #Debug = 0
-- 0 = Will not create or truncate storage table
-- 1 = Will create or truncate storage table
SET #Recycle = 0
-- 1 = Will print a message on every step
set #Verbose = 1
SET #i = 1
SET #CreateStatement = 'ALTER TABLE [dbo].[<tablename>] WITH NOCHECK ADD CONSTRAINT [<constraintname>] FOREIGN KEY([<column>]) REFERENCES [dbo].[<reftable>] ([<refcolumn>])'
SET #DropStatement = 'ALTER TABLE [dbo].[<tablename>] DROP CONSTRAINT [<constraintname>]'
SET #TruncateStatement = 'TRUNCATE TABLE [<tablename>]'
-- Drop Temporary tables
IF OBJECT_ID('tempdb..#FKs') IS NOT NULL
DROP TABLE #FKs
-- GET FKs
SELECT ROW_NUMBER() OVER (ORDER BY OBJECT_NAME(parent_object_id), clm1.name) as ID,
OBJECT_NAME(constraint_object_id) as ConstraintName,
OBJECT_NAME(parent_object_id) as TableName,
clm1.name as ColumnName,
OBJECT_NAME(referenced_object_id) as ReferencedTableName,
clm2.name as ReferencedColumnName
INTO #FKs
FROM sys.foreign_key_columns fk
JOIN sys.columns clm1
ON fk.parent_column_id = clm1.column_id
AND fk.parent_object_id = clm1.object_id
JOIN sys.columns clm2
ON fk.referenced_column_id = clm2.column_id
AND fk.referenced_object_id= clm2.object_id
--WHERE OBJECT_NAME(parent_object_id) not in ('//tables that you do not wont to be truncated')
WHERE OBJECT_NAME(referenced_object_id) = #TableToTruncate
ORDER BY OBJECT_NAME(parent_object_id)
-- Prepare Storage Table
IF Not EXISTS(SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'Internal_FK_Definition_Storage')
BEGIN
IF #Verbose = 1
PRINT '1. Creating Process Specific Tables...'
-- CREATE STORAGE TABLE IF IT DOES NOT EXISTS
CREATE TABLE [Internal_FK_Definition_Storage]
(
ID int not null identity(1,1) primary key,
FK_Name varchar(250) not null,
FK_CreationStatement varchar(max) not null,
FK_DestructionStatement varchar(max) not null,
Table_TruncationStatement varchar(max) not null
)
END
ELSE
BEGIN
IF #Recycle = 0
BEGIN
IF #Verbose = 1
PRINT '1. Truncating Process Specific Tables...'
-- TRUNCATE TABLE IF IT ALREADY EXISTS
TRUNCATE TABLE [Internal_FK_Definition_Storage]
END
ELSE
PRINT '1. Process specific table will be recycled from previous execution...'
END
IF #Recycle = 0
BEGIN
IF #Verbose = 1
PRINT '2. Backing up Foreign Key Definitions...'
-- Fetch and persist FKs
WHILE (#i <= (SELECT MAX(ID) FROM #FKs))
BEGIN
SET #ConstraintName = (SELECT ConstraintName FROM #FKs WHERE ID = #i)
SET #TableName = (SELECT TableName FROM #FKs WHERE ID = #i)
SET #ColumnName = (SELECT ColumnName FROM #FKs WHERE ID = #i)
SET #ReferencedTableName = (SELECT ReferencedTableName FROM #FKs WHERE ID = #i)
SET #ReferencedColumnName = (SELECT ReferencedColumnName FROM #FKs WHERE ID = #i)
SET #DropStatementTemp = REPLACE(REPLACE(#DropStatement,'<tablename>',#TableName),'<constraintname>',#ConstraintName)
SET #CreateStatementTemp = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(#CreateStatement,'<tablename>',#TableName),'<column>',#ColumnName),'<constraintname>',#ConstraintName),'<reftable>',#ReferencedTableName),'<refcolumn>',#ReferencedColumnName)
SET #TruncateStatementTemp = REPLACE(#TruncateStatement,'<tablename>',#TableName)
INSERT INTO [Internal_FK_Definition_Storage]
SELECT #ConstraintName, #CreateStatementTemp, #DropStatementTemp, #TruncateStatementTemp
SET #i = #i + 1
IF #Verbose = 1
PRINT ' > Backing up [' + #ConstraintName + '] from [' + #TableName + ']'
END
END
ELSE
PRINT '2. Backup up was recycled from previous execution...'
IF #Verbose = 1
PRINT '3. Dropping Foreign Keys...'
-- DROP FOREING KEYS
SET #i = 1
WHILE (#i <= (SELECT MAX(ID) FROM [Internal_FK_Definition_Storage]))
BEGIN
SET #ConstraintName = (SELECT FK_Name FROM [Internal_FK_Definition_Storage] WHERE ID = #i)
SET #Statement = (SELECT FK_DestructionStatement FROM [Internal_FK_Definition_Storage] WITH (NOLOCK) WHERE ID = #i)
IF #Debug = 1
PRINT #Statement
ELSE
EXEC(#Statement)
SET #i = #i + 1
IF #Verbose = 1
PRINT ' > Dropping [' + #ConstraintName + ']'
END
IF #Verbose = 1
PRINT '4. Truncating Tables...'
-- TRUNCATE TABLES
-- SzP: commented out as the tables to be truncated might also contain tables that has foreign keys
-- to resolve this the stored procedure should be called recursively, but I dont have the time to do it...
/*
SET #i = 1
WHILE (#i <= (SELECT MAX(ID) FROM [Internal_FK_Definition_Storage]))
BEGIN
SET #Statement = (SELECT Table_TruncationStatement FROM [Internal_FK_Definition_Storage] WHERE ID = #i)
IF #Debug = 1
PRINT #Statement
ELSE
EXEC(#Statement)
SET #i = #i + 1
IF #Verbose = 1
PRINT ' > ' + #Statement
END
*/
IF #Verbose = 1
PRINT ' > TRUNCATE TABLE [' + #TableToTruncate + ']'
IF #Debug = 1
PRINT 'TRUNCATE TABLE [' + #TableToTruncate + ']'
ELSE
EXEC('TRUNCATE TABLE [' + #TableToTruncate + ']')
IF #Verbose = 1
PRINT '5. Re-creating Foreign Keys...'
-- CREATE FOREING KEYS
SET #i = 1
WHILE (#i <= (SELECT MAX(ID) FROM [Internal_FK_Definition_Storage]))
BEGIN
SET #ConstraintName = (SELECT FK_Name FROM [Internal_FK_Definition_Storage] WHERE ID = #i)
SET #Statement = (SELECT FK_CreationStatement FROM [Internal_FK_Definition_Storage] WHERE ID = #i)
IF #Debug = 1
PRINT #Statement
ELSE
EXEC(#Statement)
SET #i = #i + 1
IF #Verbose = 1
PRINT ' > Re-creating [' + #ConstraintName + ']'
END
IF #Verbose = 1
PRINT '6. Process Completed'
END
use the following command after deletion of all rows in that table by using delete statement
delete from tablename
DBCC CHECKIDENT ('tablename', RESEED, 0)
EDIT: Corrected syntax for SQL Server
Well, since I did not find examples of the very simple solution I used, which is:
Drop foreign key;
Truncate table
Recreate foreign key
Here it goes:
1) Find the foreign key name that is causing the failure (for example: FK_PROBLEM_REASON, with field ID, from table TABLE_OWNING_CONSTRAINT)
2) Remove that key from the table:
ALTER TABLE TABLE_OWNING_CONSTRAINT DROP CONSTRAINT FK_PROBLEM_REASON
3) Truncate wanted table
TRUNCATE TABLE TABLE_TO_TRUNCATE
4) Re-add the key to that first table:
ALTER TABLE TABLE_OWNING_CONSTRAINT ADD CONSTRAINT FK_PROBLEM_REASON FOREIGN KEY(ID) REFERENCES TABLE_TO_TRUNCATE (ID)
That's it.
The process is removing foreign key constraint and truncate table
then add constrain by following steps.
This is Just for MySQL
SET FOREIGN_KEY_CHECKS = 0;
truncate table "yourTableName";
SET FOREIGN_KEY_CHECKS = 1;
you can follow this step,
By reseeding table you can delete the data of the table.
delete from table_name
dbcc checkident('table_name',reseed,0)
if some error comes then you have to reseed the primary table.
Here is a script I wrote in order to automate the process. I hope it helps.
SET NOCOUNT ON
-- GLOBAL VARIABLES
DECLARE #i int
DECLARE #Debug bit
DECLARE #Recycle bit
DECLARE #Verbose bit
DECLARE #TableName varchar(80)
DECLARE #ColumnName varchar(80)
DECLARE #ReferencedTableName varchar(80)
DECLARE #ReferencedColumnName varchar(80)
DECLARE #ConstraintName varchar(250)
DECLARE #CreateStatement varchar(max)
DECLARE #DropStatement varchar(max)
DECLARE #TruncateStatement varchar(max)
DECLARE #CreateStatementTemp varchar(max)
DECLARE #DropStatementTemp varchar(max)
DECLARE #TruncateStatementTemp varchar(max)
DECLARE #Statement varchar(max)
-- 1 = Will not execute statements
SET #Debug = 0
-- 0 = Will not create or truncate storage table
-- 1 = Will create or truncate storage table
SET #Recycle = 0
-- 1 = Will print a message on every step
set #Verbose = 1
SET #i = 1
SET #CreateStatement = 'ALTER TABLE [dbo].[<tablename>] WITH NOCHECK ADD CONSTRAINT [<constraintname>] FOREIGN KEY([<column>]) REFERENCES [dbo].[<reftable>] ([<refcolumn>])'
SET #DropStatement = 'ALTER TABLE [dbo].[<tablename>] DROP CONSTRAINT [<constraintname>]'
SET #TruncateStatement = 'TRUNCATE TABLE [<tablename>]'
-- Drop Temporary tables
DROP TABLE #FKs
-- GET FKs
SELECT ROW_NUMBER() OVER (ORDER BY OBJECT_NAME(parent_object_id), clm1.name) as ID,
OBJECT_NAME(constraint_object_id) as ConstraintName,
OBJECT_NAME(parent_object_id) as TableName,
clm1.name as ColumnName,
OBJECT_NAME(referenced_object_id) as ReferencedTableName,
clm2.name as ReferencedColumnName
INTO #FKs
FROM sys.foreign_key_columns fk
JOIN sys.columns clm1
ON fk.parent_column_id = clm1.column_id
AND fk.parent_object_id = clm1.object_id
JOIN sys.columns clm2
ON fk.referenced_column_id = clm2.column_id
AND fk.referenced_object_id= clm2.object_id
WHERE OBJECT_NAME(parent_object_id) not in ('//tables that you do not wont to be truncated')
ORDER BY OBJECT_NAME(parent_object_id)
-- Prepare Storage Table
IF Not EXISTS(SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'Internal_FK_Definition_Storage')
BEGIN
IF #Verbose = 1
PRINT '1. Creating Process Specific Tables...'
-- CREATE STORAGE TABLE IF IT DOES NOT EXISTS
CREATE TABLE [Internal_FK_Definition_Storage]
(
ID int not null identity(1,1) primary key,
FK_Name varchar(250) not null,
FK_CreationStatement varchar(max) not null,
FK_DestructionStatement varchar(max) not null,
Table_TruncationStatement varchar(max) not null
)
END
ELSE
BEGIN
IF #Recycle = 0
BEGIN
IF #Verbose = 1
PRINT '1. Truncating Process Specific Tables...'
-- TRUNCATE TABLE IF IT ALREADY EXISTS
TRUNCATE TABLE [Internal_FK_Definition_Storage]
END
ELSE
PRINT '1. Process specific table will be recycled from previous execution...'
END
IF #Recycle = 0
BEGIN
IF #Verbose = 1
PRINT '2. Backing up Foreign Key Definitions...'
-- Fetch and persist FKs
WHILE (#i <= (SELECT MAX(ID) FROM #FKs))
BEGIN
SET #ConstraintName = (SELECT ConstraintName FROM #FKs WHERE ID = #i)
SET #TableName = (SELECT TableName FROM #FKs WHERE ID = #i)
SET #ColumnName = (SELECT ColumnName FROM #FKs WHERE ID = #i)
SET #ReferencedTableName = (SELECT ReferencedTableName FROM #FKs WHERE ID = #i)
SET #ReferencedColumnName = (SELECT ReferencedColumnName FROM #FKs WHERE ID = #i)
SET #DropStatementTemp = REPLACE(REPLACE(#DropStatement,'<tablename>',#TableName),'<constraintname>',#ConstraintName)
SET #CreateStatementTemp = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(#CreateStatement,'<tablename>',#TableName),'<column>',#ColumnName),'<constraintname>',#ConstraintName),'<reftable>',#ReferencedTableName),'<refcolumn>',#ReferencedColumnName)
SET #TruncateStatementTemp = REPLACE(#TruncateStatement,'<tablename>',#TableName)
INSERT INTO [Internal_FK_Definition_Storage]
SELECT #ConstraintName, #CreateStatementTemp, #DropStatementTemp, #TruncateStatementTemp
SET #i = #i + 1
IF #Verbose = 1
PRINT ' > Backing up [' + #ConstraintName + '] from [' + #TableName + ']'
END
END
ELSE
PRINT '2. Backup up was recycled from previous execution...'
IF #Verbose = 1
PRINT '3. Dropping Foreign Keys...'
-- DROP FOREING KEYS
SET #i = 1
WHILE (#i <= (SELECT MAX(ID) FROM [Internal_FK_Definition_Storage]))
BEGIN
SET #ConstraintName = (SELECT FK_Name FROM [Internal_FK_Definition_Storage] WHERE ID = #i)
SET #Statement = (SELECT FK_DestructionStatement FROM [Internal_FK_Definition_Storage] WITH (NOLOCK) WHERE ID = #i)
IF #Debug = 1
PRINT #Statement
ELSE
EXEC(#Statement)
SET #i = #i + 1
IF #Verbose = 1
PRINT ' > Dropping [' + #ConstraintName + ']'
END
IF #Verbose = 1
PRINT '4. Truncating Tables...'
-- TRUNCATE TABLES
SET #i = 1
WHILE (#i <= (SELECT MAX(ID) FROM [Internal_FK_Definition_Storage]))
BEGIN
SET #Statement = (SELECT Table_TruncationStatement FROM [Internal_FK_Definition_Storage] WHERE ID = #i)
IF #Debug = 1
PRINT #Statement
ELSE
EXEC(#Statement)
SET #i = #i + 1
IF #Verbose = 1
PRINT ' > ' + #Statement
END
IF #Verbose = 1
PRINT '5. Re-creating Foreign Keys...'
-- CREATE FOREING KEYS
SET #i = 1
WHILE (#i <= (SELECT MAX(ID) FROM [Internal_FK_Definition_Storage]))
BEGIN
SET #ConstraintName = (SELECT FK_Name FROM [Internal_FK_Definition_Storage] WHERE ID = #i)
SET #Statement = (SELECT FK_CreationStatement FROM [Internal_FK_Definition_Storage] WHERE ID = #i)
IF #Debug = 1
PRINT #Statement
ELSE
EXEC(#Statement)
SET #i = #i + 1
IF #Verbose = 1
PRINT ' > Re-creating [' + #ConstraintName + ']'
END
IF #Verbose = 1
PRINT '6. Process Completed'
#denver_citizen and #Peter Szanto's answers didn't quite work for me, but I modified them to account for:
Composite Keys
On Delete and On Update actions
Checking the index when re-adding
Schemas other than dbo
Multiple tables at once
DECLARE #Debug bit = 0;
-- List of tables to truncate
select
SchemaName, Name
into #tables
from (values
('schema', 'table')
,('schema2', 'table2')
) as X(SchemaName, Name)
BEGIN TRANSACTION TruncateTrans;
with foreignKeys AS (
SELECT
SCHEMA_NAME(fk.schema_id) as SchemaName
,fk.Name as ConstraintName
,OBJECT_NAME(fk.parent_object_id) as TableName
,SCHEMA_NAME(t.SCHEMA_ID) as ReferencedSchemaName
,OBJECT_NAME(fk.referenced_object_id) as ReferencedTableName
,fc.constraint_column_id
,COL_NAME(fk.parent_object_id, fc.parent_column_id) AS ColumnName
,COL_NAME(fk.referenced_object_id, fc.referenced_column_id) as ReferencedColumnName
,fk.delete_referential_action_desc
,fk.update_referential_action_desc
FROM sys.foreign_keys AS fk
JOIN sys.foreign_key_columns AS fc
ON fk.object_id = fc.constraint_object_id
JOIN #tables tbl
ON OBJECT_NAME(fc.referenced_object_id) = tbl.Name
JOIN sys.tables t on OBJECT_NAME(t.object_id) = tbl.Name
and SCHEMA_NAME(t.schema_id) = tbl.SchemaName
and t.OBJECT_ID = fc.referenced_object_id
)
select
quotename(fk.ConstraintName) AS ConstraintName
,quotename(fk.SchemaName) + '.' + quotename(fk.TableName) AS TableName
,quotename(fk.ReferencedSchemaName) + '.' + quotename(fk.ReferencedTableName) AS ReferencedTableName
,replace(fk.delete_referential_action_desc, '_', ' ') AS DeleteAction
,replace(fk.update_referential_action_desc, '_', ' ') AS UpdateAction
,STUFF((
SELECT ',' + quotename(fk2.ColumnName)
FROM foreignKeys fk2
WHERE fk2.ConstraintName = fk.ConstraintName and fk2.SchemaName = fk.SchemaName
ORDER BY fk2.constraint_column_id
FOR XML PATH('')
),1,1,'') AS ColumnNames
,STUFF((
SELECT ',' + quotename(fk2.ReferencedColumnName)
FROM foreignKeys fk2
WHERE fk2.ConstraintName = fk.ConstraintName and fk2.SchemaName = fk.SchemaName
ORDER BY fk2.constraint_column_id
FOR XML PATH('')
),1,1,'') AS ReferencedColumnNames
into #FKs
from foreignKeys fk
GROUP BY fk.SchemaName, fk.ConstraintName, fk.TableName, fk.ReferencedSchemaName, fk.ReferencedTableName, fk.delete_referential_action_desc, fk.update_referential_action_desc
-- Drop FKs
select
identity(int,1,1) as ID,
'ALTER TABLE ' + fk.TableName + ' DROP CONSTRAINT ' + fk.ConstraintName AS script
into #scripts
from #FKs fk
-- Truncate
insert into #scripts
select distinct
'TRUNCATE TABLE ' + quotename(tbl.SchemaName) + '.' + quotename(tbl.Name) AS script
from #tables tbl
-- Recreate
insert into #scripts
select
'ALTER TABLE ' + fk.TableName +
' WITH CHECK ADD CONSTRAINT ' + fk.ConstraintName +
' FOREIGN KEY ('+ fk.ColumnNames +')' +
' REFERENCES ' + fk.ReferencedTableName +' ('+ fk.ReferencedColumnNames +')' +
' ON DELETE ' + fk.DeleteAction COLLATE Latin1_General_CI_AS_KS_WS + ' ON UPDATE ' + fk.UpdateAction COLLATE Latin1_General_CI_AS_KS_WS AS script
from #FKs fk
DECLARE #script nvarchar(MAX);
DECLARE curScripts CURSOR FOR
select script
from #scripts
order by ID
OPEN curScripts
WHILE 1=1 BEGIN
FETCH NEXT FROM curScripts INTO #script
IF ##FETCH_STATUS != 0 BREAK;
print #script;
IF #Debug = 0
EXEC (#script);
END
CLOSE curScripts
DEALLOCATE curScripts
drop table #scripts
drop table #FKs
drop table #tables
COMMIT TRANSACTION TruncateTrans;
You cannot truncate a table if you don't drop the constraints. A disable also doesn't work. you need to Drop everything. i've made a script that drop all constrainsts and then recreate then.
Be sure to wrap it in a transaction ;)
SET NOCOUNT ON
GO
DECLARE #table TABLE(
RowId INT PRIMARY KEY IDENTITY(1, 1),
ForeignKeyConstraintName NVARCHAR(200),
ForeignKeyConstraintTableSchema NVARCHAR(200),
ForeignKeyConstraintTableName NVARCHAR(200),
ForeignKeyConstraintColumnName NVARCHAR(200),
PrimaryKeyConstraintName NVARCHAR(200),
PrimaryKeyConstraintTableSchema NVARCHAR(200),
PrimaryKeyConstraintTableName NVARCHAR(200),
PrimaryKeyConstraintColumnName NVARCHAR(200)
)
INSERT INTO #table(ForeignKeyConstraintName, ForeignKeyConstraintTableSchema, ForeignKeyConstraintTableName, ForeignKeyConstraintColumnName)
SELECT
U.CONSTRAINT_NAME,
U.TABLE_SCHEMA,
U.TABLE_NAME,
U.COLUMN_NAME
FROM
INFORMATION_SCHEMA.KEY_COLUMN_USAGE U
INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS C
ON U.CONSTRAINT_NAME = C.CONSTRAINT_NAME
WHERE
C.CONSTRAINT_TYPE = 'FOREIGN KEY'
UPDATE #table SET
PrimaryKeyConstraintName = UNIQUE_CONSTRAINT_NAME
FROM
#table T
INNER JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS R
ON T.ForeignKeyConstraintName = R.CONSTRAINT_NAME
UPDATE #table SET
PrimaryKeyConstraintTableSchema = TABLE_SCHEMA,
PrimaryKeyConstraintTableName = TABLE_NAME
FROM #table T
INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS C
ON T.PrimaryKeyConstraintName = C.CONSTRAINT_NAME
UPDATE #table SET
PrimaryKeyConstraintColumnName = COLUMN_NAME
FROM #table T
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE U
ON T.PrimaryKeyConstraintName = U.CONSTRAINT_NAME
--DROP CONSTRAINT:
DECLARE #dynSQL varchar(MAX);
DECLARE cur CURSOR FOR
SELECT
'
ALTER TABLE [' + ForeignKeyConstraintTableSchema + '].[' + ForeignKeyConstraintTableName + ']
DROP CONSTRAINT ' + ForeignKeyConstraintName + '
'
FROM
#table
OPEN cur
FETCH cur into #dynSQL
WHILE ##FETCH_STATUS = 0
BEGIN
exec(#dynSQL)
print #dynSQL
FETCH cur into #dynSQL
END
CLOSE cur
DEALLOCATE cur
---------------------
--HERE GOES YOUR TRUNCATES!!!!!
--HERE GOES YOUR TRUNCATES!!!!!
--HERE GOES YOUR TRUNCATES!!!!!
truncate table your_table
--HERE GOES YOUR TRUNCATES!!!!!
--HERE GOES YOUR TRUNCATES!!!!!
--HERE GOES YOUR TRUNCATES!!!!!
---------------------
--ADD CONSTRAINT:
DECLARE cur2 CURSOR FOR
SELECT
'
ALTER TABLE [' + ForeignKeyConstraintTableSchema + '].[' + ForeignKeyConstraintTableName + ']
ADD CONSTRAINT ' + ForeignKeyConstraintName + ' FOREIGN KEY(' + ForeignKeyConstraintColumnName + ') REFERENCES [' + PrimaryKeyConstraintTableSchema + '].[' + PrimaryKeyConstraintTableName + '](' + PrimaryKeyConstraintColumnName + ')
'
FROM
#table
OPEN cur2
FETCH cur2 into #dynSQL
WHILE ##FETCH_STATUS = 0
BEGIN
exec(#dynSQL)
print #dynSQL
FETCH cur2 into #dynSQL
END
CLOSE cur2
DEALLOCATE cur2
If I understand correctly, what you want to do is to have a clean environment to be set up for DB involving integration tests.
My approach here would be to drop the whole schema and recreate it later.
Reasons:
You probably already have a "create schema" script. Re-using it for test isolation is easy.
Creating a schema is pretty quick.
With that approach, it is pretty easy to set up your script to have each fixture create a NEW schema (with a temporary name), and then you can start running test-fixtures in parallel, making the slowest part of your test suite much faster.
Found elsewhere on the web
EXEC sp_MSForEachTable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL'
EXEC sp_MSForEachTable 'ALTER TABLE ? DISABLE TRIGGER ALL'
-- EXEC sp_MSForEachTable 'DELETE FROM ?' -- Uncomment to execute
EXEC sp_MSForEachTable 'ALTER TABLE ? CHECK CONSTRAINT ALL'
EXEC sp_MSForEachTable 'ALTER TABLE ? ENABLE TRIGGER ALL'
I write the following ways and tried to parameterized them, so you can Run them in a Query document Or Make a useful SP with them easily.
A) Delete
If your table has not millions of records this works good and hasn't any Alter commands:
---------------------------------------------------------------
------------------- Just Fill Parameters Value ----------------
---------------------------------------------------------------
DECLARE #DbName AS NVARCHAR(30) = 'MyDb' --< Db Name
DECLARE #Schema AS NVARCHAR(30) = 'dbo' --< Schema
DECLARE #TableName AS NVARCHAR(30) = 'Book' --< Table Name
------------------ /Just Fill Parameters Value ----------------
DECLARE #Query AS NVARCHAR(500) = 'Delete FROM ' + #TableName
EXECUTE sp_executesql #Query
SET #Query=#DbName+'.'+#Schema+'.'+#TableName
DBCC CHECKIDENT (#Query,RESEED, 0)
In above answer of mine the method of resolve the mentioned problem in the question is based on #s15199d answer.
B) Truncate
If your table has millions of records or you hasn't any problem with Alter command in your codes, then use this one:
-- Book Student
--
-- | BookId | Field1 | | StudentId | BookId |
-- --------------------- ------------------------
-- | 1 | A | | 2 | 1 |
-- | 2 | B | | 1 | 1 |
-- | 3 | C | | 2 | 3 |
---------------------------------------------------------------
------------------- Just Fill Parameters Value ----------------
---------------------------------------------------------------
DECLARE #DbName AS NVARCHAR(30) = 'MyDb'
DECLARE #Schema AS NVARCHAR(30) = 'dbo'
DECLARE #TableName_ToTruncate AS NVARCHAR(30) = 'Book'
DECLARE #TableName_OfOwnerOfConstraint AS NVARCHAR(30) = 'Student' --< Decelations About FK_Book_Constraint
DECLARE #Ref_ColumnName_In_TableName_ToTruncate AS NVARCHAR(30) = 'BookId' --< Decelations About FK_Book_Constraint
DECLARE #FK_ColumnName_In_TableOfOwnerOfConstraint AS NVARCHAR(30) = 'Fk_BookId' --< Decelations About FK_Book_Constraint
DECLARE #FK_ConstraintName AS NVARCHAR(30) = 'FK_Book_Constraint' --< Decelations About FK_Book_Constraint
------------------ /Just Fill Parameters Value ----------------
DECLARE #Query AS NVARCHAR(2000)
SET #Query= 'ALTER TABLE '+#TableName_OfOwnerOfConstraint+' DROP CONSTRAINT '+#FK_ConstraintName
EXECUTE sp_executesql #Query
SET #Query= 'Truncate Table '+ #TableName_ToTruncate
EXECUTE sp_executesql #Query
SET #Query= 'ALTER TABLE '+#TableName_OfOwnerOfConstraint+' ADD CONSTRAINT '+#FK_ConstraintName+' FOREIGN KEY('+#FK_ColumnName_In_TableOfOwnerOfConstraint+') REFERENCES '+#TableName_ToTruncate+'('+#Ref_ColumnName_In_TableName_ToTruncate+')'
EXECUTE sp_executesql #Query
In above answer of mine the method of resolve the mentioned problem in the question is based on #LauroWolffValenteSobrinho answer.
If you have more than one CONSTRAINT then you should append its codes like me to the above query
Also you can change the above code base #SerjSagan answer to disable an enable the constraint
truncate did not work for me, delete + reseed is the best way out.
In case there are some of you out there who need to iterate over huge number of tables to perform delete + reseed, you might run into issues with some tables which does not have an identity column, the following code checks if identity column exist before attempting to reseed
EXEC ('DELETE FROM [schemaName].[tableName]')
IF EXISTS (Select * from sys.identity_columns where object_name(object_id) = 'tableName')
BEGIN
EXEC ('DBCC CHECKIDENT ([schemaName.tableName], RESEED, 0)')
END
For MS SQL, at least the newer versions, you can just disable the constrains with code like this:
ALTER TABLE Orders
NOCHECK CONSTRAINT [FK_dbo.Orders_dbo.Customers_Customer_Id]
GO
TRUNCATE TABLE Customers
GO
ALTER TABLE Orders
WITH CHECK CHECK CONSTRAINT [FK_dbo.Orders_dbo.Customers_Customer_Id]
GO
The only way is to drop foreign keys before doing the truncate. And after truncating the data, you must re-create the indexes.
The following script generates the required SQL for dropping all foreign key constraints.
DECLARE #drop NVARCHAR(MAX) = N'';
SELECT #drop += N'
ALTER TABLE ' + QUOTENAME(cs.name) + '.' + QUOTENAME(ct.name)
+ ' DROP CONSTRAINT ' + QUOTENAME(fk.name) + ';'
FROM sys.foreign_keys AS fk
INNER JOIN sys.tables AS ct
ON fk.parent_object_id = ct.[object_id]
INNER JOIN sys.schemas AS cs
ON ct.[schema_id] = cs.[schema_id];
SELECT #drop
Next, the following script generates the required SQL for re-creating foreign keys.
DECLARE #create NVARCHAR(MAX) = N'';
SELECT #create += N'
ALTER TABLE '
+ QUOTENAME(cs.name) + '.' + QUOTENAME(ct.name)
+ ' ADD CONSTRAINT ' + QUOTENAME(fk.name)
+ ' FOREIGN KEY (' + STUFF((SELECT ',' + QUOTENAME(c.name)
-- get all the columns in the constraint table
FROM sys.columns AS c
INNER JOIN sys.foreign_key_columns AS fkc
ON fkc.parent_column_id = c.column_id
AND fkc.parent_object_id = c.[object_id]
WHERE fkc.constraint_object_id = fk.[object_id]
ORDER BY fkc.constraint_column_id
FOR XML PATH(N''), TYPE).value(N'.[1]', N'nvarchar(max)'), 1, 1, N'')
+ ') REFERENCES ' + QUOTENAME(rs.name) + '.' + QUOTENAME(rt.name)
+ '(' + STUFF((SELECT ',' + QUOTENAME(c.name)
-- get all the referenced columns
FROM sys.columns AS c
INNER JOIN sys.foreign_key_columns AS fkc
ON fkc.referenced_column_id = c.column_id
AND fkc.referenced_object_id = c.[object_id]
WHERE fkc.constraint_object_id = fk.[object_id]
ORDER BY fkc.constraint_column_id
FOR XML PATH(N''), TYPE).value(N'.[1]', N'nvarchar(max)'), 1, 1, N'') + ');'
FROM sys.foreign_keys AS fk
INNER JOIN sys.tables AS rt -- referenced table
ON fk.referenced_object_id = rt.[object_id]
INNER JOIN sys.schemas AS rs
ON rt.[schema_id] = rs.[schema_id]
INNER JOIN sys.tables AS ct -- constraint table
ON fk.parent_object_id = ct.[object_id]
INNER JOIN sys.schemas AS cs
ON ct.[schema_id] = cs.[schema_id]
WHERE rt.is_ms_shipped = 0 AND ct.is_ms_shipped = 0;
SELECT #create
Run the generated script to drop all foreign keys, truncate tables, and then run the generated script to re-create all foreign keys.
The queries are taken from here.
It's my solution of this issue. I used it for altering PK, but idea the same. Hope this will be useful)
PRINT 'Script starts'
DECLARE #foreign_key_name varchar(255)
DECLARE #keycnt int
DECLARE #foreign_table varchar(255)
DECLARE #foreign_column_1 varchar(255)
DECLARE #foreign_column_2 varchar(255)
DECLARE #primary_table varchar(255)
DECLARE #primary_column_1 varchar(255)
DECLARE #primary_column_2 varchar(255)
DECLARE #TablN varchar(255)
-->> Type the primary table name
SET #TablN = ''
--------------------------------------------------------------------------------------- ------------------------------
--Here will be created the temporary table with all reference FKs
---------------------------------------------------------------------------------------------------------------------
PRINT 'Creating the temporary table'
select cast(f.name as varchar(255)) as foreign_key_name
, r.keycnt
, cast(c.name as varchar(255)) as foreign_table
, cast(fc.name as varchar(255)) as foreign_column_1
, cast(fc2.name as varchar(255)) as foreign_column_2
, cast(p.name as varchar(255)) as primary_table
, cast(rc.name as varchar(255)) as primary_column_1
, cast(rc2.name as varchar(255)) as primary_column_2
into #ConTab
from sysobjects f
inner join sysobjects c on f.parent_obj = c.id
inner join sysreferences r on f.id = r.constid
inner join sysobjects p on r.rkeyid = p.id
inner join syscolumns rc on r.rkeyid = rc.id and r.rkey1 = rc.colid
inner join syscolumns fc on r.fkeyid = fc.id and r.fkey1 = fc.colid
left join syscolumns rc2 on r.rkeyid = rc2.id and r.rkey2 = rc.colid
left join syscolumns fc2 on r.fkeyid = fc2.id and r.fkey2 = fc.colid
where f.type = 'F' and p.name = #TablN
ORDER BY cast(p.name as varchar(255))
---------------------------------------------------------------------------------------------------------------------
--Cursor, below, will drop all reference FKs
---------------------------------------------------------------------------------------------------------------------
DECLARE #CURSOR CURSOR
/*Fill in cursor*/
PRINT 'Cursor 1 starting. All refernce FK will be droped'
SET #CURSOR = CURSOR SCROLL
FOR
select foreign_key_name
, keycnt
, foreign_table
, foreign_column_1
, foreign_column_2
, primary_table
, primary_column_1
, primary_column_2
from #ConTab
OPEN #CURSOR
FETCH NEXT FROM #CURSOR INTO #foreign_key_name, #keycnt, #foreign_table, #foreign_column_1, #foreign_column_2,
#primary_table, #primary_column_1, #primary_column_2
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC ('ALTER TABLE ['+#foreign_table+'] DROP CONSTRAINT ['+#foreign_key_name+']')
FETCH NEXT FROM #CURSOR INTO #foreign_key_name, #keycnt, #foreign_table, #foreign_column_1, #foreign_column_2,
#primary_table, #primary_column_1, #primary_column_2
END
CLOSE #CURSOR
PRINT 'Cursor 1 finished work'
---------------------------------------------------------------------------------------------------------------------
--Here you should provide the chainging script for the primary table
---------------------------------------------------------------------------------------------------------------------
PRINT 'Altering primary table begin'
TRUNCATE TABLE table_name
PRINT 'Altering finished'
---------------------------------------------------------------------------------------------------------------------
--Cursor, below, will add again all reference FKs
--------------------------------------------------------------------------------------------------------------------
PRINT 'Cursor 2 starting. All refernce FK will added'
SET #CURSOR = CURSOR SCROLL
FOR
select foreign_key_name
, keycnt
, foreign_table
, foreign_column_1
, foreign_column_2
, primary_table
, primary_column_1
, primary_column_2
from #ConTab
OPEN #CURSOR
FETCH NEXT FROM #CURSOR INTO #foreign_key_name, #keycnt, #foreign_table, #foreign_column_1, #foreign_column_2,
#primary_table, #primary_column_1, #primary_column_2
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC ('ALTER TABLE [' +#foreign_table+ '] WITH NOCHECK ADD CONSTRAINT [' +#foreign_key_name+ '] FOREIGN KEY(['+#foreign_column_1+'])
REFERENCES [' +#primary_table+'] (['+#primary_column_1+'])')
EXEC ('ALTER TABLE [' +#foreign_table+ '] CHECK CONSTRAINT [' +#foreign_key_name+']')
FETCH NEXT FROM #CURSOR INTO #foreign_key_name, #keycnt, #foreign_table, #foreign_column_1, #foreign_column_2,
#primary_table, #primary_column_1, #primary_column_2
END
CLOSE #CURSOR
PRINT 'Cursor 2 finished work'
---------------------------------------------------------------------------------------------------------------------
PRINT 'Temporary table droping'
drop table #ConTab
PRINT 'Finish'
The following works for me even with FK constraints, and combines the following answers to only drop the specified tables:
Transaction Automatic Rollback
Looping through comma-separated list
Executing Dynamic SQL (with table names from variable)
DELETE and RESEED table (in this thread)
USE [YourDB];
DECLARE #TransactionName varchar(20) = 'stopdropandroll';
BEGIN TRAN #TransactionName;
set xact_abort on; /* automatic rollback https://stackoverflow.com/a/1749788/1037948 */
-- ===== DO WORK // =====
-- dynamic sql placeholder
DECLARE #SQL varchar(300);
-- LOOP: https://stackoverflow.com/a/10031803/1037948
-- list of things to loop
DECLARE #delim char = ';';
DECLARE #foreach varchar(MAX) = 'Table;Names;Separated;By;Delimiter' + #delim + 'AnotherName' + #delim + 'Still Another';
DECLARE #token varchar(MAX);
WHILE len(#foreach) > 0
BEGIN
-- set current loop token
SET #token = left(#foreach, charindex(#delim, #foreach+#delim)-1)
-- ======= DO WORK // ===========
-- dynamic sql (parentheses are required): https://stackoverflow.com/a/989111/1037948
SET #SQL = 'DELETE FROM [' + #token + ']; DBCC CHECKIDENT (''' + #token + ''',RESEED, 0);'; -- https://stackoverflow.com/a/11784890
PRINT #SQL;
EXEC (#SQL);
-- ======= // END WORK ===========
-- continue loop, chopping off token
SET #foreach = stuff(#foreach, 1, charindex(#delim, #foreach+#delim), '')
END
-- ===== // END WORK =====
-- review and commit
SELECT ##TRANCOUNT as TransactionsPerformed, ##ROWCOUNT as LastRowsChanged;
COMMIT TRAN #TransactionName;
Note:
I think it still helps to declare the tables in the order you want them deleted (i.e. kill dependencies first). As seen in this answer, rather than loop specific names you could substitute all tables with
EXEC sp_MSForEachTable 'DELETE FROM ?; DBCC CHECKIDENT (''?'',RESEED, 0);';
If none of these answers worked like in my case do this:
Drop constraints
Set all values to allow nulls
Truncate table
Add constraints that were dropped.
Good luck!
Delete then reset auto-increment:
delete from tablename;
then
ALTER TABLE tablename AUTO_INCREMENT = 1;
In SSMS I had Diagram open showing the Key. After deleting the Key and truncating the file I refreshed then focused back on the Diagram and created an update by clearing then restoring an Identity box. Saving the Diagram brought up a Save dialog box, than a "Changes were made in the database while you where working" dialog box, clicking Yes restored the Key, restoring it from the latched copy in the Diagram.
If you're doing this at any sort of a frequency, heck even on a schedule, I would absolutely, unequivocally never use a DML statement. The cost of writing to the transaction log is just to high, and setting the entire database into SIMPLE recovery mode to truncate one table is ridiculous.
The best way, is unfortunately the hard or laborious way. That being:
Drop constraints
Truncate table
Re-create constraints
My process for doing this involves the following steps:
In SSMS right-click on the table in question, and select View Dependencies
Take note of the tables referenced (if any)
Back in object explorer, expand the Keys node and take note of the foreign keys (if any)
Start scripting (drop / truncate / re-create)
Scripts of this nature should be done within a begin tran and commit tran block.
This is an example for someone that use Entity Framework
Table to be reset: Foo
Another table that depends on: Bar
Constraint Column on table Foo : FooColumn
Constraint Column on table Bar : BarColumn
public override void Down()
{
DropForeignKey("dbo.Bar", "BarColumn", "dbo.Foo");
Sql("TRUNCATE TABLE Foo");
AddForeignKey("dbo.Bar", "BarColumn", "dbo.Foo", "FooColumn", cascadeDelete: true);
}
The following script truncates all foreign key constraints & recreates them:
DECLARE #DROP_CONSTRAINT_SCRIPT VARCHAR(MAX);
DECLARE #CREATE_CONSTRAINT_SCRIPT VARCHAR(MAX);
SET #DROP_CONSTRAINT_SCRIPT='';
SET #CREATE_CONSTRAINT_SCRIPT='';
SELECT #DROP_CONSTRAINT_SCRIPT=#DROP_CONSTRAINT_SCRIPT + 'ALTER TABLE '+FK_Table+'
DROP CONSTRAINT '+FK_Name+';
',
#CREATE_CONSTRAINT_SCRIPT = #CREATE_CONSTRAINT_SCRIPT +
'ALTER TABLE '+FK_Table+'
ADD CONSTRAINT '+FK_Name+'
FOREIGN KEY ('+FK_Column+') REFERENCES '+PK_Table+'('+PK_Column+');
'
FROM (
SELECT RC.CONSTRAINT_NAME FK_Name
, KF.TABLE_SCHEMA FK_Schema
, KF.TABLE_NAME FK_Table
, KF.COLUMN_NAME FK_Column
, RC.UNIQUE_CONSTRAINT_NAME PK_Name
, KP.TABLE_SCHEMA PK_Schema
, KP.TABLE_NAME PK_Table
, KP.COLUMN_NAME PK_Column
, RC.MATCH_OPTION MatchOption
, RC.UPDATE_RULE UpdateRule
, RC.DELETE_RULE DeleteRule
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS RC
JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KF ON RC.CONSTRAINT_NAME = KF.CONSTRAINT_NAME
JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KP ON RC.UNIQUE_CONSTRAINT_NAME = KP.CONSTRAINT_NAME
WHERE KP.TABLE_NAME='TABLE_NAME_TRUNCATE'
) TBL
-- DROP CONSTRAINTS
EXEC( #DROP_CONSTRAINT_SCRIPT);
-------
-- TRUNCATE TABLE SCRIPT NEEDS TO BE PUT BELOW
-------
TRUNCATE TABLE TABLE_NAME_TRUNCATE
-- RECREATE CONSTRAINTS
EXEC( #CREATE_CONSTRAINT_SCRIPT);
SET FOREIGN_KEY_CHECKS=0;
TRUNCATE table1;
TRUNCATE table2;
SET FOREIGN_KEY_CHECKS=1;
reference - truncate foreign key constrained table
Working for me in MYSQL
You could try DELETE FROM <your table >;.
The server will show you the name of the restriction and the table, and deleting that table you can delete what you need.
I have just found that you can use TRUNCATE table on a parent table with foreign key constraints on a child as long as you DISABLE the constraints on the child table first.
E.g.
Foreign key CONSTRAINT child_par_ref on child table, references PARENT_TABLE
ALTER TABLE CHILD_TABLE DISABLE CONSTRAINT child_par_ref;
TRUNCATE TABLE CHILD_TABLE;
TRUNCATE TABLE PARENT_TABLE;
ALTER TABLE CHILD_TABLE ENABLE CONSTRAINT child_par_ref;
The easiest way:
1 - Enter in phpmyadmin
2 - Click on table name in left column
3 - Click in Operation (top menu)
4 - Click "Empty the table (TRUNCATE)
5 - Disable box "Enable foreign key checks"
6 - Done!
Link to image tutorial
Tutorial: http://www.imageno.com/wz6gv1wuqajrpic.html
(sorry, I don't have enough reputation to upload images here :P)

In SQL Server 2005, can I do a cascade delete without setting the property on my tables?

I have a database full of customer data. It's so big that it's really cumbersome to operate on, and I'd rather just slim it down to 10% of the customers, which is plenty for development. I have an awful lot of tables and I don't want to alter them all with "ON DELETE CASCADE", especially because this is a one-time deal.
Can I do a delete operation that cascades through all my tables without setting them up first? If not, what is my best option?
Combining your advice and a script I found online, I made a procedure that will produce SQL you can run to perform a cascaded delete regardless of ON DELETE CASCADE. It was probably a big waste of time, but I had a good time writing it. An advantage of doing it this way is, you can put a GO statement between each line, and it doesn't have to be one big transaction. The original was a recursive procedure; this one unrolls the recursion into a stack table.
create procedure usp_delete_cascade (
#base_table_name varchar(200), #base_criteria nvarchar(1000)
)
as begin
-- Adapted from http://www.sqlteam.com/article/performing-a-cascade-delete-in-sql-server-7
-- Expects the name of a table, and a conditional for selecting rows
-- within that table that you want deleted.
-- Produces SQL that, when run, deletes all table rows referencing the ones
-- you initially selected, cascading into any number of tables,
-- without the need for "ON DELETE CASCADE".
-- Does not appear to work with self-referencing tables, but it will
-- delete everything beneath them.
-- To make it easy on the server, put a "GO" statement between each line.
declare #to_delete table (
id int identity(1, 1) primary key not null,
criteria nvarchar(1000) not null,
table_name varchar(200) not null,
processed bit not null,
delete_sql varchar(1000)
)
insert into #to_delete (criteria, table_name, processed) values (#base_criteria, #base_table_name, 0)
declare #id int, #criteria nvarchar(1000), #table_name varchar(200)
while exists(select 1 from #to_delete where processed = 0) begin
select top 1 #id = id, #criteria = criteria, #table_name = table_name from #to_delete where processed = 0 order by id desc
insert into #to_delete (criteria, table_name, processed)
select referencing_column.name + ' in (select [' + referenced_column.name + '] from [' + #table_name +'] where ' + #criteria + ')',
referencing_table.name,
0
from sys.foreign_key_columns fk
inner join sys.columns referencing_column on fk.parent_object_id = referencing_column.object_id
and fk.parent_column_id = referencing_column.column_id
inner join sys.columns referenced_column on fk.referenced_object_id = referenced_column.object_id
and fk.referenced_column_id = referenced_column.column_id
inner join sys.objects referencing_table on fk.parent_object_id = referencing_table.object_id
inner join sys.objects referenced_table on fk.referenced_object_id = referenced_table.object_id
inner join sys.objects constraint_object on fk.constraint_object_id = constraint_object.object_id
where referenced_table.name = #table_name
and referencing_table.name != referenced_table.name
update #to_delete set
processed = 1
where id = #id
end
select 'print ''deleting from ' + table_name + '...''; delete from [' + table_name + '] where ' + criteria from #to_delete order by id desc
end
exec usp_delete_cascade 'root_table_name', 'id = 123'
Here's a version of the accepted answer optimised for sparsely populated data models. It checks for the existence of data in a FK chain before adding it to the deletion list. I use it to clean up test data.
Don't use it in an active transactional db- it will hold locks way too long.
/*
-- ============================================================================
-- Purpose: Performs a cascading hard-delete.
-- Not for use on an active transactional database- it holds locks for too long.
-- (http://stackoverflow.com/questions/116968/in-sql-server-2005-can-i-do-a-cascade-delete-without-setting-the-property-on-my)
-- eg:
exec dbo.hp_Common_Delete 'tblConsumer', 'Surname = ''TestDxOverdueOneReviewWm''', 1
-- ============================================================================
*/
create proc [dbo].[hp_Common_Delete]
(
#TableName sysname,
#Where nvarchar(4000), -- Shouldn't include 'where' keyword, e.g. Surname = 'smith', NOT where Surname = 'smith'
#IsDebug bit = 0
)
as
set nocount on
begin try
-- Prepare tables to store deletion criteria.
-- #tmp_to_delete stores criteria that is tested for results before being added to #to_delete
create table #to_delete
(
id int identity(1, 1) primary key not null,
criteria nvarchar(4000) not null,
table_name sysname not null,
processed bit not null default(0)
)
create table #tmp_to_delete
(
id int primary key identity(1,1),
criteria nvarchar(4000) not null,
table_name sysname not null
)
-- Open a transaction (it'll be a long one- don't use this on production!)
-- We need a transaction around criteria generation because we only
-- retain criteria that has rows in the db, and we don't want that to change under us.
begin tran
-- If the top-level table meets the deletion criteria, add it
declare #Sql nvarchar(4000)
set #Sql = 'if exists(select top(1) * from ' + #TableName + ' where ' + #Where + ')
insert #to_delete (criteria, table_name) values (''' + replace(#Where, '''', '''''') + ''', ''' + #TableName + ''')'
exec (#Sql)
-- Loop over deletion table, walking foreign keys to generate delete targets
declare #id int, #tmp_id int, #criteria nvarchar(4000), #new_criteria nvarchar(4000), #table_name sysname, #new_table_name sysname
while exists(select 1 from #to_delete where processed = 0)
begin
-- Grab table/criteria to work on
select top(1) #id = id,
#criteria = criteria,
#table_name = table_name
from #to_delete
where processed = 0
order by id desc
-- Insert all immediate child tables into a temp table for processing
insert #tmp_to_delete
select referencing_column.name + ' in (select [' + referenced_column.name + '] from [' + #table_name +'] where ' + #criteria + ')',
referencing_table.name
from sys.foreign_key_columns fk
inner join sys.columns referencing_column on fk.parent_object_id = referencing_column.object_id
and fk.parent_column_id = referencing_column.column_id
inner join sys.columns referenced_column on fk.referenced_object_id = referenced_column.object_id
and fk.referenced_column_id = referenced_column.column_id
inner join sys.objects referencing_table on fk.parent_object_id = referencing_table.object_id
inner join sys.objects referenced_table on fk.referenced_object_id = referenced_table.object_id
inner join sys.objects constraint_object on fk.constraint_object_id = constraint_object.object_id
where referenced_table.name = #table_name
and referencing_table.name != referenced_table.name
-- Loop on child table criteria, and insert them into delete table if they have records in the db
select #tmp_id = max(id) from #tmp_to_delete
while (#tmp_id >= 1)
begin
select #new_criteria = criteria, #new_table_name = table_name from #tmp_to_delete where id = #tmp_id
set #Sql = 'if exists(select top(1) * from ' + #new_table_name + ' where ' + #new_criteria + ')
insert #to_delete (criteria, table_name) values (''' + replace(#new_criteria, '''', '''''') + ''', ''' + #new_table_name + ''')'
exec (#Sql)
set #tmp_id = #tmp_id - 1
end
truncate table #tmp_to_delete
-- Move to next record
update #to_delete
set processed = 1
where id = #id
end
-- We have a list of all tables requiring deletion. Actually delete now.
select #id = max(id) from #to_delete
while (#id >= 1)
begin
select #criteria = criteria, #table_name = table_name from #to_delete where id = #id
set #Sql = 'delete from [' + #table_name + '] where ' + #criteria
if (#IsDebug = 1) print #Sql
exec (#Sql)
-- Next record
set #id = #id - 1
end
commit
end try
begin catch
-- Any error results in a rollback of the entire job
if (##trancount > 0) rollback
declare #message nvarchar(2047), #errorProcedure nvarchar(126), #errorMessage nvarchar(2048), #errorNumber int, #errorSeverity int, #errorState int, #errorLine int
select #errorProcedure = isnull(error_procedure(), N'hp_Common_Delete'),
#errorMessage = isnull(error_message(), N'hp_Common_Delete unable to determine error message'),
#errorNumber = error_number(), #errorSeverity = error_severity(), #errorState = error_state(), #errorLine = error_line()
-- Prepare error information as it would be output in SQL Mgt Studio
declare #event nvarchar(2047)
select #event = 'Msg ' + isnull(cast(#errorNumber as varchar), 'null') +
', Level ' + isnull(cast(#errorSeverity as varchar), 'null') +
', State ' + isnull(cast(#errorState as varchar), 'null') +
', Procedure ' + isnull(#errorProcedure, 'null') +
', Line ' + isnull(cast(#errorLine as varchar), 'null') +
': ' + isnull(#errorMessage, '#ErrorMessage null')
print #event
-- Re-raise error to ensure admin/job runners understand there was a failure
raiserror(#errorMessage, #errorSeverity, #errorState)
end catch
Unless you want to maintain all related queries as proposed by Chris, the ON DELETE CASCADE is by far the quickest and the most direct solution. And if you don't want it to be permanent, why don't you have some T-SQL code that will switch this option on and off like here
remove the original Tbl_A_MyFK constraint (without the ON DELETE CASCADE)
ALTER TABLE Tbl_A DROP CONSTRAINT Tbl_A_MyFK
set the constraint Tbl_A_MyFK with the ON DELETE CASCADE
ALTER TABLE Tbl_A ADD CONSTRAINT Tbl_A_MyFK FOREIGN KEY (MyFK) REFERENCES Tbl_B(Column) ON DELETE CASCADE
Here you can do your delete
DELETE FROM Tbl_A WHERE ...
drop your constraint Tbl_A_MyFK
ALTER TABLE Tbl_A DROP CONSTRAINT Tbl_A_MyFK
set the constraint Tbl_A_MyFK without the ON DELETE CASCADE
ALTER TABLE Tbl_A ADD CONSTRAINT Tbl_A_MyFK FOREIGN KEY (MyFK) REFERENCES (Tbl_B)
Go into SQL Server Management Studio and right-click the database. Select Tasks->Generate Scripts. Click Next twice. On the Options window choose set it to generate CREATE statements only, and put everything to False except for the Foreign Keys. Click Next. Select Tables and Click Next again. Click the "Select All" button and click Next then Finish and send the script to your choice of a query window or file (don't use the clipboard, since it might be a big script). Now remove all of the script that adds the tables and you should be left with a script to create your foreign keys.
Make a copy of that script because it is how you'll restore your database to its current state. Use a search and replace to add the ON DELETE CASCADE to the end of each constraint. This might vary depending on how your FKs are currently set up and you might need to do some manual editing.
Repeat the script generation, but this time set it to generate DROP statements only. Be sure to manually remove the table drops that are generated. Run the drops, then run your edited creates to make them all cascade on delete. Do your deletes, run the drop script again and then run the script that you saved off at the start.
Also - MAKE A BACKUP OF YOUR DB FIRST! Even if it's just a dev database, it will save you some headache if part of the script isn't quite right.
Hope this helps!
BTW - you should definitely do some testing with your full test data as another poster suggested, but I can see why you might not need that for initial development. Just don't forget to include that as part of QA at some point.
Kevin post is incomplete, his t-sql sp only prints the command, to execute these command, before last end add this
DECLARE #commandText VARCHAR(8000)
DECLARE curDeletes CURSOR FOR
select 'delete from [' + table_name + '] where ' + criteria from #to_delete order by id desc
OPEN curDeletes
FETCH NEXT FROM curDeletes
INTO
#commandText
WHILE(##FETCH_STATUS=0)
BEGIN
EXEC (#commandText)
FETCH NEXT FROM curDeletes INTO #commandText
END
CLOSE curDeletes
DEALLOCATE curDeletes
I usually just hand write the queries to delete the records I don't want and save that as a .sql file for future reference. The pseudocode is:
select id's of records from the main table that I want to delete into a temp table
write a delete query for each related table which joins to the temp table.
write a delete query for the main table joining to my temp table.
My suggestion is to go ahead and write a script that will add the on delete cascade to each relationship in the database while exporting a list of modified relationships. Then you can reverse the process and remove the on delete cascade command on each table in the list.
Personally if you are going to leave the records in production, I would also leave them in development. Otherwise you may write code that works fine when the recordset is small but times out when faced with the real recordset.
But if you are determined to do this, I would copy the id field of the records you want to dete from the main table first to a work table. Then I would take each related table and write a delete joining to that worktable to only delete those records. Finish up with the parent table. Make sure this ia written in a script and saved so the next time you want to do a similar thing to your test data, you can easily run it without having to figure out what are the reated tables that need records deleted from them.
Taking the accepted answer a bit further, I had the need to do this across tables in different schemas. I have updated the script to include schema in the outputted delete scripts.
CREATE PROCEDURE usp_delete_cascade (
#base_table_schema varchar(100), #base_table_name varchar(200), #base_criteria nvarchar(1000)
)
as begin
-- Expects the name of a table, and a conditional for selecting rows
-- within that table that you want deleted.
-- Produces SQL that, when run, deletes all table rows referencing the ones
-- you initially selected, cascading into any number of tables,
-- without the need for "ON DELETE CASCADE".
-- Does not appear to work with self-referencing tables, but it will
-- delete everything beneath them.
-- To make it easy on the server, put a "GO" statement between each line.
declare #to_delete table (
id int identity(1, 1) primary key not null,
criteria nvarchar(1000) not null,
table_schema varchar(100),
table_name varchar(200) not null,
processed bit not null,
delete_sql varchar(1000)
)
insert into #to_delete (criteria, table_schema, table_name, processed) values (#base_criteria, #base_table_schema, #base_table_name, 0)
declare #id int, #criteria nvarchar(1000), #table_name varchar(200), #table_schema varchar(100)
while exists(select 1 from #to_delete where processed = 0) begin
select top 1 #id = id, #criteria = criteria, #table_name = table_name, #table_schema = table_schema from #to_delete where processed = 0 order by id desc
insert into #to_delete (criteria, table_schema, table_name, processed)
select referencing_column.name + ' in (select [' + referenced_column.name + '] from [' + #table_schema + '].[' + #table_name +'] where ' + #criteria + ')',
schematable.name,
referencing_table.name,
0
from sys.foreign_key_columns fk
inner join sys.columns referencing_column on fk.parent_object_id = referencing_column.object_id
and fk.parent_column_id = referencing_column.column_id
inner join sys.columns referenced_column on fk.referenced_object_id = referenced_column.object_id
and fk.referenced_column_id = referenced_column.column_id
inner join sys.objects referencing_table on fk.parent_object_id = referencing_table.object_id
inner join sys.schemas schematable on referencing_table.schema_id = schematable.schema_id
inner join sys.objects referenced_table on fk.referenced_object_id = referenced_table.object_id
inner join sys.objects constraint_object on fk.constraint_object_id = constraint_object.object_id
where referenced_table.name = #table_name
and referencing_table.name != referenced_table.name
update #to_delete set
processed = 1
where id = #id
end
select 'print ''deleting from ' + table_name + '...''; delete from [' + table_schema + '].[' + table_name + '] where ' + criteria from #to_delete order by id desc
end
exec usp_delete_cascade 'schema', 'RootTable', 'Id = 123'
exec usp_delete_cascade 'schema', 'RootTable', 'GuidId = ''A7202F84-FA57-4355-B499-1F8718E29058'''
Expansion of croisharp's answer to take triggers into consideration, i.e. schema-aware solution that disables all affecting triggers, deletes rows, and enables the triggers.
CREATE PROCEDURE usp_delete_cascade (
#base_table_schema varchar(100),
#base_table_name varchar(200),
#base_criteria nvarchar(1000)
)
as begin
-- Expects the name of a table, and a conditional for selecting rows
-- within that table that you want deleted.
-- Produces SQL that, when run, deletes all table rows referencing the ones
-- you initially selected, cascading into any number of tables,
-- without the need for "ON DELETE CASCADE".
-- Does not appear to work with self-referencing tables, but it will
-- delete everything beneath them.
-- To make it easy on the server, put a "GO" statement between each line.
declare #to_delete table (
id int identity(1, 1) primary key not null,
criteria nvarchar(1000) not null,
table_schema varchar(100),
table_name varchar(200) not null,
processed bit not null,
delete_sql varchar(1000)
)
insert into #to_delete (criteria, table_schema, table_name, processed) values (#base_criteria, #base_table_schema, #base_table_name, 0)
declare #id int, #criteria nvarchar(1000), #table_name varchar(200), #table_schema varchar(100)
while exists(select 1 from #to_delete where processed = 0) begin
select top 1 #id = id, #criteria = criteria, #table_name = table_name, #table_schema = table_schema from #to_delete where processed = 0 order by id desc
insert into #to_delete (criteria, table_schema, table_name, processed)
select referencing_column.name + ' in (select [' + referenced_column.name + '] from [' + #table_schema + '].[' + #table_name +'] where ' + #criteria + ')',
schematable.name,
referencing_table.name,
0
from sys.foreign_key_columns fk
inner join sys.columns referencing_column on fk.parent_object_id = referencing_column.object_id
and fk.parent_column_id = referencing_column.column_id
inner join sys.columns referenced_column on fk.referenced_object_id = referenced_column.object_id
and fk.referenced_column_id = referenced_column.column_id
inner join sys.objects referencing_table on fk.parent_object_id = referencing_table.object_id
inner join sys.schemas schematable on referencing_table.schema_id = schematable.schema_id
inner join sys.objects referenced_table on fk.referenced_object_id = referenced_table.object_id
inner join sys.objects constraint_object on fk.constraint_object_id = constraint_object.object_id
where referenced_table.name = #table_name
and referencing_table.name != referenced_table.name
update #to_delete set
processed = 1
where id = #id
end
select 'print ''deleting from ' + table_name + '...''; delete from [' + table_schema + '].[' + table_name + '] where ' + criteria from #to_delete order by id desc
DECLARE #commandText VARCHAR(8000), #triggerOn VARCHAR(8000), #triggerOff VARCHAR(8000)
DECLARE curDeletes CURSOR FOR
select
'DELETE FROM [' + table_schema + '].[' + table_name + '] WHERE ' + criteria,
'ALTER TABLE [' + table_schema + '].[' + table_name + '] DISABLE TRIGGER ALL',
'ALTER TABLE [' + table_schema + '].[' + table_name + '] ENABLE TRIGGER ALL'
from #to_delete order by id desc
OPEN curDeletes
FETCH NEXT FROM curDeletes INTO #commandText, #triggerOff, #triggerOn
WHILE(##FETCH_STATUS=0)
BEGIN
EXEC (#triggerOff)
EXEC (#commandText)
EXEC (#triggerOn)
FETCH NEXT FROM curDeletes INTO #commandText, #triggerOff, #triggerOn
END
CLOSE curDeletes
DEALLOCATE curDeletes
end
after select you have to build and execute the actual delete
declare #deleteSql nvarchar(1200)
declare delete_cursor cursor for
select table_name, criteria
from #to_delete
order by id desc
open delete_cursor
fetch next from delete_cursor
into #table_name, #criteria
while ##fetch_status = 0
begin
select #deleteSql = 'delete from ' + #table_name + ' where ' + #criteria
--print #deleteSql
-- exec sp_execute #deleteSql
EXEC SP_EXECUTESQL #deleteSql
fetch next from delete_cursor
into #table_name, #criteria
end
close delete_cursor
deallocate delete_cursor
Post here a script that will work with foreign keys contain more than one column.
create procedure usp_delete_cascade (
#TableName varchar(200), #Where nvarchar(1000)
) as begin
declare #to_delete table (
id int identity(1, 1) primary key not null,
criteria nvarchar(1000) not null,
table_name varchar(200) not null,
processed bit not null default(0),
delete_sql varchar(1000)
)
DECLARE #MyCursor CURSOR
declare #referencing_column_name varchar(1000)
declare #referencing_table_name varchar(1000)
declare #Sql nvarchar(4000)
insert into #to_delete (criteria, table_name) values ('', #TableName)
declare #id int, #criteria nvarchar(1000), #table_name varchar(200)
while exists(select 1 from #to_delete where processed = 0) begin
select top 1 #id = id, #criteria = criteria, #table_name = table_name from #to_delete where processed = 0 order by id desc
SET #MyCursor = CURSOR FAST_FORWARD
FOR
select referencing_column.name as column_name,
referencing_table.name as table_name
from sys.foreign_key_columns fk
inner join sys.columns referencing_column on fk.parent_object_id = referencing_column.object_id
and fk.parent_column_id = referencing_column.column_id
inner join sys.columns referenced_column on fk.referenced_object_id = referenced_column.object_id
and fk.referenced_column_id = referenced_column.column_id
inner join sys.objects referencing_table on fk.parent_object_id = referencing_table.object_id
inner join sys.objects referenced_table on fk.referenced_object_id = referenced_table.object_id
inner join sys.objects constraint_object on fk.constraint_object_id = constraint_object.object_id
where referenced_table.name = #table_name
and referencing_table.name != referenced_table.name
OPEN #MyCursor
FETCH NEXT FROM #MYCursor
INTO #referencing_column_name, #referencing_table_name
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT #referencing_column_name
PRINT #referencing_table_name
update #to_delete set criteria = criteria + ' AND '+#table_name+'.'+#referencing_column_name+'='+ #referencing_table_name+'.'+#referencing_column_name
where table_name = #referencing_table_name
if(##ROWCOUNT = 0)
BEGIN
--if(#id <> 1)
--BEGIN
insert into #to_delete (criteria, table_name)
VALUES( ' LEFT JOIN '+#table_name+' ON '+#table_name+'.'+#referencing_column_name+'='+ #referencing_table_name+'.'+#referencing_column_name+ #criteria,
#referencing_table_name
)
--END
--ELSE
--BEGIN
--insert into #to_delete (criteria, table_name)
--VALUES( ' LEFT JOIN '+#table_name+' ON '+#table_name+'.'+#referencing_column_name+'='+ #referencing_table_name+'.'+#referencing_column_name,
--#referencing_table_name
--)
--END
END
FETCH NEXT FROM #MYCursor
INTO #referencing_column_name, #referencing_table_name
END
CLOSE #MyCursor
DEALLOCATE #MyCursor
update #to_delete set
processed = 1
where id = #id
end
--select 'print ''deleting from ' + table_name + '...''; delete from [' + table_name + '] where ' + criteria from #to_delete order by id desc
--select id, table_name, criteria, #Where from #to_delete order by id desc
select #id = max(id) from #to_delete
while (#id >= 1)
begin
select #criteria = criteria, #table_name = table_name from #to_delete where id = #id
set #Sql = 'delete [' + #table_name + '] from [' + #table_name + '] ' + #criteria+' WHERE '+#Where
exec (#Sql)
PRINT #Sql
-- Next record
set #id = #id - 1
end
end
This script has two issues:
1. You must indicate the condition 1=1 in order to delete all table base.
2. This creates the direct relations with the base table only. If the final table has another table parent relation, the the delete fail
DELETE FROM [dbo].[table2] WHERE TableID in (select [ID] from [dbo].[table3] where 1=1)
If table2 has a parent relation table1

Resources