在SQL Server中,UPDATE和DELETE語句是可以結合INNER/LEFT/RIGHT/FULL JOIN來使用的。
我們首先在數據庫中新建兩張表:
[T_A]
CREATE TABLE [dbo].[T_A]( [ID] [int] NOT NULL, [Name] [nvarchar](50) NULL, [Age] [int] NULL, CONSTRAINT [PK_T_A] PRIMARY KEY CLUSTERED ( [ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY]
[T_B]
CREATE TABLE [dbo].[T_B]( [ID] [int] NOT NULL, [Name] [nvarchar](50) NULL, [Age] [int] NULL, CONSTRAINT [PK_T_B] PRIMARY KEY CLUSTERED ( [ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY]
UPDATE與INNER/LEFT/RIGHT/FULL JOIN
UPDATE結合INNER JOIN:
TRUNCATE TABLE [T_A]; TRUNCATE TABLE [T_B]; INSERT INTO [T_A]([ID],[Name],[Age]) VALUES (1,N'Tome',10), (2,N'Jack',20), (3,N'Jim',30), (4,N'Mike',40), (5,N'Bob',50); INSERT INTO [T_B]([ID],[Name],[Age]) VALUES (1,N'Tome',100), (2,N'Jack',200), (3,N'Jim',300); UPDATE [T_A] SET Age=[T_B].Age FROM [T_A] INNER JOIN [T_B] ON [T_A].ID=[T_B].ID; SELECT * FROM [dbo].[T_A];
表[T_A]的結果如下所示:
其效果相當於通過下面INNER JOIN查詢,先找出表[T_A]的數據記錄,然后UPDATE這些找出的數據記錄:
SELECT [T_A].*, [T_B].* FROM [T_A] INNER JOIN [T_B] ON [T_A].ID=[T_B].ID;
注意如果表[T_A]中的某行數據與表[T_B]中多行數據匹配上,這種情況下,表[T_A]的該行數據也只會被UPDATE一次,不過用表[T_B]中的哪一行匹配數據去UPDATE表[T_A]是不確定的。
UPDATE結合LEFT JOIN:
TRUNCATE TABLE [T_A]; TRUNCATE TABLE [T_B]; INSERT INTO [T_A]([ID],[Name],[Age]) VALUES (1,N'Tome',10), (2,N'Jack',20), (3,N'Jim',30), (4,N'Mike',40), (5,N'Bob',50); INSERT INTO [T_B]([ID],[Name],[Age]) VALUES (1,N'Tome',100), (2,N'Jack',200), (3,N'Jim',300); UPDATE [T_A] SET Age=[T_B].Age FROM [T_A] LEFT JOIN [T_B] ON [T_A].ID=[T_B].ID; SELECT * FROM [dbo].[T_A];
表[T_A]的結果如下所示:
其效果相當於通過下面LEFT JOIN查詢,先找出表[T_A]的數據記錄,然后UPDATE這些找出的數據記錄:
SELECT [T_A].*, [T_B].* FROM [T_A] LEFT JOIN [T_B] ON [T_A].ID=[T_B].ID;
UPDATE結合RIGHT JOIN:
TRUNCATE TABLE [T_A]; TRUNCATE TABLE [T_B]; INSERT INTO [T_A]([ID],[Name],[Age]) VALUES (1,N'Tome',10), (2,N'Jack',20), (3,N'Jim',30), (4,N'Mike',40), (5,N'Bob',50); INSERT INTO [T_B]([ID],[Name],[Age]) VALUES (1,N'Tome',100), (2,N'Jack',200), (3,N'Jim',300), (4,N'Mike',400), (5,N'Bob',500), (6,N'Clark',600), (7,N'Sam',700); UPDATE [T_A] SET Age=[T_B].Age FROM [T_A] RIGHT JOIN [T_B] ON [T_A].ID=[T_B].ID; SELECT * FROM [dbo].[T_A];
表[T_A]的結果如下所示:
其效果相當於通過下面RIGHT JOIN查詢,先找出表[T_A]的數據記錄,然后UPDATE這些找出的數據記錄:
SELECT [T_A].*, [T_B].* FROM [T_A] RIGHT JOIN [T_B] ON [T_A].ID=[T_B].ID;
UPDATE結合FULL JOIN:
TRUNCATE TABLE [T_A]; TRUNCATE TABLE [T_B]; INSERT INTO [T_A]([ID],[Name],[Age]) VALUES (1,N'Tome',10), (2,N'Jack',20), (3,N'Jim',30), (4,N'Mike',40), (5,N'Bob',50); INSERT INTO [T_B]([ID],[Name],[Age]) VALUES (1,N'Tome',100), (2,N'Jack',200), (3,N'Jim',300); UPDATE [T_A] SET Age=[T_B].Age FROM [T_A] FULL JOIN [T_B] ON [T_A].ID=[T_B].ID; SELECT * FROM [dbo].[T_A];
表[T_A]的結果如下所示:
其效果相當於通過下面FULL JOIN查詢,先找出表[T_A]的數據記錄,然后UPDATE這些找出的數據記錄:
SELECT [T_A].*, [T_B].* FROM [T_A] FULL JOIN [T_B] ON [T_A].ID=[T_B].ID;
DELETE與INNER/LEFT/RIGHT/FULL JOIN
DELETE結合INNER JOIN:
TRUNCATE TABLE [T_A]; TRUNCATE TABLE [T_B]; INSERT INTO [T_A]([ID],[Name],[Age]) VALUES (1,N'Tome',10), (2,N'Jack',20), (3,N'Jim',30), (4,N'Mike',40), (5,N'Bob',50); INSERT INTO [T_B]([ID],[Name],[Age]) VALUES (1,N'Tome',100), (2,N'Jack',200), (3,N'Jim',300); DELETE [T_A] FROM [T_A] INNER JOIN [T_B] ON [T_A].ID=[T_B].ID; SELECT * FROM [dbo].[T_A];
表[T_A]的結果如下所示:
其效果相當於通過下面INNER JOIN查詢,先找出表[T_A]的數據記錄,然后DELETE這些找出的數據記錄:
SELECT [T_A].* FROM [T_A] INNER JOIN [T_B] ON [T_A].ID=[T_B].ID;
DELETE結合LEFT JOIN:
TRUNCATE TABLE [T_A]; TRUNCATE TABLE [T_B]; INSERT INTO [T_A]([ID],[Name],[Age]) VALUES (1,N'Tome',10), (2,N'Jack',20), (3,N'Jim',30), (4,N'Mike',40), (5,N'Bob',50); INSERT INTO [T_B]([ID],[Name],[Age]) VALUES (1,N'Tome',100), (2,N'Jack',200), (3,N'Jim',300); DELETE [T_A] FROM [T_A] LEFT JOIN [T_B] ON [T_A].ID=[T_B].ID; SELECT * FROM [dbo].[T_A];
表[T_A]的結果如下所示:
其效果相當於通過下面LEFT JOIN查詢,先找出表[T_A]的數據記錄,然后DELETE這些找出的數據記錄:
SELECT [T_A].* FROM [T_A] LEFT JOIN [T_B] ON [T_A].ID=[T_B].ID;
DELETE結合RIGHT JOIN:
TRUNCATE TABLE [T_A]; TRUNCATE TABLE [T_B]; INSERT INTO [T_A]([ID],[Name],[Age]) VALUES (1,N'Tome',10), (2,N'Jack',20), (3,N'Jim',30), (4,N'Mike',40), (5,N'Bob',50); INSERT INTO [T_B]([ID],[Name],[Age]) VALUES (1,N'Tome',100), (2,N'Jack',200), (3,N'Jim',300), (4,N'Mike',400), (5,N'Bob',500), (6,N'Clark',600), (7,N'Sam',700); DELETE [T_A] FROM [T_A] RIGHT JOIN [T_B] ON [T_A].ID=[T_B].ID; SELECT * FROM [dbo].[T_A];
表[T_A]的結果如下所示:
其效果相當於通過下面RIGHT JOIN查詢,先找出表[T_A]的數據記錄,然后DELETE這些找出的數據記錄:
SELECT [T_A].* FROM [T_A] RIGHT JOIN [T_B] ON [T_A].ID=[T_B].ID;
DELETE結合FULL JOIN:
TRUNCATE TABLE [T_A]; TRUNCATE TABLE [T_B]; INSERT INTO [T_A]([ID],[Name],[Age]) VALUES (1,N'Tome',10), (2,N'Jack',20), (3,N'Jim',30), (4,N'Mike',40), (5,N'Bob',50); INSERT INTO [T_B]([ID],[Name],[Age]) VALUES (1,N'Tome',100), (2,N'Jack',200), (3,N'Jim',300); DELETE [T_A] FROM [T_A] FULL JOIN [T_B] ON [T_A].ID=[T_B].ID; SELECT * FROM [dbo].[T_A];
表[T_A]的結果如下所示:
其效果相當於通過下面FULL JOIN查詢,先找出表[T_A]的數據記錄,然后DELETE這些找出的數據記錄:
SELECT [T_A].* FROM [T_A] FULL JOIN [T_B] ON [T_A].ID=[T_B].ID;
JOIN語句使用子查詢
其實我們還可以在UPDATE和DELETE語句使用JOIN時,對UPDATE和DELETE的表使用子查詢,但是這種用法我個人不推薦,我們來看一個UPDATE的例子:
TRUNCATE TABLE [T_A]; TRUNCATE TABLE [T_B]; INSERT INTO [T_A]([ID],[Name],[Age]) VALUES (1,N'Tome',10), (2,N'Jack',20), (3,N'Jim',30), (4,N'Mike',40), (5,N'Bob',50); INSERT INTO [T_B]([ID],[Name],[Age]) VALUES (1,N'Tome',100), (2,N'Jack',200), (3,N'Jim',300); UPDATE [T_A] SET Age=[T_B].Age FROM ( SELECT * FROM [T_A] WHERE [T_A].ID<=2 ) AS [T_A] INNER JOIN [T_B] ON [T_A].ID=[T_B].ID; SELECT * FROM [dbo].[T_A];
表[T_A]的結果如下所示:
可以看到由於我們現在對表[T_A]做了子查詢,用WHERE條件限制了其ID<=2,所以子查詢只會返回表[T_A]的兩條數據,因此最終表[T_A]只有兩條數據得到了更新。
這個結果是符合我們預期的,但是其中有一個很重要的因素,就是UPDATE關鍵字后面的表名要和子查詢的別名一致,我們對上面的UPDATE語句稍作修改,如下所示:
TRUNCATE TABLE [T_A]; TRUNCATE TABLE [T_B]; INSERT INTO [T_A]([ID],[Name],[Age]) VALUES (1,N'Tome',10), (2,N'Jack',20), (3,N'Jim',30), (4,N'Mike',40), (5,N'Bob',50); INSERT INTO [T_B]([ID],[Name],[Age]) VALUES (1,N'Tome',100), (2,N'Jack',200), (3,N'Jim',300); UPDATE [T_A] SET Age=[T_B].Age FROM ( SELECT * FROM [T_A] WHERE [T_A].ID<=2 ) AS [T_A_1] INNER JOIN [T_B] ON [T_A_1].ID=[T_B].ID; SELECT * FROM [dbo].[T_A];
現在我們將子查詢的名字命名為了[T_A_1],但是我們UPDATE的表是[T_A],上面語句執行后,表[T_A]的結果如下所示:
我們可以看到表[T_A]的所有數據都被莫名其妙地更新了,我們來看看UPDATE語句的執行計划,如下所示:
我們可以看到最關鍵的一個步驟,也就是表[T_A]和JOIN結果集之間的"Nested Loops"這個JOIN有個警告:"No Join Predicate",其含義就是說表[T_A]和JOIN結果集之間的JOIN是沒有ON條件的,相當於CROSS JOIN,所以我們最后才看到表[T_A]的所有數據都被莫名其妙地更新了。這是因為現在UPDATE語句后面的表名[T_A]和子查詢的命名[T_A_1]不一致,所以UPDATE語句現在不知道如何將[T_A_1]和[T_B]之間INNER JOIN后的結果集對應到UPDATE的表[T_A]中,所以就將表[T_A]的所有數據都更新了。
要解決這個問題其實也很簡單,只要將UPDATE語句后面的表名改為子查詢的名字[T_A_1],使得UPDATE語句后面的表名和子查詢的名字一致就行了,如下所示:
TRUNCATE TABLE [T_A]; TRUNCATE TABLE [T_B]; INSERT INTO [T_A]([ID],[Name],[Age]) VALUES (1,N'Tome',10), (2,N'Jack',20), (3,N'Jim',30), (4,N'Mike',40), (5,N'Bob',50); INSERT INTO [T_B]([ID],[Name],[Age]) VALUES (1,N'Tome',100), (2,N'Jack',200), (3,N'Jim',300); UPDATE [T_A_1] SET Age=[T_B].Age FROM ( SELECT * FROM [T_A] WHERE [T_A].ID<=2 ) AS [T_A_1] INNER JOIN [T_B] ON [T_A_1].ID=[T_B].ID; SELECT * FROM [dbo].[T_A];
現在,表[T_A]的結果如下所示:
可以看到這次UPDATE語句正確地只更新了表[T_A]的兩行數據,我們看看UPDATE語句的執行計划:
可以看到這次"Nested Loops"沒有任何警告,正確地將表[T_A]和[T_B]進行了INNER JOIN,所以UPDATE語句只更新了表[T_A]的兩行數據。這說明雖然我們在UPDATE語句后面寫的是子查詢的名字[T_A_1],但是UPDATE語句還是可以根據子查詢[T_A_1]知道要更新的表實際上是[T_A],不得不說這一點SQL Server還是挺智能的。
但是鑒於在UPDATE和DELETE語句中使用JOIN時,再對UPDATE和DELETE的表使用子查詢看起來比較怪,並且如上所示,用得不對會造成結果出錯,所以我個人還是不推薦在UPDATE和DELETE語句中使用JOIN時,再對UPDATE和DELETE的表使用子查詢,況且這種子查詢實際上完全可以用其它方式來替代。
總結
舉了這么多例子,其實我個人覺得UPDATE和DELETE語句與INNER JOIN結合使用才是最有用的,但是不管是什么JOIN,從上面的例子可以看出,其實都相當於是先用SELECT語句做表[T_A]的INNER/LEFT/RIGHT/FULL JOIN查詢,然后UPDATE或DELETE表[T_A]中查詢出的這些數據記錄。
參考文獻