3

I have to remove a row from each of two tables, they're linked by an ID but not with a proper PK - FK relationship (this db has NO foreign keys!)

The tables have a supposed 1-1 relationship. I don't know why they weren't just put in the same table but I'm not at liberty to change it.

People

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

Info

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

To decide what to delete, I have to find a username and whether or not they own a monkey:

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

SO I'm doing two separate statements using this idea, deleting from Info first and then from 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');

I found a promising answer 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')

But it gives a syntax error in Sql Server (2012), I tried it without alias' too, but it doesn't seem possible to delete on two tables at once

jamheadart
  • 5,047
  • 4
  • 32
  • 63
  • 1
    "this db has NO foreign keys!" - you have my sympathy. – Dai Sep 17 '20 at 13:30
  • Please post the **full** syntax error message returned from SQL Server. Your initial `DELETE FROM` statements should work. What version of SSMS are you using? – Dai Sep 17 '20 at 13:33
  • Is there any particular reason you don't want to do two separate statements? – Nabav Sep 17 '20 at 13:33
  • 1
    Also, your double-`DELETE FROM` statements should be wrapped in a `TRY/CATCH` and `BEGIN TRANSACTION/COMMIT` (with a `ROLLBACK` in the `CATCH` block). – Dai Sep 17 '20 at 13:34
  • 2
    A DML statement can only effect one object at a time. If you need multiple tables to be affected you either need to issue multiple DDL statements (in this case `DELETE`) or implement Primary and Foreign Keys with `CASCADE` enabled. – Thom A Sep 17 '20 at 13:41
  • @Dai the error is at the first comma `Incorrect syntax near ','` – jamheadart Sep 17 '20 at 13:48
  • I was hoping to avoid two separate statements on the off-chance the second doesn't work for whatever reason, interrupted - concurrency really, I guess the `TRY/CATCH` will work well for that. – jamheadart Sep 17 '20 at 13:50
  • What I don't understand is the confusion around the double delete statement - some say it works, some say it doesn't. I'm using SMSS 18 connected to Sql Server 2012 db. I'm guessing it's just a version discrepancy somewhere. – jamheadart Sep 17 '20 at 13:51
  • 2
    @jamheadart `TRY/CATCH` is necessary but insufficient. You **need** to combine it with a `BEGIN TRANSACTION` + `COMMIT` (with `ROLLBACK` in the `CATCH` block). – Dai Sep 17 '20 at 13:54
  • @Dai sorry yeah I did mean all statements you mentioned just didn't want to reiterate the whole lot :D – jamheadart Sep 17 '20 at 13:56

1 Answers1

5

Can I delete entries from two tables in one statement?

No. One statement can delete rows only from one table in MS SQL Server.

The answer that you refer to talks about MySQL and MySQL indeed allows to delete from several tables with one statement, as can be seen in the MySQL docs. MS SQL Server doesn't support this, as can be seen in the docs. There is no syntax to include more than one table in the DELETE statement in SQL Server. If you try to delete from a view, rather than a table, there is a limitation as well:

The view referenced by table_or_view_name must be updatable and reference exactly one base table in the FROM clause of the view definition.


I was hoping to avoid two separate statements on the off-chance the second doesn't work for whatever reason, interrupted - concurrency really, I guess the TRY/CATCH will work well for that.

This is what transactions are for. You can put several statements in a transaction and either all of them would succeed, or all of them would fail. Either all or nothing.

In your case you not just can, but should put both DELETE statements in a transaction.

TRY/CATCH helps to process possible errors in a more controlled way, but the primary concept is "transaction".

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

I highly recommend to read a great article Error and Transaction Handling in SQL Server by Erland Sommarskog.


If you try to be tricky, like this:

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;

You'll get an error:

View or function 'CTE' is not updatable because the modification affects multiple base tables.

Or like this:

WITH
CTE
AS
(
    SELECT
    PersonId
    FROM Info

    UNION ALL

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

You'll get another error:

View 'CTE' is not updatable because the definition contains a UNION operator.

Vladimir Baranov
  • 31,799
  • 5
  • 53
  • 90
  • 2
    It needs SET XACT_ABORT ON to rollback completely when there are multiple DML statements within the transaction – SteveC Sep 17 '20 at 14:52
  • Thank you for the comprehensive answer - do you have any idea regarding the `delete a.*, b.*` part of my question? This was an accepted answer on SO and some people say the syntax is fine - I'm wondering where this syntax is fine! – jamheadart Sep 17 '20 at 14:58
  • 2
    @jamheadart Because MySQL and SQL Server have different sql dialects, different features, and different rules. There is a great deal of overlap in functionality supported by both, but also major differences. – SMor Sep 17 '20 at 15:17
  • 1
    @jamheadart, I'm surprised to see that MySQL allows to delete from several tables with one statement, but it indeed does, as can be seen in the [docs](https://dev.mysql.com/doc/refman/8.0/en/delete.html). MS SQL doesn't support this, as can be seen in the [docs](https://learn.microsoft.com/en-us/sql/t-sql/statements/delete-transact-sql?view=sql-server-ver15). – Vladimir Baranov Sep 17 '20 at 23:12
  • Ok thanks. I nearly suggested it might be a MySQL thing because the linked question was for MySQL, it's just that someone in the comments said they had "no problem with the statements in SQL Server" - I think they were just telling lies! – jamheadart Sep 17 '20 at 23:23
  • @jamheadart, I've seen many times that people misread MySQL and MS SQL. It is only one letter difference, but significantly different software. Some people may not realise that it is two different RDMSs. – Vladimir Baranov Sep 17 '20 at 23:26
  • 1
    @SteveC, it **is** recommended to `SET XACT_ABORT ON` and I do it in all of my stored procedures. I didn't mention it in the answer, because I thought it was out of scope. This option affects how you can and/or should handle possible errors. But, setting this option (or not) would not change the primary behaviour of the transaction - either all or nothing. There is a great article about [Error and Transaction Handling in SQL Server](http://www.sommarskog.se/error_handling/Part1.html#jumpXACT_ABORT) by Erland Sommarskog. – Vladimir Baranov Sep 17 '20 at 23:34
  • 1
    @SteveC, sorry, I was wrong here about "all or nothing" regardless of the `XACT_ABORT` option. Documentation about [`XACT_ABORT`](https://docs.microsoft.com/en-us/sql/t-sql/statements/set-xact-abort-transact-sql?view=sql-server-ver15) shows an example where `SET XACT_ABORT` is `OFF` and only the statement that raised the error is rolled back and the transaction continues processing. When `SET XACT_ABORT` is `ON`, if a statement raises a run-time error, the entire transaction is terminated and rolled back. So, yes, you'd better `SET XACT_ABORT ON` unless you really know what you are doing. – Vladimir Baranov Sep 19 '20 at 01:03