SQL DELETE命中约束

时间:2012-04-12 15:02:38

标签: sql sql-server database

让我们看看着名的Nortwind数据库。假设我运行DELETE FROM Clients

在MSAccess中,当针对具有参照完整性约束的表运行DELETE语句时,Jet将删除可以删除的记录,并保留其他记录。在这种情况下,它只会删除没有Clients的{​​{1}} 在SQL Server中,执行此操作似乎只是失败,并显示一条消息,指出Orders

我的问题是:是否有一种简单的方法让SQL Server只删除那些可以删除的记录?或者我是否必须添加The DELETE statement conflicted with the REFERENCE constraint ....
换句话说,我可以让SQL Server WHERE ClientId NOT IN (SELECT Id FROM Clients)像Jet的DELETE一样工作吗?

有关信息:我不是很懒,但有很多限制,我想保持我的代码简单......

6 个答案:

答案 0 :(得分:1)

您的选择是:

  1. 级联删除 - 将删除所有依赖的记录 那些客户。
  2. 掉落约束(我不推荐这个:))
  3. 提前检查是否可以删除并且不会发生冲突

答案 1 :(得分:1)

如果你想留下有FK引用的行,那么只有几个选项,而且它们都不漂亮:

  1. 在执行删除之前检查约束
  2. 修改查询,使其具有您在问题中提到的F​​K的where子句
  3. 将逻辑更改为一次删除一行,提交每一行并在删除时回滚删除。
  4. '最糟糕'选项实际上取决于有多少个FK,你要删除多少行以及一行有FK依赖性的可能性。如果这是一个相对罕见的事件,那么选项#3可能是最好的,尽管我倾向于倾向于前两个选项。

答案 2 :(得分:1)

另一种方法是使用CURSORTRY .. CATCH块来循环删除(逐行)以忽略删除引用行的问题。

在这种方法中,您不必为现有和未来的约束建模。

示例:

SET NOCOUNT ON; -- use not to have "(N row(s) affected)" for each deleted row

DECLARE del_cursor CURSOR
FOR SELECT ClientID FROM Clients

DECLARE @CurrentClientID INT -- use your proper type
DECLARE @message VARCHAR(200) -- just for building messages

OPEN del_cursor

FETCH NEXT FROM del_cursor
INTO @CurrentClientID

WHILE @@FETCH_STATUS = 0
BEGIN
    BEGIN TRY
        DELETE FROM Clients WHERE CURRENT OF del_cursor
    END TRY
    BEGIN CATCH
        SET @message = 'Row ' + CAST(@CurrentClientID AS VARCHAR) + ' cannot be deleted - skipping.'
        PRINT @message
    END CATCH

    FETCH NEXT FROM del_cursor
    INTO @CurrentClientID
END

CLOSE del_cursor
DEALLOCATE del_cursor

您可以使用CREATE PROCEDURE DeleteClients将上述示例换行并使用EXEC DeleteClients代替DELETE FROM Clients

答案 3 :(得分:0)

我知道这是一个有点复杂的解决方案,但你只需要做一次。

阻止删除的触发器怎么样?

create table parent(
id int not null primary key
)
create table son(
id int not null primary key,
idparent int)

alter table son add foreign key(idparent) references parent(id)

insert into parent values(1)
insert into parent values(2)
insert into parent values(3)

insert into son values(1,1)

--select * from parent
--select * from son

create trigger preventDelete
on parent
instead of delete
as
begin
  delete from parent where id not in (select idparent from son) and id in (select id from deleted)
end

delete from parent

记录2和3将被删除

答案 4 :(得分:0)

试试这个,它会生成你的陈述。请原谅我写得很快的格式:

DECLARE @i INT, @SQL NVARCHAR(2000), @TABLENAME NVARCHAR(100)
SET @i = 1
SET @TABLENAME = 'informer_alert'
SET @SQL = ''

DECLARE @col VARCHAR(50), @basecol VARCHAR(50), @tname VARCHAR(50)

DECLARE @TABLE TABLE ([table] VARCHAR(50), col VARCHAR(50), basecol VARCHAR(50))
INSERT INTO @TABLE
SELECT t.name, sc.name, sc2.name
FROM sys.foreign_key_columns fk
INNER JOIN sys.tables t ON fk.parent_object_id = t.OBJECT_ID
INNER JOIN syscolumns sc ON fk.parent_column_id = sc.colorder
AND sc.id = fk.parent_object_id
INNER JOIN syscolumns sc2 ON fk.referenced_object_id = sc2.id
AND fk.constraint_column_id = sc2.colorder
WHERE fk.referenced_object_id = (SELECT OBJECT_ID 
                                    FROM sys.tables 
                                    WHERE name = @TABLENAME)


WHILE (@i <= (SELECT COUNT(*) FROM @TABLE))
BEGIN

    SELECT @tname = [table], @col = col, @basecol = basecol
    FROM (SELECT    ROW_NUMBER() OVER(ORDER BY col) [N],
            [table], col, basecol
            FROM @TABLE) A
    WHERE A.N = @i

    SET @SQL = @SQL + ' DELETE FROM ' + @TABLENAME + ' WHERE ' + @basecol  + ' NOT IN (SELECT ' + @col+ ' FROM ' + @tname + ')'

    SET @i = @i + 1

END

SELECT @SQL

答案 5 :(得分:0)

  • 如果您想保持代码简单(您说过),请让CREATE VIEW放在桌面上。
  • 定义WHERE子句以仅获取可删除的行。
  • 然后你可能只是DELETE FROM ClientsDeletable
  • 请记住您的新视图的DELETE权限。

脚本示例:

CREATE VIEW ClientsDeletable
AS
SELECT * 
FROM Clients
WHERE
ClientID NOT IN (SELECT CliID FROM ForeignTab1)
AND
ClientID NOT IN (SELECT CliID FROM ForeignTab2)

注意,FROM不能包含JOIN - 其他方式会出错:

Msg 4405, Level 16, State 1, Line 1
View or function 'ClientsDeletable' is not updatable because the modification affects multiple base tables.