2

让我们看看著名的 Nortwind 数据库。说我跑DELETE FROM Clients

在 MSAccess 中,当对具有参照完整性约束的表运行 DELETE 语句时,Jet 将删除它可以删除的记录,并保留其他记录。在这种情况下,它只会删除Clients没有Orders.
在 SQL Server 中,这样做似乎只是失败,并显示一条消息指出The DELETE statement conflicted with the REFERENCE constraint .....

因此,我的问题是:有没有一种简单的方法可以让 SQL Server 只删除那些可以删除的记录?还是我必须添加一个WHERE ClientId NOT IN (SELECT Id FROM Clients)
换句话说,我可以让 SQL ServerDELETE像 Jet 一样工作DELETE吗?

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

4

6 回答 6

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

于 2012-04-12T18:28:33.603 回答
1

您的选择是:

  1. 级联删除 - 将删除依赖于这些客户端的所有记录。
  2. 删除约束(我不推荐这个:))
  3. 提前检查是否可以删除,不会有冲突
于 2012-04-12T15:06:09.070 回答
1

如果您想保留具有 FK 引用的行,那么只有几个选项,而且没有一个是漂亮的:

  1. 在执行删除之前检查约束
  2. 如您在问题中提到的,修改查询以包含 FK 的 where 子句
  3. 更改您的逻辑以一次删除一个行,提交每一行并在删除失败时回滚删除。

“最不糟糕”选项实际上取决于有多少 FK,您将删除多少行以及一行具有 FK 依赖关系的可能性。如果这是一个相对罕见的事件,那么选项#3 可能是最好的,尽管我倾向于倾向于前两个选项。

于 2012-04-12T15:15:37.047 回答
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.
于 2012-04-12T16:43:05.840 回答
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 将被删除

于 2012-04-12T15:18:17.727 回答
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
于 2012-04-12T15:59:13.687 回答