删除相交

DELETE WITH INTERSECT

我有两个 table 具有相同的列数但没有主键(我知道,这不是我的错)。现在我需要删除 table A 中存在于 table B 中的所有行(它们相等,每行有 30 列)。

我认为最直接的方法是 INNER JOIN 并解决我的问题。但是,所有列的写入条件(担心 NULL)并不优雅(可能是因为我的 table 也不优雅)。

我想使用 INTERSECT。我不知道该怎么做?这是我的第一个问题:

我试过了(SQL Fiddle):

declare @A table (value int, username varchar(20))
declare @B table (value int, username varchar(20))

insert into @A values (1, 'User 1'), (2, 'User 2'), (3, 'User 3'), (4, 'User 4')
insert into @B values (2, 'User 2'), (4, 'User 4'), (5, 'User 5')

DELETE @A 
    FROM (SELECT * FROM @A INTERSECT SELECT * from @B) A

但是 table @A.

中的所有行都被删除了

这让我想到了第二个问题:为什么命令 DELETE @A FROM @B 会删除 table @A 中的所有行?

要回答你的第一个问题,你可以根据join删除:

delete a 
from @a a
join @b b on a.value = b.value and a.username = b.username

第二种情况真是奇怪。我记得这里有类似的案例,还有很多关于这种行为的投诉。我会尝试解决这个问题。

  1. 创建一个table(T)定义主键
  2. 将 A 中的所有记录插入到 T 中(我假设 A 中没有重复项)
  3. 尝试将 B 中的所有记录插入 T 中 3A.如果插入失败从 B 中删除它(已经存在)
  4. 掉T(你真的不应该!!!)

您可以使用 Giorgi 的回答来删除您需要的行。

关于为什么删除所有行的问题,那是因为没有限制条件。您的 FROM 子句得到一个 table 来处理,但是没有 WHERE 子句来防止某些行从 @A.

中删除

Giorgi 的回答明确比较了您希望避免的所有列。 可以编写不显式列出所有列的代码。 EXCEPT 生成您需要的结果集,但我不知道将此结果集用于 DELETE 来自 A 没有主键的原始行的好方法。因此,下面的解决方案使用 SELECT * INTO 将此中间结果保存在临时 table 中。然后删除 A 中的所有内容并将临时结果复制到 A 中。将其包装在交易中。

-- generate the final result set that we want to have and save it in a temporary table
SELECT *
INTO #t
FROM
(
    SELECT * FROM @A
    EXCEPT
    SELECT * FROM @B
) AS E;

-- copy temporary result back into A
DELETE FROM @A;

INSERT INTO @A
SELECT * FROM #t;

DROP TABLE #t;

-- check the result
SELECT * FROM @A;

结果集

value    username
1        User 1
3        User 3

这个解决方案好的一面是它使用 * 而不是完整的列列表。当然,您也可以明确列出所有列。它仍然比编写所有列的比较和处理可能的 NULL 更容易编写和处理。

试试这个:

DELETE a 
FROM @A a
WHERE EXISTS (SELECT a.* INTERSECT SELECT * FROM @B)

从@A 中删除,其中对于@A 中的每条记录,@A 中的记录与@B 中的记录相交。

这是基于 Paul White blog post 使用 INTERSECT 进行不等式检查。

SQL Fiddle