tsql - sp_executesql 区别
tsql - sp_executesql difference
我的 orm 工具执行程序的方式有问题。
我是这样执行的:
exec PostViewProc @Id='18767F6A-FF7A-47E9-AF09-6DB8A2F3B20E',@AuthorId='5455D9B9-B25A-41BD-BD2C-C9CBAE87D629'
Returns 1 行符合预期..
这是 orm 工具生成它的方式:
exec sp_executesql
N'EXEC [PostViewProc] @Id, @AuthorId',
N'@Id uniqueidentifier,@AuthorId uniqueidentifier',
@Id='18767F6A-FF7A-47E9-AF09-6DB8A2F3B20E',@AuthorId='5455D9B9-B25A-41BD-BD2C-C9CBAE87D629'
Returns 没有!
第一个效果很好。但是 orm 生成的 'sp_executesql' 永远无法正常工作。如果我删除@AuthorId,orm 生成的那个工作正常,但随后我添加了一些其他参数,它再次崩溃。
我还有一些其他程序,并且一遍又一遍地遇到同样的问题。不同的参数会导致意想不到的结果,甚至参数的个数也会导致意想不到的结果。甚至错误。 (当使用 sp_executesql 时,另一个工作得很好)
我需要一些关于这里发生的事情的很好的解释,因为程序本身没有任何问题,我现在很困惑。
尝试以下脚本,
exec sp_executesql '18767F6A-FF7A-47E9-AF09-6DB8A2F3B20E','5455D9B9-B25A-41BD-BD2C-C9CBAE87D629'
我们确实讨论过这种情况,并决定最好的解决方案是以正确的顺序传递所有参数。如果过程为其自己的参数处理空值,则只需将它们设置为空或任何其他最适合的值。
感谢你们的时间、耐心和兴趣。 @GarethD & @Mukund
好的,正如评论中指出的那样,问题出在参数的顺序上。为了演示这个问题想象一下下面的简单过程
CREATE PROCEDURE dbo.TestProc @A INT = NULL, @B INT = NULL, @C INT = NULL
AS
BEGIN
SELECT TOP 1 A = @A, B = @B, C = @C;
END
GO
要记住的关键是变量和参数之间没有映射,例如
DECLARE @C INT = 1;
EXECUTE dbo.TestProc @C;
无法识别您的变量名为 @C
,因此您想将其分配给同名参数。如果您在调用您的过程时没有显式地为参数分配值,那么它们将按照先到先得的原则进行分配,因此以上内容将 return:
A B C
------------------
1 NULL NULL
事实上,由于您的变量只不过是一个占位符,因此上述查询与仅执行没有什么不同:
EXECUTE dbo.TestProc 1;
而且您当然不会期望该过程知道您打算传递 1 作为 @C 而不是 @A 的值。您的 ORM 生成的查询也有类似的问题。与上述过程等效的代码是:
EXECUTE sp_executesql N'EXEC dbo.TestProc @A, @C', N'@A INT, @C INT', @A = 1, @C = 2;
哪个 returns:
A B C
------------
1 2 NULL
现在在评论中回答您的一些问题。
我在使用 sp_executesql 时会传递参数名称和类型,不是吗?
是的,你可以,但这不是强制性的,以下两个语句将产生相同的结果:
EXECUTE sp_executesql
N'EXEC dbo.TestProc @A = @A, @B = @B, @C = @C',
N'@A INT, @B INT, @C INT',
@A = 1, @B = 2, @C = 3;
EXECUTE sp_executesql
N'EXEC dbo.TestProc @A, @B, @C',
N'@A INT, @B INT, @C INT',
1, 2, 3;
如果您只想传递特定的值,那么使用命名参数是可取的,但不是强制性的。不同之处在于,如果您不使用参数名称,则必须以正确的顺序提供参数:
EXECUTE sp_executesql
N'EXEC dbo.TestProc @A = @A, @C = @C',
N'@A INT, @C INT',
@A = 1, @C = 3;
EXECUTE sp_executesql
N'EXEC dbo.TestProc @A, @B, @C',
N'@A INT, @B INT, @C INT',
1, NULL, 3;
为什么proc不能匹配到它们?
如上所述,该过程不知道您的变量名,只知道它的值,因此以下两个语句是等价的:
DECLARE @C INT = 1;
EXECUTE dbo.TestProc @c;
EXECUTE dbo.TestProc 1;
如果您检查前一个查询的执行计划 XML,您可以确认:
<ParameterList>
<ColumnReference Column="@C" ParameterCompiledValue="NULL" ParameterRuntimeValue="NULL" />
<ColumnReference Column="@B" ParameterCompiledValue="NULL" ParameterRuntimeValue="NULL" />
<ColumnReference Column="@A" ParameterCompiledValue="NULL" ParameterRuntimeValue="(1)" />
</ParameterList>
任何对传递给过程的变量名称的引用都将丢失。此外,即使它没有丢失,也没有理由匹配,如果不匹配,你会怎么做,例如
EXECUTE dbo.TestProc @D, @A, 2;
@A
匹配参数名称,但 @D
不匹配,常量 2
也不匹配。没有合理的方法来解决这个问题,AFIK 没有语言可以从传递的变量名称中推断出参数。
这 4 行 tsql 代码的作用是什么?
1. exec sp_executesql
2. N'EXEC [PostViewProc] @Id, @AuthorId',
3. N'@Id uniqueidentifier,@AuthorId uniqueidentifier',
4. @Id='18767F6A-FF7A-47E9-AF09-6DB8A2F3B20E',@AuthorId='5455D9B9-B25A-41BD-BD2C-C9CBAE87D629'
- 第 1 行只是调用过程
sp_executesql
。
- 第 2 行是语句参数 - 这是要执行的 sql。
- 第 3 行是
@params
参数,这包含语句中使用的所有参数的定义。
- 第 4 行是
@params
中使用的参数的值,除非明确分配(例如 @A = 1, @B = 2
),否则这些参数的分配顺序与定义参数的顺序相同
我还应该为该过程定义什么才能让它了解这里发生的事情?
我不知道你使用的是什么 ORM,所以我不确定如何强制它生成正确的代码,它应该生成:
EXEC sp_executesql
N'EXEC [PostViewProc] @Id = @Id, @AuthorID = @AuthorId',
N'@Id uniqueidentifier,@AuthorId uniqueidentifier',
@Id='18767F6A-FF7A-47E9-AF09-6DB8A2F3B20E',@AuthorId='5455D9B9-B25A-41BD-BD2C-C9CBAE87D629';
不同之处在于第二行已从
更改
N'EXEC [PostViewProc] @Id, @AuthorId',
至
N'EXEC [PostViewProc] @Id = @Id, @AuthorID = @AuthorId',
如果您不能强制使用 named parameters,那么您将必须以正确的顺序传递所有值,包括 NULL 值。
我的 orm 工具执行程序的方式有问题。
我是这样执行的:
exec PostViewProc @Id='18767F6A-FF7A-47E9-AF09-6DB8A2F3B20E',@AuthorId='5455D9B9-B25A-41BD-BD2C-C9CBAE87D629'
Returns 1 行符合预期..
这是 orm 工具生成它的方式:
exec sp_executesql
N'EXEC [PostViewProc] @Id, @AuthorId',
N'@Id uniqueidentifier,@AuthorId uniqueidentifier',
@Id='18767F6A-FF7A-47E9-AF09-6DB8A2F3B20E',@AuthorId='5455D9B9-B25A-41BD-BD2C-C9CBAE87D629'
Returns 没有!
第一个效果很好。但是 orm 生成的 'sp_executesql' 永远无法正常工作。如果我删除@AuthorId,orm 生成的那个工作正常,但随后我添加了一些其他参数,它再次崩溃。
我还有一些其他程序,并且一遍又一遍地遇到同样的问题。不同的参数会导致意想不到的结果,甚至参数的个数也会导致意想不到的结果。甚至错误。 (当使用 sp_executesql 时,另一个工作得很好)
我需要一些关于这里发生的事情的很好的解释,因为程序本身没有任何问题,我现在很困惑。
尝试以下脚本,
exec sp_executesql '18767F6A-FF7A-47E9-AF09-6DB8A2F3B20E','5455D9B9-B25A-41BD-BD2C-C9CBAE87D629'
我们确实讨论过这种情况,并决定最好的解决方案是以正确的顺序传递所有参数。如果过程为其自己的参数处理空值,则只需将它们设置为空或任何其他最适合的值。
感谢你们的时间、耐心和兴趣。 @GarethD & @Mukund
好的,正如评论中指出的那样,问题出在参数的顺序上。为了演示这个问题想象一下下面的简单过程
CREATE PROCEDURE dbo.TestProc @A INT = NULL, @B INT = NULL, @C INT = NULL
AS
BEGIN
SELECT TOP 1 A = @A, B = @B, C = @C;
END
GO
要记住的关键是变量和参数之间没有映射,例如
DECLARE @C INT = 1;
EXECUTE dbo.TestProc @C;
无法识别您的变量名为 @C
,因此您想将其分配给同名参数。如果您在调用您的过程时没有显式地为参数分配值,那么它们将按照先到先得的原则进行分配,因此以上内容将 return:
A B C
------------------
1 NULL NULL
事实上,由于您的变量只不过是一个占位符,因此上述查询与仅执行没有什么不同:
EXECUTE dbo.TestProc 1;
而且您当然不会期望该过程知道您打算传递 1 作为 @C 而不是 @A 的值。您的 ORM 生成的查询也有类似的问题。与上述过程等效的代码是:
EXECUTE sp_executesql N'EXEC dbo.TestProc @A, @C', N'@A INT, @C INT', @A = 1, @C = 2;
哪个 returns:
A B C
------------
1 2 NULL
现在在评论中回答您的一些问题。
我在使用 sp_executesql 时会传递参数名称和类型,不是吗?
是的,你可以,但这不是强制性的,以下两个语句将产生相同的结果:
EXECUTE sp_executesql
N'EXEC dbo.TestProc @A = @A, @B = @B, @C = @C',
N'@A INT, @B INT, @C INT',
@A = 1, @B = 2, @C = 3;
EXECUTE sp_executesql
N'EXEC dbo.TestProc @A, @B, @C',
N'@A INT, @B INT, @C INT',
1, 2, 3;
如果您只想传递特定的值,那么使用命名参数是可取的,但不是强制性的。不同之处在于,如果您不使用参数名称,则必须以正确的顺序提供参数:
EXECUTE sp_executesql
N'EXEC dbo.TestProc @A = @A, @C = @C',
N'@A INT, @C INT',
@A = 1, @C = 3;
EXECUTE sp_executesql
N'EXEC dbo.TestProc @A, @B, @C',
N'@A INT, @B INT, @C INT',
1, NULL, 3;
为什么proc不能匹配到它们?
如上所述,该过程不知道您的变量名,只知道它的值,因此以下两个语句是等价的:
DECLARE @C INT = 1;
EXECUTE dbo.TestProc @c;
EXECUTE dbo.TestProc 1;
如果您检查前一个查询的执行计划 XML,您可以确认:
<ParameterList>
<ColumnReference Column="@C" ParameterCompiledValue="NULL" ParameterRuntimeValue="NULL" />
<ColumnReference Column="@B" ParameterCompiledValue="NULL" ParameterRuntimeValue="NULL" />
<ColumnReference Column="@A" ParameterCompiledValue="NULL" ParameterRuntimeValue="(1)" />
</ParameterList>
任何对传递给过程的变量名称的引用都将丢失。此外,即使它没有丢失,也没有理由匹配,如果不匹配,你会怎么做,例如
EXECUTE dbo.TestProc @D, @A, 2;
@A
匹配参数名称,但 @D
不匹配,常量 2
也不匹配。没有合理的方法来解决这个问题,AFIK 没有语言可以从传递的变量名称中推断出参数。
这 4 行 tsql 代码的作用是什么?
1. exec sp_executesql
2. N'EXEC [PostViewProc] @Id, @AuthorId',
3. N'@Id uniqueidentifier,@AuthorId uniqueidentifier',
4. @Id='18767F6A-FF7A-47E9-AF09-6DB8A2F3B20E',@AuthorId='5455D9B9-B25A-41BD-BD2C-C9CBAE87D629'
- 第 1 行只是调用过程
sp_executesql
。 - 第 2 行是语句参数 - 这是要执行的 sql。
- 第 3 行是
@params
参数,这包含语句中使用的所有参数的定义。 - 第 4 行是
@params
中使用的参数的值,除非明确分配(例如@A = 1, @B = 2
),否则这些参数的分配顺序与定义参数的顺序相同
我还应该为该过程定义什么才能让它了解这里发生的事情?
我不知道你使用的是什么 ORM,所以我不确定如何强制它生成正确的代码,它应该生成:
EXEC sp_executesql
N'EXEC [PostViewProc] @Id = @Id, @AuthorID = @AuthorId',
N'@Id uniqueidentifier,@AuthorId uniqueidentifier',
@Id='18767F6A-FF7A-47E9-AF09-6DB8A2F3B20E',@AuthorId='5455D9B9-B25A-41BD-BD2C-C9CBAE87D629';
不同之处在于第二行已从
更改N'EXEC [PostViewProc] @Id, @AuthorId',
至
N'EXEC [PostViewProc] @Id = @Id, @AuthorID = @AuthorId',
如果您不能强制使用 named parameters,那么您将必须以正确的顺序传递所有值,包括 NULL 值。