为什么我的更新语句会更新每一行?

Sha*_*tor 8 sql-server update

问题:

由于语法和回滚事务,我的最终更新语句应该出错,预期的错误应该类似于:

消息 207,级别 16,状态 1,第 45 行无效的列名称“FakeTest1”

我很好奇当允许执行引用不存在的列的子查询时,为什么 SQL Server 更新列中的每一行。

背景

我正在创建一个包含两列的临时表,FakeID并且FakeVarchar.

 SELECT  [FakeID], [FakeVarchar]
 INTO [#T]
 FROM [CTE];
Run Code Online (Sandbox Code Playgroud)

当我的UPDATE语句使用此WHERE子句在 中指定列 [ID] 时SELECT SUBQUERY,它会更新整个表,而不是在解析命令时出错。

WHERE [FakeTableDestination].[ID] IN
(
    SELECT [ID]
    FROM [#T]
);
Run Code Online (Sandbox Code Playgroud)

应该在子查询中以使其正确运行的列将是FakeID. 此语句显然有效,并且按预期更新。

WHERE [FakeTableDestination].[ID] IN
(
    SELECT [FakeID]
    FROM [#T]
);
Run Code Online (Sandbox Code Playgroud)

当我用任何其他列名替换 FakeID / ID 时,它会按预期失败。

WHERE [FakeTableDestination].[ID] IN
(
    SELECT [NotActuallyAColumnInThisTableEither]
    FROM [#T]
);
Run Code Online (Sandbox Code Playgroud)

完整的 SQL 代码,当它应该出错时更新整个表

IF OBJECT_ID('dbo.FakeTableSource', 'U') IS NOT NULL
BEGIN
    DROP TABLE [dbo].[FakeTableSource];
END;

CREATE TABLE [dbo].[FakeTableSource]
( 
             [FakeID] INT PRIMARY KEY, [FakeVarchar] VARCHAR(10)
);

GO

IF OBJECT_ID('tempdb.dbo.#T', 'U') IS NOT NULL
BEGIN
    DROP TABLE [#T];
END;

IF OBJECT_ID('dbo.FakeTableDestination', 'U') IS NOT NULL
BEGIN
    DROP TABLE [dbo].[FakeTableDestination];
END;

CREATE TABLE [dbo].[FakeTableDestination]
( 
             [ID] INT PRIMARY KEY, [FakeVarchar] VARCHAR(10)
);

GO

INSERT INTO [dbo].[FakeTableSource]( [FakeID], [FakeVarchar] )
VALUES( 1, 'One' ), ( 2, 'Two' ), ( 3, 'Three' ), ( 4, 'Four' );

INSERT INTO [dbo].[FakeTableDestination]( [ID], [FakeVarchar] )
VALUES( 1, 'One' ), ( 2, 'Two' ), ( 3, 'Five' ), ( 5, 'Four' );

WITH CTE
     AS (SELECT [FakeID], [FakeVarchar]
         FROM [dbo].[FakeTableSource])
     SELECT  [FakeID], [FakeVarchar]
     INTO [#T]
     FROM [CTE];

UPDATE [dbo].[FakeTableDestination]
  SET [FakeVarchar] = [src].[FakeVarchar]
FROM [dbo].[FakeTableSource] AS [SRC]
WHERE [FakeTableDestination].[ID] IN
(
    SELECT [ID]
    FROM [#T]
);

SELECT *
FROM [dbo].[FakeTableSource];

SELECT *
FROM [dbo].[FakeTableDestination];

SELECT *
FROM [#t];
Run Code Online (Sandbox Code Playgroud)

语法正确并按预期更新值的完整 SQL 代码

IF OBJECT_ID('dbo.FakeTableSource', 'U') IS NOT NULL
BEGIN
    DROP TABLE [dbo].[FakeTableSource];
END;

CREATE TABLE [dbo].[FakeTableSource]
( 
             [FakeID] INT PRIMARY KEY, [FakeVarchar] VARCHAR(10)
);

GO

IF OBJECT_ID('tempdb.dbo.#T', 'U') IS NOT NULL
BEGIN
    DROP TABLE [#T];
END;

IF OBJECT_ID('dbo.FakeTableDestination', 'U') IS NOT NULL
BEGIN
    DROP TABLE [dbo].[FakeTableDestination];
END;

CREATE TABLE [dbo].[FakeTableDestination]
( 
             [ID] INT PRIMARY KEY, [FakeVarchar] VARCHAR(10)
);

GO

INSERT INTO [dbo].[FakeTableSource]( [FakeID], [FakeVarchar] )
VALUES( 1, 'One' ), ( 2, 'Two' ), ( 3, 'Three' ), ( 4, 'Four' );

INSERT INTO [dbo].[FakeTableDestination]( [ID], [FakeVarchar] )
VALUES( 1, 'One' ), ( 2, 'Two' ), ( 3, 'Five' ), ( 5, 'Four' );

WITH CTE
     AS (SELECT [FakeID], [FakeVarchar]
         FROM [dbo].[FakeTableSource])
     SELECT  [FakeID], [FakeVarchar]
     INTO [#T]
     FROM [CTE];

UPDATE [dbo].[FakeTableDestination]
  SET [FakeVarchar] = [src].[FakeVarchar]
FROM [dbo].[FakeTableSource] AS [SRC]
WHERE [FakeTableDestination].[ID] IN
(
    SELECT [FakeID]
    FROM [#T]
);

SELECT *
FROM [dbo].[FakeTableSource];

SELECT *
FROM [dbo].[FakeTableDestination];

SELECT *
FROM [#t];
Run Code Online (Sandbox Code Playgroud)

我发现了什么

WHERE条款似乎是允许这种UPDATE情况发生的原因。例如,我会将列名FakeTableDestination从更改FakeIDTest。然后我可以引用该Test列,#T即使该列的别名仍为FakeID. 这会导致整个列在更新语句期间更新。

说明该行为的 SQL 代码

IF OBJECT_ID('dbo.FakeTableSource', 'U') IS NOT NULL
BEGIN
    DROP TABLE [dbo].[FakeTableSource];
END;

CREATE TABLE [dbo].[FakeTableSource]
( 
             [FakeID] INT PRIMARY KEY, [FakeVarchar] VARCHAR(10)
);

GO

IF OBJECT_ID('tempdb.dbo.#T', 'U') IS NOT NULL
BEGIN
    DROP TABLE [#T];
END;

IF OBJECT_ID('dbo.FakeTableDestination', 'U') IS NOT NULL
BEGIN
    DROP TABLE [dbo].[FakeTableDestination];
END;

CREATE TABLE [dbo].[FakeTableDestination]
( 
             [Test] INT PRIMARY KEY, [FakeVarchar] VARCHAR(10)
);

GO

INSERT INTO [dbo].[FakeTableSource]( [FakeID], [FakeVarchar] )
VALUES( 1, 'One' ), ( 2, 'Two' ), ( 3, 'Three' ), ( 4, 'Four' );

INSERT INTO [dbo].[FakeTableDestination]( [Test], [FakeVarchar] )
VALUES( 1, 'One' ), ( 2, 'Two' ), ( 3, 'Five' ), ( 5, 'Four' );

WITH CTE
     AS (SELECT [FakeID], [FakeVarchar]
         FROM [dbo].[FakeTableSource])
     SELECT  [FakeID], [FakeVarchar]
     INTO [#T]
     FROM [CTE];


UPDATE [dbo].[FakeTableDestination]
  SET [FakeVarchar] = [src].[FakeVarchar]
FROM [dbo].[FakeTableSource] AS [SRC]
WHERE [FakeTableDestination].[Test] IN
(
    SELECT [Test]
    FROM [#T]
);

SELECT *
FROM [dbo].[FakeTableSource];

SELECT *
FROM [dbo].[FakeTableDestination];

SELECT *
FROM [#t];
Run Code Online (Sandbox Code Playgroud)

我尝试/想到的事情

如果 ID 是临时表中列的名称并且它是隐藏的怎么办?

SELECT 
       *
FROM [tempdb].[sys].[syscolumns]
WHERE [name] = 'FakeID';
Run Code Online (Sandbox Code Playgroud)

好的,我看到这里列出了 ID,我看到了其他列,如 xtype、typestat 等。那么如果我尝试使用其他列之一呢?我尝试替换 xtype / typestat 并且它们在语法上也失败了。

也许是因为您使用的是临时表?

我对表变量和物理表也有同样的行为。

表变量代码

IF OBJECT_ID('dbo.FakeTableSource', 'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[FakeTableSource];
END;

CREATE TABLE [dbo].[FakeTableSource]
([FakeID]      INT
 PRIMARY KEY, 
 [FakeVarchar] VARCHAR(10)
);

GO

IF OBJECT_ID('dbo.FakeTableDestination', 'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[FakeTableDestination];
END;

CREATE TABLE [dbo].[FakeTableDestination]
([ID]          INT
 PRIMARY KEY, 
 [FakeVarchar] VARCHAR(10)
);

GO

DECLARE @T TABLE
([FakeID]      INT
 PRIMARY KEY, 
 [FakeVarchar] VARCHAR(10)
);

INSERT INTO [dbo].[FakeTableSource]
(
       [FakeID], 
       [FakeVarchar]
)
VALUES
       (1, 
        'One'),
       (2, 
        'Two'),
       (3, 
        'Three'),
       (4, 
        'Four');

INSERT INTO [dbo].[FakeTableDestination]
(
       [ID], 
       [FakeVarchar]
)
VALUES
       (1, 
        'One'),
       (2, 
        'Two'),
       (3, 
        'Five'),
       (5, 
        'Four');

WITH CTE
     AS (SELECT 
                [FakeID], 
                [FakeVarchar]
         FROM [dbo].[FakeTableSource])

     INSERT INTO @T
     (
            [FakeID], 
            [FakeVarchar]
     )
     SELECT 
            [FakeID], 
            [FakeVarchar]
     FROM [CTE];

UPDATE [dbo].[FakeTableDestination]
  SET 
      [FakeVarchar] = [src].[FakeVarchar]
FROM [dbo].[FakeTableSource] AS [SRC]
WHERE 
      [FakeTableDestination].[ID] IN
(
    SELECT 
           [ID]
    FROM @T
);
Run Code Online (Sandbox Code Playgroud)

物理表代码片段

SELECT 
     [FakeID], 
     [FakeVarchar]
INTO dbo.T
FROM [CTE];

UPDATE [dbo].[FakeTableDestination]
  SET 
      [FakeVarchar] = [src].[FakeVarchar]
FROM [dbo].[FakeTableSource] AS [SRC]
WHERE 
      [FakeTableDestination].[ID] IN
(
    SELECT 
           [ID]
    FROM T
);
Run Code Online (Sandbox Code Playgroud)

如果将 更改为UPDATENOT IN怎样?

它不会更新该列中的任何行。

你在什么版本的 SQL Server 上试过这个?SQL Server 2012 SP1、SP2、2016 SP 2。

查询计划是什么样的?

查询我认为应该在语法上出错

查询正确更新