关于 sp_executesql 的错误

A bug about sp_executesql

CREATE TABLE [dbo].[MyTable]([id] [int] IDENTITY(1,1) NOT NULL,
[MyNumericColumn] [decimal](18, 2) NOT NULL,[insert_time] [datetime] NOT NULL CONSTRAINT [DF__MyTable__insert___0F975522]  DEFAULT (getdate()),
 CONSTRAINT [PK_MyTable] 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]
GO
exec sp_executesql N'insert into MyTable (  MyNumericColumn, insert_time ) values ( @P0, getdate()),( @P1, getdate())',N'@P0 decimal(38,1),@P1 decimal(38,2)',10.0,0.99
exec sp_executesql N'insert into MyTable (  MyNumericColumn, insert_time ) values ( @P0, getdate()),( @P1, getdate()),( @P2, getdate()) ',N'@P0 decimal(38,2),@P1 decimal(38,1),@P2 decimal(38,3)',0.99,10.00,0.999
select * from [MyTable];
exec sp_executesql N'insert into MyTable (  MyNumericColumn, insert_time ) values( @P0, getdate()),( @P1, getdate()), ( @P2, getdate())',N'@P0 decimal(38,2),@P1 decimal(38,2),@P2 decimal(38,3)',0.99,10.00,0.999
select * from [MyTable];

while @P0 decimal(38,2),@P1 decimal(38,1),@P2 decimal(38,3)为什么所有数据都是decimal(38,1)..

这种舍入行为是预期的,与 sp_executesql 无关。在组合不同类型的表达式时,行构造函数的行为类似于 UNIONUNION ALLthe documentation.

中描述了组合不同精度和小数位数的小数类型的规则

使用带有局部变量的 UNION ALL SELECT 查询考虑此重现:

DECLARE
     @P0 decimal(38,1) = 10.0
    ,@P1 decimal(38,2) = 0.99
SELECT @P0 
UNION ALL
SELECT @P1;

结果:

+------------------+
| 10.0             |
| 1.0              |
+------------------+

使用 sp_describe_first_reesult_set 执行查询显示结果类型是 decimal(38,1) 而不是人们可能预期的 decimal(38,2) ,导致 0.99 值四舍五入为 1.00。这样做是为了避免在指定较大值时截断整数部分。

EXEC sp_describe_first_result_set N'
DECLARE
     @P0 decimal(38,1) = 10.0
    ,@P1 decimal(38,2) = 0.99
SELECT @P0 
UNION ALL
SELECT @P1;
';

如果我们将小数位数减小到 37,则结果类型为 decimal(38,2),并且这些值不会四舍五入。但是,最佳做法是声明参数类型以匹配目标 table 的参数类型,并理解精度更高的值将被四舍五入:

exec sp_executesql N'insert into MyTable (  MyNumericColumn, insert_time ) values ( @P0, getdate()), ( @P1, getdate())',N'@P0 decimal(38,2),@P1 decimal(38,2)',10.0,0.99
exec sp_executesql N'insert into MyTable (  MyNumericColumn, insert_time ) values ( @P0, getdate()), ( @P1, getdate()), ( @P2, getdate()) ',N'@P0 decimal(38,2),@P1 decimal(38,2),@P2 decimal(38,2)',0.99,10.00,0.999
select * from [MyTable];
exec sp_executesql N'insert into MyTable (  MyNumericColumn, insert_time ) values( @P0, getdate()),( @P1, getdate()), ( @P2, getdate())',N'@P0 decimal(38,2),@P1 decimal(38,2),@P2 decimal(38,2)',0.99,10.00,0.999
select * from [MyTable];