SQL Table 值参数仅在其数据被复制到临时文件时才有效 table

SQL Table Value Parameter only works when its data is copied to temporary table

我正在使用 dapper 通过 C# 将数据传递到 sql 存储过程。

c#(使用小巧玲珑):

 var dt = new DataTable("dbo.TVPtype");
 dt.SetTypeName("dbo.TVPtype");
 dt.Columns.Add("ID", typeof(int));
 dt.Rows.Add(5);
_db.Execute("mysp", param: new { TVP = dt.AsTableValuedParameter("dbo.TVPtype"), commandType: CommandType.StoredProcedure);

下面的 SQL 查询将不起作用,我不知道为什么!并且它从 mytable 中删除了所有数据。 SQL 存储过程:

CREATE PROCEDURE [dbo].[mysp] @TVP dbo.TVPtype READONLY AS
DELETE FROM [dbo].[mytable] WHERE NOT EXISTS
(
SELECT NULL FROM @TVP na
WHERE ID = na.ID
)

为了解决这个问题,我在下面的存储过程中使用了一个临时的 table,并且效果很好。 我的解决方案使用临时 table:

CREATE table temptb (ID int)
insert into temptb select * from @TVP

现在我在删除查询中使用 temptb 而不是 @TVP:

DELETE FROM [dbo].[mytable] WHERE NOT EXISTS
(
SELECT NULL FROM temptb na
WHERE ID = na.ID
)

它运行良好并删除特定数据(不是所有数据)! 那么我的第一个查询有什么问题呢?

我不能告诉你,为什么它与临时 table 一起工作,但如果你将其更改为:

,问题来自 DELETE 语句
DELETE t1 
FROM [dbo].[mytable] AS t1 WHERE NOT EXISTS
(
    SELECT NULL FROM @TVP na
    WHERE t1.ID = na.ID
)

它会起作用的。看,当我将别名添加到 mytable 条件时,它应该比较哪个 ID 就很清楚了。在您的示例中,它可能会将 @TVP ID 与其自身进行比较(这是我的猜测)。

我想为您的查询再补充两点:

  1. 最好在检查是否存在时 select 不是 NULL 的东西,因为 NULL 在 SQL 中是特殊的(尽管它在您的示例中有效)。例如 SELECT 1 FROM @TVP... 会更具可读性

  2. 为什么不这样做:

片段:

DELETE FROM [dbo].[mytable] AS t1 WHERE ID NOT IN
(
    SELECT na.ID FROM @TVP na
)

限定您的列名!你以为你在写:

DELETE FROM [dbo].[mytable]
    WHERE NOT EXISTS (SELECT 1
                      FROM @TVP na
                      WHERE mytable.ID = na.ID
                     );

但你不是。 SQL 的范围规则将您的查询解释为:

DELETE FROM [dbo].[mytable]
    WHERE NOT EXISTS (SELECT 1
                      FROM @TVP na
                      WHERE na.ID = na.ID
                     );

这很荒谬。 WHERE 与外部查询不相关。它将删除所有行(如果 @TVP 为空或 ID 始终为 NULL)或不删除任何行(任何其他情况)。

如果你限定所有列引用,你将永远不会遇到这个问题。