【问题标题】:How to Delete data in Both Tables in SQL Server?如何在 SQL Server 中删除两个表中的数据?
【发布时间】:2017-06-07 08:49:08
【问题描述】:

我有一个表 TableA,它的 PK 是 AId。我有另一个表 TableB,它唯一的列是 BId。 BId 实际上是 AId 的一个子集。现在,我们不能使用 FK(因为这个和那个原因)。

如何删除表B中的TOP 1000行以及表A中与其相关的行(如果表A中存在相同的id)?

一种解决方法是从tableB中SELECT TOP 100)并保存到临时表中,然后用它来删除表A中的数据,然后删除表B中的行。

但它应该有更有效的方法来做到这一点。

我也试过了:

DELETE TOP (1000) FROM tableA WHERE AId IN (SELECT BId FROM TableB)

但是如何确保前 1000 个 BId 被删除?

谢谢

【问题讨论】:

  • 我估计有两个删除步骤,我不知道任何其他方法.. 所以从 TABLEA 中删除(从 TABLEB 中选择前 1000 个键)...从 TABLEB 中删除(选择TOP 1000 KEY FROM TABLEB) ...前提是 tableB 在接下来的一两分钟内保持不变。否则#temptables 应该是要走的路
  • 没有订单不能保证TOP返回同组数据。

标签: sql sql-server


【解决方案1】:

一种方法是创建一个表变量,存储 1000 个 ID 并在两次删除中引用它。当然我不知道它们是否是整数,所以如果需要,请更改数据类型。还有其他方法,我相信其他人会指出,但如果我需要一致性,这是我的标准方法。

DECLARE @keyValues TABLE (keyValue INT);

INSERT INTO @keyValues
SELECT TOP 1000 FROM TABLEA WHERE AID IN (SELECT BID FROM TABLEB);

DELETE FROM TABLEB WHERE BID IN (SELECT keyValue FROM @keyValues);
DELETE FROM TABLEA WHERE AID IN (SELECT keyValue FROM @keyValues);

【讨论】:

  • 谢谢。我很好奇是否有更有效的方法来做到这一点。显然,无论是临时表还是表变量,都会占用一些“额外”的内存/CPU。
  • 您需要将ID的中间列表存储在某处。存储它的唯一其他地方是源表本身(通过直接标记)
【解决方案2】:

一般情况下,一条DELETE 语句只能更改一张表中的数据。 因此,一般来说,您需要两个单独的 DELETE 语句来从两个表中删除行。

从技术上讲,您不必将BId 写入临时表,只需从TableB 读取两次即可。

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN TRANSACTION;

DELETE FROM TableA
WHERE TableA.AId IN
(
    SELECT TOP(1000) TableB.BId
    FROM TableB
    ORDER BY TableB.BId
)
;

DELETE FROM TableB
WHERE TableB.BId IN
(
    SELECT TOP(1000) TableB.BId
    FROM TableB
    ORDER BY TableB.BId
)
;

COMMIT TRANSACTION;

您需要确保 1000 个BId 的集合在两个查询中相同,这意味着您必须将ORDER BYTOP 一起使用。此外,您应该采取措施防止两个 DELETE 语句之间的另一个进程更改 TableB

最后,处理并发问题(例如,通过将事务隔离级别设置为可序列化)可能会比将这 1000 个 Id 写入临时表产生更多的开销。


另一方面,您说“BId 实际上是 AId 的子集”。所以,TableA 是大师,TableB 是细节。

从技术上讲,您可以使用 ON DELETE CASCADE 选项定义外键。我不清楚为什么不能使用外键约束的问题。

ALTER TABLE TableB WITH CHECK ADD CONSTRAINT [FK_TableB_TableA] FOREIGN KEY(BId)
REFERENCES TableA(AId) ON DELETE CASCADE
GO

ALTER TABLE TableB CHECK CONSTRAINT [FK_TableB_TableA]
GO

然后您将仅从TableA 中删除,而TableB 中的子行将被外键约束删除。这是否比两个明确的DELETEs 更有效很难说 - 您需要在硬件上测试自己。

在这种情况下,一个明确的DELETE 就足够了:

DELETE FROM TableA
WHERE TableA.AId IN
(
    SELECT TOP(1000) TableB.BId
    FROM TableB
    ORDER BY TableB.BId
)
;

这也将处理并发问题。

【讨论】:

  • 您可以将其包装在某种事务+隔离级别中,以确保它是相同的集合。
  • @Nick.McDermaid,是的,两个单独的删除应该在一个事务中。
  • 读取它们会比将 TOP 1000 保存在表变量中并从表变量中读取它们慢两倍?
  • 我们不能使用 FK 的原因是,B 是 A 的子集,但有时,我们有另一个进程首先从 A 中删除行,我们确实可以控制它(在这种情况下, B 不再是 A 的子集)。
  • @urlreader,写通常比读慢。使用临时表,您仍然可以读取两次并写入一次。没有临时表,您只能阅读两次。无论如何,第二次读取很可能来自缓存。但是,实际性能应该在您的硬件上进行测试。有很多事情可以影响它。
【解决方案3】:

其他可能的方式:

1) 使用 Output 子句从 TableB 中删除 TOP (1000)(到临时表中),然后从 AID 在该输出表中的 TableA 中删除。

2) 为 TableB 实现 DELETE 触发器,这样当从 TableB 中删除任何行时,它们也会自动从 TableA 中删除。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-07-04
    • 1970-01-01
    • 2011-09-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多