【问题标题】:Can I delete entries from two tables in one statement?我可以在一个语句中从两个表中删除条目吗?
【发布时间】:2020-09-17 13:29:03
【问题描述】:

我必须从两个表中的每一个中删除一行,它们由一个 ID 链接,但没有正确的 PK - FK 关系(这个数据库没有外键!)

这些表具有假定的 1-1 关系。我不知道为什么它们不只是放在同一张桌子上,但我无权更改它。

PersonId | Name | OwnsMonkey
----------------------------
    1       Jim       true
    2       Jim       false
    3       Gaz       true

信息

PersonId |    FurtherInfo
-----------------------------
    1       Hates his monkey
    2        Wants a monkey
    3       Loves his monkey

要决定删除什么,我必须找到用户名以及他们是否拥有猴子:

Select PersonId from People where Name = 'Jim' and OwnsMonkey = 'false'

所以我正在使用这个想法做两个单独的语句,首先从 Info 删除,然后从 People 删除

delete from Info where PersonId = (Select PersonId from People where Name = 'Jim' and OwnsMonkey = 'false');

delete from People where PersonId = (Select PersonId from People where Name = 'Jim' and OwnsMonkey = 'false');

我找到了一个有希望的答案here on StackOverflow

delete      a.*, b.*
from        People a
inner join  Info b
where       a.People = b.Info
            and a.PersonId = 
            (Select PersonId from People where Name = 'Jim' and OwnsMonkey = 'false')

但它在 Sql Server (2012) 中给出了语法错误,我也尝试过不使用别名,但似乎无法同时删除两个表

【问题讨论】:

  • "这个数据库没有外键!" - 你有我的同情。
  • 请发布从 SQL Server 返回的 full 语法错误消息。您最初的 DELETE FROM 语句应该有效。您使用的是什么版本的 SSMS?
  • 另外,您的双DELETE FROM 语句应包含在TRY/CATCHBEGIN TRANSACTION/COMMIT 中(ROLLBACKCATCH 块中)。
  • 一个 DML 语句一次只能影响一个对象。如果您需要影响多个表,您要么需要发出多个 DDL 语句(在本例中为 DELETE),要么在启用 CASCADE 的情况下实现主键和外键。
  • @jamheadart TRY/CATCH 是必要的,但还不够。您需要将它与BEGIN TRANSACTION + COMMIT 结合起来(在CATCH 块中使用ROLLBACK)。

标签: sql sql-server sql-server-2012


【解决方案1】:

我可以在一个语句中从两个表中删除条目吗?

没有。在 MS SQL Server 中,一条语句只能删除一张表中的行。

您提到的关于 MySQL 和 MySQL 的答案确实允许使用一条语句从多个表中删除,如MySQL docs 所示。 MS SQL Server 不支持这一点,可以在docs 中看到。 SQL Server 中的DELETE 语句中没有包含多个表的语法。如果您尝试从视图而不是表中删除,也存在限制:

table_or_view_name 引用的视图必须是可更新的并且 在视图的 FROM 子句中准确引用一个基表 定义。


我希望避免两个单独的声明 无论出于何种原因,第二个都不起作用,被中断 - 并发 真的,我想 TRY/CATCH 可以很好地解决这个问题。

这就是交易的用途。 您可以在事务中放置多个语句,它们要么全部成功,要么全部失败。要么全有,要么全无。

在您的情况下,您不仅可以,而且应该将两个 DELETE 语句放在一个事务中。

TRY/CATCH 有助于以更可控的方式处理可能出现的错误,但主要概念是“事务”。

BEGIN TRANSACTION

delete from Info where PersonId = (Select PersonId from People where Name = 'Jim' and OwnsMonkey = 'false');

delete from People where PersonId = (Select PersonId from People where Name = 'Jim' and OwnsMonkey = 'false');

COMMIT

我强烈推荐阅读 Erland Sommarskog 的一篇精彩文章 Error and Transaction Handling in SQL Server


如果你想耍花招,像这样:

WITH
CTE
AS
(
    SELECT
        Info.PersonId AS ID1, People.PersonId AS ID2
    FROM
        Info
        INNER JOIN People ON Info.PersonId = People.PersonId
)
DELETE FROM CTE
WHERE ID1 = 1;

你会得到一个错误:

视图或函数“CTE”不可更新,因为修改 影响多个基表。

或者像这样:

WITH
CTE
AS
(
    SELECT
    PersonId
    FROM Info

    UNION ALL

    SELECT
    PersonId
    FROM People
)
DELETE FROM CTE
WHERE PersonId = 1;

你会得到另一个错误:

视图“CTE”不可更新,因为定义包含 UNION 运算符。

【讨论】:

  • 事务中有多个DML语句时需要SET XACT_ABORT ON才能完全回滚
  • @jamheadart 因为 MySQL 和 SQL Server 的 sql 方言不同,特性不同,规则不同。两者支持的功能有很多重叠之处,但也存在重大差异。
  • @jamheadart,我很惊讶地看到 MySQL 允许使用一条语句从多个表中删除,但确实如此,正如 docs 中所见。 MS SQL 不支持这一点,可以在docs 中看到。
  • @SteveC,它推荐给SET XACT_ABORT ON,我在所有存储过程中都这样做。我没有在答案中提到它,因为我认为它超出了范围。此选项会影响您可以和/或应该如何处理可能的错误。但是,设置这个选项(或不设置)不会改变交易的主要行为——要么全部要么什么都没有。 Erland Sommarskog 有一篇关于 Error and Transaction Handling in SQL Server 的精彩文章。
  • @SteveC,对不起,不管XACT_ABORT 选项如何,我在这里关于“全有或全无”的说法是错误的。关于XACT_ABORT 的文档显示了一个示例,其中SET XACT_ABORTOFF,并且只有引发错误的语句被回滚并且事务继续处理。当SET XACT_ABORTON 时,如果语句引发运行时错误,则整个事务将终止并回滚。所以,是的,除非你真的知道自己在做什么,否则你最好 SET XACT_ABORT ON
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-06
  • 2017-12-03
  • 1970-01-01
  • 1970-01-01
  • 2015-09-16
  • 2013-04-13
相关资源
最近更新 更多