Не удается усечь таблицу, поскольку на нее ссылается ограничение FOREIGN KEY?

Используя MSSQL2005, можно ли обрезать таблицу с ограничением внешнего ключа, если я сначала усекаю дочернюю таблицу (таблицу с первичным ключом отношения FK)?

Я знаю, что я могу либо

  • Использовать DELETE без оговорки, а затем RESEED личность (или)
  • Удалите FK, обрежьте таблицу и воссоздайте FK.

Я думал, что, пока я обрезаю дочернюю таблицу до родительской, я буду в порядке, не выполняя ни один из указанных выше вариантов, но я получаю эту ошибку:

Невозможно усечь таблицу "TableName", так как на нее ссылается ограничение FOREIGN KEY.

32 ответа

Решение

Правильный; Вы не можете обрезать таблицу с ограничением FK.

Обычно мой процесс для этого:

  1. Отбрось ограничения
  2. Усечь стол
  3. Воссоздайте ограничения.

(Все в сделке, конечно.)

Конечно, это применимо только в том случае, если ребенок уже был усечен. В противном случае я иду другим путем, полностью завися от того, как выглядят мои данные. (Слишком много переменных, чтобы попасть сюда.)

Оригинальный плакат определил, ПОЧЕМУ это так; см. этот ответ для более подробной информации.

DELETE FROM TABLENAME
DBCC CHECKIDENT ('DATABASENAME.dbo.TABLENAME',RESEED, 0)

Обратите внимание, что это, вероятно, не то, что вы хотели бы, если у вас есть миллионы + записей, поскольку это очень медленно.

Так как TRUNCATE TABLE является командой DDL, она не может проверить, ссылаются ли записи в таблице на запись в дочерней таблице.

Вот почему DELETE работает и TRUNCATE TABLE нет: потому что база данных может убедиться, что на нее не ссылается другая запись.

Без 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|1])

Как хранимая процедура

https://github.com/reduardo7/TableTruncate

Обратите внимание, что это, вероятно, не то, что вы хотели бы, если у вас есть миллионы + записей, поскольку это очень медленно.

Решение @denver_citizen, представленное выше, не работает для меня, но мне понравился его дух, поэтому я изменил несколько вещей:

  • сделал это хранимой процедурой
  • изменил способ заполнения и воссоздания внешних ключей
  • исходный сценарий усекает все ссылочные таблицы, это может вызвать ошибку нарушения внешнего ключа, когда на ссылочную таблицу есть другие ссылки на внешний ключ. Этот скрипт усекает только таблицу, указанную в качестве параметра. Пользователь сам может несколько раз вызвать эту хранимую процедуру для всех таблиц в правильном порядке.

Для блага публики вот обновленный скрипт:

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

Ну, так как я не нашел примеров очень простого решения, которое я использовал, а именно:

  1. Сбросить внешний ключ;
  2. Усеченный стол
  3. Воссоздать внешний ключ

Здесь это идет:

1) Найдите имя внешнего ключа, который вызывает ошибку (например: FK_PROBLEM_REASON, с полем IDиз таблицы TABLE_OWNING_CONSTRAINT) 2) Удалить этот ключ из таблицы:

ALTER TABLE TABLE_OWNING_CONSTRAINT DROP CONSTRAINT FK_PROBLEM_REASON

3) Обрезать требуемый стол

TRUNCATE TABLE TABLE_TO_TRUNCATE

4) Повторно добавьте ключ к этой первой таблице:

ALTER TABLE TABLE_OWNING_CONSTRAINT ADD CONSTRAINT FK_PROBLEM_REASON FOREIGN KEY(ID) REFERENCES TABLE_TO_TRUNCATE (ID)

Вот и все.

Используйте следующую команду после удаления всех строк в этой таблице с помощью оператора delete

delete from tablename

DBCC CHECKIDENT ('tablename', RESEED, 0)

РЕДАКТИРОВАТЬ: исправлен синтаксис для SQL Server

SET FOREIGN_KEY_CHECKS = 0; 

truncate table "yourTableName";

SET FOREIGN_KEY_CHECKS = 1;

Вы можете выполнить этот шаг, reseeding table Вы можете удалить данные таблицы.

delete from table_name
dbcc checkident('table_name',reseed,0)

если возникнет какая-то ошибка, вам придется заново заполнить основную таблицу.

Вот сценарий, который я написал, чтобы автоматизировать процесс. Я надеюсь, что это помогает.

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 мне не совсем помог, но я изменил его, чтобы учесть:

  1. Композитные ключи
  2. Действия при удалении и обновлении
  3. Проверка индекса при повторном добавлении
  4. Схемы кроме dbo
  5. Несколько столов одновременно
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
        ,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 SCHEMA_NAME(fk.schema_id) = tbl.SchemaName 
                AND OBJECT_NAME(fc.referenced_object_id) = tbl.Name
)
select
    quotename(fk.ConstraintName) AS ConstraintName
    ,quotename(fk.SchemaName) + '.' + quotename(fk.TableName) AS TableName
    ,quotename(fk.SchemaName) + '.' + 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
        ORDER BY fk2.constraint_column_id
        FOR XML PATH(''),TYPE
    ).value('.','VARCHAR(MAX)'),1,1,'') AS ColumnNames
    ,STUFF((
        SELECT ',' + quotename(fk2.ReferencedColumnName)
        FROM foreignKeys fk2 
        WHERE fk2.ConstraintName = fk.ConstraintName
        ORDER BY fk2.constraint_column_id
        FOR XML PATH(''),TYPE
    ).value('.','VARCHAR(MAX)'),1,1,'') AS ReferencedColumnNames
into #FKs
from foreignKeys fk
GROUP BY fk.SchemaName, fk.ConstraintName, fk.TableName, 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 ' + fk.TableName AS script
from #FKs fk

-- 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 + ' ON UPDATE ' + fk.UpdateAction 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;

Найдено в другом месте в Интернете

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'

Вы не можете обрезать таблицу, если не отбрасываете ограничения. Отключение также не работает. тебе нужно все бросить. Я сделал скрипт, который отбрасывает все ограничения, а затем воссоздает.

Обязательно заверните это в транзакцию;)

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

Если я правильно понимаю, вам нужно иметь чистую среду для БД, включающую интеграционные тесты.

Мой подход здесь состоит в том, чтобы отбросить всю схему и воссоздать ее позже.

Причины:

  1. У вас, вероятно, уже есть сценарий "создания схемы". Повторно использовать его для изоляции теста легко.
  2. Создание схемы довольно быстро.
  3. При таком подходе довольно легко настроить сценарий так, чтобы каждый прибор создавал НОВУЮ схему (с временным именем), а затем вы можете запустить параллельный запуск тестовых приспособлений, что значительно ускоряет работу самой медленной части вашего набора тестов.,

Я пишу следующие способы и попытался параметризовать их, чтобы вы могли запустить их в Query document Или сделать полезное SPс ними легко.

А) Удалить

Если в вашей таблице нет миллионов записей, это хорошо работает ине имеет команд Alter:

---------------------------------------------------------------
------------------- 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)
  • В моем ответе выше метод решения упомянутой проблемы в вопросе основан на ответе@s15199d.

Б) усекать

Если в вашей таблице миллионы записей или у вас нет проблем скомандой Alter в ваших кодах, используйте эту:

--   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
  • В моем ответе выше метод решения упомянутой проблемы в вопросе основан на ответе@LauroWolffValenteSobrinho.

  • Если у вас есть более одного ограничения, вы должны добавить его коды, как я, к вышеуказанному запросу

  • Также вы можете изменить вышеуказанный код базы@SerjSagan, чтобы отключить ограничение включения

truncate не работает для меня, delete + 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

За MS SQLПо крайней мере, в более новых версиях вы можете просто отключить ограничения с помощью следующего кода:

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

Единственный способ - удалить внешние ключи перед усечением. А после усечения данных вы должны заново создать индексы.

Следующий сценарий генерирует требуемый SQL для снятия всех ограничений внешнего ключа.

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

Затем следующий сценарий генерирует необходимый SQL для воссоздания внешних ключей.

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

Запустите сгенерированный сценарий, чтобы удалить все внешние ключи, усечь таблицы, а затем запустите сгенерированный сценарий, чтобы воссоздать все внешние ключи.

Запросы берутся отсюда.

Следующее работает для меня даже с ограничениями FK и объединяет следующие ответы только для удаления указанных таблиц:


USE [YourDB];

DECLARE @TransactionName varchar(20) = 'stopdropandroll';

BEGIN TRAN @TransactionName;
set xact_abort on; /* automatic rollback https://stackru.com/a/1749788/1037948 */
    -- ===== DO WORK // =====

    -- dynamic sql placeholder
    DECLARE @SQL varchar(300);

    -- LOOP: https://stackru.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://stackru.com/a/989111/1037948
        SET @SQL = 'DELETE FROM [' + @token + ']; DBCC CHECKIDENT (''' + @token + ''',RESEED, 0);'; -- https://stackru.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;

Замечания:

Я думаю, что все еще помогает объявить таблицы в том порядке, в котором вы хотите их удалить (т.е. сначала убить зависимости). Как видно из этого ответа, вместо циклических имен вы можете заменить все таблицы

EXEC sp_MSForEachTable 'DELETE FROM ?; DBCC CHECKIDENT (''?'',RESEED, 0);';

Это мое решение этой проблемы. Я использовал его для изменения ПК, но идея та же. Надеюсь это будет полезно)

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'

Если ни один из этих ответов не сработал так, как в моем случае, сделайте следующее:

  1. Отбросьте ограничения
  2. Установите все значения, чтобы разрешить нули
  3. Усеченный стол
  4. Добавьте ограничения, которые были удалены.

Удачи!

Удалить, а затем сбросить автоинкремент:

delete from tablename;

затем

ALTER TABLE tablename AUTO_INCREMENT = 1;

Следующий скрипт усекает все ограничения внешнего ключа и воссоздает их:

      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);

Увидев, насколько хорошо работает это решение , я собрал удобную хранимую процедуру для простоты использования.

      CREATE OR ALTER PROCEDURE ResetTable
(
  @database NVARCHAR(128),
  @schema NVARCHAR(128),
  @table NVARCHAR(128)
)
AS
BEGIN
  BEGIN TRANSACTION;

  DECLARE @sql NVARCHAR(MAX);

  SET @sql = 'DELETE FROM ' + QUOTENAME(@schema) + '.' + QUOTENAME(@table) + ';' +
             'DBCC CHECKIDENT (''' + QUOTENAME(@database) + '.' + QUOTENAME(@schema) + '.' + QUOTENAME(@table) + ''', RESEED, 0);';

  BEGIN TRY
    EXEC sp_executesql @sql;
    COMMIT;
  END TRY
  BEGIN CATCH
    ROLLBACK;
  END CATCH;
END;

Пример использования:

      EXEC ResetTable @database='MyDatabase', @schema='dbo', @table='Users';

Если вы делаете это с любой частотой, чёрт возьми, даже по расписанию, я бы абсолютно и безоговорочно никогда не использовал оператор DML. Стоимость записи в журнал транзакций слишком высока, и установка всей базы данных в SIMPLE Режим восстановления для усечения одной таблицы смешон.

Лучший способ, к сожалению, трудный или трудоемкий. Это существо:

  • Отбросьте ограничения
  • Усеченный стол
  • Восстановить ограничения

Мой процесс для этого включает в себя следующие шаги:

  1. В SSMS щелкните правой кнопкой мыши по рассматриваемой таблице и выберите View Dependencies
  2. Обратите внимание на таблицы, на которые есть ссылки (если есть)
  3. Вернитесь в проводник объектов, разверните узел Ключи и запишите внешние ключи (если есть)
  4. Запустить сценарии (удалить / усечь / заново создать)

Сценарии такого рода должны быть сделаны в течение begin tran а также commit tran блок.

В SSMS у меня была открытая диаграмма, показывающая ключ. После удаления ключа и усечения файла я обновил его, затем снова сосредоточился на диаграмме и создал обновление, очистив и восстановив поле Identity. При сохранении диаграммы появилось диалоговое окно "Сохранить", после чего в диалоговом окне "Изменения были внесены в базу данных во время работы", нажав "Да", восстановил ключ и восстановил его из заблокированной копии на диаграмме.

Черт возьми - так много ответов... и я рискну вставить другой. Мне нужно было что-то, чтобы периодически удалять подмножество таблиц в производственных базах данных, и почти все существующие ответы имели какую-то проблему, которая не отвечала моим интересам... которые были:

  • Мне нужно было иметь дело с ограничениями, которые имеют такие имена, как ThisTable.to.ThatTable
  • Список таблиц может охватывать несколько схем
  • Список усеченных таблиц может варьироваться - поэтому мне нужен ввод
  • Есть внешние ключи с on delete cascade и другие действия
  • Если после усечения я не могу восстановить внешние ключи, мне нужно выполнить откат
  • Мне нужно ограничить область удаления / повторного добавления внешних ключей только для затронутых таблиц
  • Мне нужны как минимум минимальные проверки работоспособности, так как мы заставляем клиентов запускать этот беспорядок
  • Я хочу, чтобы клиенты запускали что, если перед тем, как делать это по-настоящему
  • Я не могу себе позволить delete table по ряду причин

Я также хотел что-то более понятное и понятное, чем некоторые сценарии, которые я изучал. Я не знаю... Я думаю, это субъективно. Вы можете ненавидеть мой стиль кодирования.

Во всяком случае, он предварительно обрабатывает, выбирая в какой-то большой nvarchar(max) переменные: одна для удаления внешних ключей, одна для отключения триггеров / усечения таблицы / включения триггеров и, наконец, одна для восстановления внешних ключей. Затем он либо печатает, либо выполняет результаты.

Ожидается, что в качестве входного аргумента будет задан пользовательский тип таблицы. У нас есть такая вещь во всех наших базах данных. Этот тип определяется так:

create type dbo.NamesType as table( Name sysname )

Чтобы вызвать его для некоторых одноразовых таблиц в нескольких схемах со странными именами ограничений (и, возможно, с небольшим злонамеренным вводом), вот суть моего тестового набора:

create schema nop authorization dbo
go
  create schema gak authorization dbo
  go
    create table nop.a(one int primary key)
    create table gak.b(one int constraint [b.to.a] foreign key references nop.a)
    go
      --> do the damage...
      declare @t dbo.NamesType
      insert @t values ('nop.a'),('gak.b') --,(';drop database'),('no.bobby.tables')
      execute dbo.TruncateTables @t --, 0
    go
    drop table gak.b
    drop table nop.a
  go
  drop schema gak
go
drop schema nop

... который печатает:

alter table gak.b drop constraint [b.to.a];
alter table nop.a disable trigger all;
truncate table nop.a;
alter table nop.a enable trigger all;
alter table gak.b disable trigger all;
truncate table gak.b;
alter table gak.b enable trigger all;
alter table gak.b add constraint [b.to.a] foreign key ( one ) references nop.a( one );

И, наконец, вот клубок пряжи. Надеюсь, это кому- нибудь поможет;-)

create procedure dbo.TruncateTables
  @tableNames dbo.NamesType readonly, --> Note: create type dbo.NamesType as table( Name sysname )
  @printOnly bit = 1 --> by default, it only prints...you have to explicly set to 0 to get it to do the damage
as 
begin

  set nocount on

  declare 
    @dropFKsSql nvarchar( max ) = N'',
    @addFKsSql nvarchar( max ) = N'',
    @truncateSql nvarchar( max ) = N'',
    @cr nvarchar( 2 ) = nchar( 13 ) + nchar( 10 ),
    @numberOfInputs int,
    @numberOfEligible int

  declare
    @truncatableTables table( object_id int not null )

  insert @truncatableTables
  select t.object_id 
  from
    @tableNames n 
    inner join
    sys.tables t
    on
      object_id( n.Name ) = t.object_id

  select @numberOfInputs = count(*) from @tableNames
  select @numberOfEligible = count(*) from @truncatableTables
  if ( @numberOfEligible < @numberOfInputs )
  begin
    raiserror( 'At least one of the inputs is not a table in this database', 16, 1 )
    return 1
  end

  select 
    @truncateSql += 
      N'alter table ' + schema_name(t.schema_id) + N'.' + t.name + N' disable trigger all;' + @cr +
      N'truncate table ' + schema_name(t.schema_id) + N'.' + t.name + N';' + @cr +
      N'alter table ' + schema_name(t.schema_id) + N'.' + t.name + N' enable trigger all;' + @cr 
  from 
    sys.tables t
  where 
    t.object_id in ( select object_id from @truncatableTables )

  select 
    @dropFKsSql +=  
      N'alter table '+ schema_name(from_table.schema_id) + N'.' + from_table.name + 
      N' drop constraint [' + fk.name + N']'+
      N';' + @cr,
    @addFKsSql +=
      N'alter table '+ schema_name(from_table.schema_id) + N'.' + from_table.name + 
      N' add constraint [' + fk.name + N'] foreign key ( ' + fk_cols.from_columns + N' ) references ' + 
      schema_name(to_table.schema_id) + N'.' + to_table.name + N'( ' + fk_cols.to_columns + N' )' + 
      case (fk.delete_referential_action) 
      when 1 then N' on delete cascade'  
      when 2 then N' on delete set null'
      when 3 then N' on delete set default'
      else N'' 
      end + 
      case (fk.update_referential_action) 
      when 1 then N' on update cascade'  
      when 2 then N' on update set null'
      when 3 then N' on update set default'
      else N'' 
      end + 
      N';' + @cr
  from 
    sys.foreign_keys fk
    inner join
    sys.objects from_table
    on
      fk.parent_object_id = from_table.object_id
    inner join
    sys.objects to_table
    on
      fk.referenced_object_id = to_table.object_id
    inner join
    (
      select
        o.constraint_object_id,
        stuff( (
          select N',' + col_name(fkc.parent_object_id, fkc.parent_column_id)
          from sys.foreign_key_columns fkc
          where fkc.constraint_object_id = o.constraint_object_id
          order by fkc.constraint_column_id  --> has to be same order in to_columns (below)
          for xml path( '' )
        ), 1, 1, N'' ) as from_columns, 
        stuff( (
          select N',' + col_name(fkc.referenced_object_id, fkc.referenced_column_id)
          from sys.foreign_key_columns fkc
          where fkc.constraint_object_id = o.constraint_object_id
          order by fkc.constraint_column_id --> has to be in same order as from_colums (above)
          for xml path( '' )
        ), 1, 1, N'' ) as to_columns
      from 
        sys.foreign_key_columns o
      where 
        o.constraint_column_id = 1 --> just need a representative row
      group by 
        constraint_object_id
    ) as fk_cols
    on
      fk.object_id = fk_cols.constraint_object_id
    where
      fk.referenced_object_id in ( select object_id from @truncatableTables ) --> limits scope

  print @dropFKsSql
  print @truncateSql
  print @addFKsSql

  if ( @printOnly = 0 )
  begin 
    begin transaction  --> won't have this luxury in azure
      execute sp_executesql @statement = @dropFKsSql
      execute sp_executesql @statement = @truncateSql
      execute sp_executesql @statement = @addFKsSql
      print 'Got this far, and it didn''t blow up!'
    commit transaction
  end

end

В Azure вы не можете запустить truncate table в сделке (грррр....). В такой среде мне нужно заранее обеспечить наличие записей во внешних ключах, которые не участвуют в усечении. Не безнадежно сложный, но пока не необходимый для меня.

Это пример для тех, кто использует Entity Framework.

  • Таблица для сброса:

  • Еще одна таблица, которая зависит от:

  • Столбец ограничений в таблице Foo : FooColumn

  • Столбец ограничений в таблице 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);
     }
    

Я только что обнаружил, что вы можете использовать таблицу TRUNCATE для родительской таблицы с ограничениями внешнего ключа для дочернего элемента, если сначала вы отключите ограничения для дочерней таблицы. Например

Внешний ключ CONSTRAINT child_par_ref для дочерней таблицы, ссылается на 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;

Вы могли бы попробовать DELETE FROM <your table >;,

Сервер покажет вам имя ограничения и таблицу, и, удалив эту таблицу, вы можете удалить то, что вам нужно.

Другие вопросы по тегам