【问题标题】:A bug about sp_executesql关于 sp_executesql 的错误
【发布时间】:2023-03-10 13:03:01
【问题描述】:
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];

而@P0 十进制(38,2),@P1 十进制(38,1),@P2 十进制(38,3) 为什么所有数据都是十进制(38,1)..

【问题讨论】:

  • 在您用来生成这些语句的任何客户端结束时,您都可能成为数据类型推断的受害者。例如,所有 ADO.NET 客户端都将尝试仅根据参数值而不是语句来推断参数的类型。像0.999 这样的值将被推断为decimal(38, 3),因为这被认为是“安全”的精度,无论这最终在查询中是否有意义。在decimal 的情况下,尤其是这可能会导致不直观或不正确的结果,这就是为什么您应该注意明确指定比例和精度的原因。
  • @JeroenMostert,也许AddWithValue 是罪魁祸首。

标签: sql-server tsql dynamic-sql


【解决方案1】:

这种舍入行为是预期的,与sp_executesql 无关。在组合不同类型的表达式时,行构造函数的行为类似于 UNIONUNION ALLthe documentation中描述了组合不同精度和小数位数的十进制类型的规则。

考虑这个使用带有局部变量的UNION ALL SELECT 查询的repro:

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),并且这些值不会四舍五入。但是,最好的做法是声明参数类型以匹配目标表的类型,同时理解精度更高的值将被舍入:

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];

【讨论】:

猜你喜欢
  • 2012-06-17
  • 1970-01-01
  • 1970-01-01
  • 2012-07-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-06
  • 2017-05-09
相关资源
最近更新 更多