39

I have a table called PX_Child that has a foreign key on PX_Parent. I'd like to temporarily disable this FK constraint so that I can truncate PX_Parent. I'm not sure how this goes however.

I've tried these commands

ALTER TABLE PX_Child NOCHECK CONSTRAINT ALL

ALTER TABLE PX_Parent NOCHECK CONSTRAINT ALL

(truncate commands)

ALTER TABLE PX_Child CHECK CONSTRAINT ALL

ALTER TABLE PX_Parent CHECK CONSTRAINT ALL

But the truncate still tells me it can't truncate PX_Parent because of a foreign key constraint. I've looked all around the net and can't seem to find what I'm doing wrong, sorry for the basic nature of this question.

LittleBobbyTables - Au Revoir
  • 32,008
  • 25
  • 109
  • 114
larryq
  • 15,713
  • 38
  • 121
  • 190
  • 1
    Looks like Kalen Delaney was inadvertently responsible for starting this idea off. [Here she clarifies](http://www.eggheadcafe.com/software/aspnet/29927698/cant-truncate-table.aspx) "you have to drop the referencing constraint in order to truncate the table." – Martin Smith Oct 02 '10 at 00:37

4 Answers4

51

You can't truncate the table if there is any foreign key referencing it, including disabled constraints. You either need to drop the foreign key constraints or use the DELETE command.

bobs
  • 21,844
  • 12
  • 67
  • 78
  • 1
    See my (5 years later) answer below for how to quickly generate the `DROP CONSTRAINT` and `ADD CONSTRAINT` SQL – RJB Apr 24 '16 at 00:53
  • The DELETE statement conflicted with the REFERENCE constraint. (Delete is not working either) – Enrico Dec 17 '19 at 11:05
22

There is an easier-ish way. I was faced with the same problem and found this solution: https://www.mssqltips.com/sqlservertip/3347/drop-and-recreate-all-foreign-key-constraints-in-sql-server/

If you just run this query in your DB, it will generate the T-SQL you need to include before/after your sproc, in order to delete and then restore any foreign key constraints.

Don't worry about trying to understand this query itself.

CREATE TABLE #x -- feel free to use a permanent table
(
  drop_script NVARCHAR(MAX),
  create_script NVARCHAR(MAX)
);

DECLARE @drop   NVARCHAR(MAX) = N'',
        @create NVARCHAR(MAX) = N'';

-- drop is easy, just build a simple concatenated list from sys.foreign_keys:
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];

INSERT #x(drop_script) SELECT @drop;

-- create is a little more complex. We need to generate the list of 
-- columns on both sides of the constraint, even though in most cases
-- there is only one column.
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;

UPDATE #x SET create_script = @create;

PRINT @drop;
PRINT @create;

/*
EXEC sp_executesql @drop
-- clear out data etc. here
EXEC sp_executesql @create;
*/

Generates a bunch of:

ALTER TABLE [dbo].[Whatever] DROP CONSTRAINT....
--
ALTER TABLE [dbo].[Whatever] ADD CONSTRAINT....
RJB
  • 2,063
  • 5
  • 29
  • 34
  • 29
    bad advice: "Don't worry about trying to understand this query itself". Never run anything gotten off the net without understanding it – Dharmendar Kumar 'DK' May 22 '16 at 20:39
  • True, but it wasn't a production version of the DB – RJB May 22 '16 at 23:01
  • 5
    There is a difference between understanding how a query works and making sure it won't do any harm. The latter is almost always easier. – wolfrevokcats Jul 09 '16 at 09:15
  • Thank you so much for this script! Would have taken a while to figure out myself. While I do understand it, I totally agree, you do not have to understand it to use it. Just make sure the code it generates is correct before executing it. – Brandon Frenchak Mar 21 '17 at 13:47
  • 3
    The fact that this mess of dynamic sql is necessary to simply execute a TRUNCATE TABLE is revolting. – Pxtl May 18 '17 at 19:46
  • 3
    As written this doesn't properly recreate constraints with 'ON DELETE CASCADE` rules. It's a simple fix, change the line `FOR XML PATH(N''), TYPE).value(N'.[1]', N'nvarchar(max)'), 1, 1, N'') + ');'` to `FOR XML PATH(N''), TYPE).value(N'.[1]', N'nvarchar(max)'), 1, 1, N'') + ') ON DELETE ' + REPLACE(fk.delete_referential_action_desc, '_', ' ') + ';'` – Jason Larke Sep 14 '17 at 04:03
  • 2
    Note that the `PRINT @drop` and `PRINT @create` statements will truncate in SQL Management Studio. If you want these full values you will need to `SELECT * FROM #x` and then copy values from the two resulting columns. Even then I ended up having to `DECLARE @tempCreate NVARCHAR(MAX); SELECT @tempCreate = create_script from #x; EXEC sp_executesql @tempCreate;` for my create statement. – Tyler Forsythe Mar 16 '18 at 00:08
  • This script didn't work for my tables, it must have missed something. – jnnnnn Sep 05 '18 at 10:07
  • 1
    As Tyler said it will truncate the text if it's too long. Just tried in SQL Ops Studio (now known as Azure Data Studio) with same truncation. Simply adding `select @drop union all select @create` at the end of the script will solve the problem. – Adam Plocher Oct 24 '18 at 07:01
  • If you receive an error "Cannot resolve the collation conflict..." after making the change suggested by @JasonLarke, then you can add " collate database_default" to the end of the query. See https://github.com/Microsoft/mssql-jdbc/issues/590 – ajphall Oct 21 '19 at 04:40
7

SQL server will not let you truncate the table while the constraint exists, even if it's disabled. Drop the constraint and re-create it after truncating the table. Or just drop and re-create the tables, whichever is easier to do in your application.

Paul Groke
  • 6,259
  • 2
  • 31
  • 32
  • 3
    What do you mean it's not a transactional command? You can roll it back just fine. `CREATE TABLE Blah(a int); INSERT Blah VALUES(1); SELECT * FROM Blah; BEGIN TRAN; TRUNCATE TABLE Blah; SELECT * FROM Blah; ROLLBACK TRAN SELECT * FROM Blah; DROP TABLE Blah`. Truncate works by deallocating entire pages rather than removing rows, but it's still transactional. – ErikE Oct 02 '10 at 01:11
  • @Emtucifor: Oops, seems like I misinterpreted the documentation you you're right! I removed that piece of misinformation. – Paul Groke Oct 02 '10 at 01:47
  • 2
    @Emtucifor, @pgroke, in a way you are both correct as the standard allows for TRUNCATE to be non-transactional, but implementations are allowed to make it transaction. Hence TRUNCATE as defined doesn't promise a rollback can be done, but SqlServer (and Postgres) adds that promise beyond the standard. – Jon Hanna Oct 02 '10 at 10:43
  • 1
    @Jon Thanks for clarifying. Let me rephrase. **In SQL Server**, truncate is transactional. – ErikE Oct 02 '10 at 21:17
2

There is no such option to truncate table while foreign key constraint but we can use some trick like

 ALTER TABLE [dbo].[table2] DROP CONSTRAINT [FK_table2_table1]
    GO
    truncate table [table1]
GO
    ALTER TABLE [dbo].[table2]  WITH CHECK ADD  CONSTRAINT [FK_table2_table1] FOREIGN KEY([FKId])
    REFERENCES [dbo].[table1] ([ID])
    GO

    ALTER TABLE [dbo].[table2] CHECK CONSTRAINT [FK_table2_table1]
    GO
BJ Coder
  • 350
  • 5
  • 16