Dmi*_*sev 20 sql sql-server sql-server-2008-r2 sql-server-2014 sql-server-2016
在开发过程中遇到了相当奇怪的SQL Server行为.在这里,绝对相同的数字我们有完全相同的公式.唯一的区别是我们如何得到这个数字(4.250).从表,临时表,变量表或硬编码值.在所有情况下,圆角和铸造都是完全相同的.
-- normal table
CREATE TABLE [dbo].[value]
(
[val] [decimal] (5, 3) NOT NULL
)
INSERT INTO [value] VALUES (4.250 )
SELECT ROUND(CAST(val * 0.01 / 12 AS DECIMAL(15, 9)), 9) AS val FROM [value] AS pr
-- inline query from normal table
SELECT * FROM (SELECT ROUND(CAST(val * 0.01 / 12 AS DECIMAL(15, 9)), 9) AS val FROM [value] AS pr) a
-- record without table
SELECT ROUND(CAST(4.250 * 0.01 / 12 AS DECIMAL(15, 9)), 9) AS val
-- table variable
DECLARE @value AS TABLE (
val [decimal] (5, 3)
);
INSERT INTO @value VALUES (4.250 )
SELECT ROUND(CAST(val * 0.01 / 12 AS DECIMAL(15, 9)), 9) AS val FROM @value
-- temp table
CREATE TABLE #value
(
val [decimal] (5, 3)
)
INSERT INTO #value VALUES (4.250 )
SELECT ROUND(CAST(val * 0.01 / 12 AS DECIMAL(15, 9)), 9) AS val FROM #value AS pr
-- all records together
SELECT ROUND(CAST(val * 0.01 / 12 AS DECIMAL(15, 9)), 9) AS val FROM [value] AS pr
UNION ALL
SELECT ROUND(CAST(4.250 * 0.01 / 12 AS DECIMAL(15, 9)), 9) AS val
UNION ALL
SELECT ROUND(CAST(val * 0.01 / 12 AS DECIMAL(15, 9)), 9) AS val FROM @value
UNION ALL
SELECT ROUND(CAST(val * 0.01 / 12 AS DECIMAL(15, 9)), 9) AS val FROM #value AS pr
DROP TABLE #value;
DROP TABLE [dbo].[value];
Run Code Online (Sandbox Code Playgroud)
结果是:
Bri*_*dge 14
这似乎是因为您没有在硬编码该值的任何地方指定4.250的数据类型,以及混合数据类型decimal(5,3)
以及decimal(15,9)
表声明和演员语句.
请注意,在任何地方指定相同的精度
-- normal table
CREATE TABLE [dbo].[value]
(
[val] DECIMAL(15, 9) NOT NULL
)
INSERT INTO [value]
SELECT CAST(4.250 AS DECIMAL(15, 9))
SELECT ROUND(CAST(val * 0.01 / 12 AS DECIMAL(15, 9)), 9) AS val
FROM [value] AS pr
-- inline query from normal table
SELECT *
FROM (SELECT ROUND(CAST(val * 0.01 / 12 AS DECIMAL(15, 9)), 9) AS val
FROM [value] AS pr) a
-- record without table
SELECT ROUND(CAST(CAST(4.250 AS DECIMAL(15, 9)) * 0.01 / 12 AS DECIMAL(15, 9)), 9) AS val
-- table variable
DECLARE @value AS TABLE
(
val [DECIMAL] (15, 9)
);
INSERT INTO @value
SELECT CAST(4.250 AS DECIMAL(15, 9))
SELECT ROUND(CAST(val * 0.01 / 12 AS DECIMAL(15, 9)), 9) AS val
FROM @value
-- temp table
CREATE TABLE #value
(
val [DECIMAL] (15, 9)
)
INSERT INTO #value
SELECT CAST(4.250 AS DECIMAL(15, 9))
SELECT ROUND(CAST(val * 0.01 / 12 AS DECIMAL(15, 9)), 9) AS val
FROM #value AS pr
-- all records together
SELECT ROUND(CAST(val * 0.01 / 12 AS DECIMAL(15, 9)), 9) AS val
FROM [value] AS pr
UNION ALL
SELECT ROUND(CAST(CAST(4.250 AS DECIMAL(15, 9)) * 0.01 / 12 AS DECIMAL(15, 9)), 9) AS val
UNION ALL
SELECT ROUND(CAST(val * 0.01 / 12 AS DECIMAL(15, 9)), 9) AS val
FROM @value
UNION ALL
SELECT ROUND(CAST(val * 0.01 / 12 AS DECIMAL(15, 9)), 9) AS val
FROM #value AS pr
DROP TABLE #value;
DROP TABLE [dbo].[value];
Run Code Online (Sandbox Code Playgroud)
每行得到相同的结果:
0.003541667
进一步说明:
您可以通过将其填充到变体中来测试以查看硬编码数值的数据类型:
DECLARE @var SQL_VARIANT;
SELECT @var = 4.250
SELECT SQL_VARIANT_PROPERTY(@var, 'BaseType'),
SQL_VARIANT_PROPERTY(@var, 'Precision'),
SQL_VARIANT_PROPERTY(@var, 'Scale');
Run Code Online (Sandbox Code Playgroud)
这将numeric(4,3)
在我的本地SQL Server框中返回.(数字和小数是一回事)
编辑#2:进一步挖掘
仅举第一个例子:
CREATE TABLE [dbo].[value]
(
[val] [decimal] (5, 3) NOT NULL
)
INSERT INTO [value] VALUES (4.250 )
SELECT ROUND(CAST(val * 0.01 / 12 AS DECIMAL(15, 9)), 9) AS val FROM [value] AS pr
-- inline query from normal table
SELECT * FROM (SELECT ROUND(CAST(val * 0.01 / 12 AS DECIMAL(15, 9)), 9) AS val FROM [value] AS pr) a
DROP TABLE VALUE
Run Code Online (Sandbox Code Playgroud)
进一步挖掘后,执行计划是不同的 - 第一个语句是参数化的,而子查询版本不是:
如果查看属性窗口:
它不会列出这些参数的数据类型,但带馅的价值观做同样的伎俩0.01
,并12
进入一个变种与数据类型结束numeric(2,2)
并int
分别.
如果将第二个语句中的硬编码值强制转换为这些数据类型:
SELECT * FROM (SELECT ROUND(CAST(val * CAST(0.01 AS NUMERIC(2,2)) / CAST(12 AS INT) AS DECIMAL(15, 9)), 9) AS val FROM [value] AS pr) a
Run Code Online (Sandbox Code Playgroud)
两个语句都得到相同的结果.为什么它决定参数化select而不是子查询,参数的数据类型实际是什么,以及硬编码值在第二个语句中正常处理的数据类型......对我来说仍然是个谜.我们可能需要询问具有SQL Server引擎内部知识的人.
Hon*_*ger 11
如果我跑:
SELECT CAST(pr.val * 0.01 / 12 AS DECIMAL(15, 9)) AS val
, SQL_VARIANT_PROPERTY(CAST(pr.val * 0.01 / 12 AS DECIMAL(15, 9)), 'BaseType')
FROM [value] AS pr
Run Code Online (Sandbox Code Playgroud)
0.003541660
返回值.
如果我跑:
SELECT CAST(pr.val * 0.01 / 12 AS DECIMAL(15, 9)) AS val
FROM [value] AS pr
Run Code Online (Sandbox Code Playgroud)
0.003541667
返回值.
闻起来像我的臭虫......
编辑
根据Bridge的回答,我也决定看一下执行计划.罗和贝恩:
SELECT CAST(pr.val * 0.01 / 12 AS DECIMAL(15, 9)) AS val
FROM [value] AS pr
OPTION (RECOMPILE)
-- inline query from normal table
SELECT a.val
FROM (
SELECT CAST(pr.val * 0.01 / 12 AS DECIMAL(15, 9)) AS val
FROM [value] AS pr
) AS a
OPTION (RECOMPILE)
Run Code Online (Sandbox Code Playgroud)
两个查询都返回0.003541660
.因此,执行计划的重用似乎是"错误"的起源.(注意:DBCC FREEPROCCACHE
结果不一样!)
额外注意:如果我将执行计划保存为xml,则无论是否有文件都是相同的OPTION (RECOMPILE)
.
编辑:
如果我将数据库设置为PARAMETERIZATION FORCED
,则子查询仍然在没有参数的情况下执行.如果我通过显式使用0.01
和12
作为变量强制参数化,则返回的值也是相同的.我认为SQL Server以不同于预期的数据类型定义参数.我无法将结果强制为0.003541660.这也解释了为什么OPTION(RECOMPILE)
结果具有相同的值:如果使用RECOMPILE,则关闭参数化.
Ale*_*lex 11
当您使用+, - ,*,/或%算术运算符执行int,smallint,tinyint或bigint常量值到float,real,decimal或numeric数据类型的隐式或显式转换时,SQL Server的规则在计算数据类型时应用,并且表达式结果的精度根据查询是否自动参数化而不同.
因此,查询中的类似表达式有时会产生不同的结果.当查询不是自动参数化时,常量值首先转换为数字,其精度只要大到足以保存常量的值,然后再转换为指定的数据类型.例如,将常数值1转换为
numeric (1, 0)
,并将常数值250转换为numeric (3, 0)
.查询自动参数化时,常量值将
numeric (10, 0)
在转换为最终数据类型之前始终转换为.当涉及/运算符时,不仅结果类型的精度在类似查询中不同,但结果值也可能不同.例如,包含表达式的自动参数化查询SELECT CAST (1.0 / 7 AS float)
的结果值将不同于未自动参数化的同一查询的结果值,因为自动参数化查询的结果将被截断以适合numeric (10, 0)
数据类型.
注意:
numeric (10, 0)
相当于INT
.
在上面的示例中,当被除数和除数都是整数时,该类型被视为INT
例如INT
/ INT
=INT
另一方面,如果其中一种类型被强制为"正确" NUMERIC
类型,则表达式被视为NUMERIC( 10, 0 )
/ NUMERIC( 10, 0 )
= NUMERIC( 21, 11 )
.请参阅:精度,小数位数和长度(Transact-SQL),以说明如何计算结果类型.
例:
EXEC sp_describe_first_result_set N'SELECT 1 as a, 7 as b, 1 / 7 AS Result'
EXEC sp_describe_first_result_set N'SELECT 1 as a, CONVERT( NUMERIC( 10, 0 ), 7 ) as b, CONVERT( INT, 1 ) / CONVERT( NUMERIC( 10, 0 ), 7 ) AS a'
Run Code Online (Sandbox Code Playgroud)
注意: NUMERIC
数据类型只有一个固定的小数位数(比例)来存储小数.当除法产生具有(无限长)小数部分的结果时,这变得很重要,例如1/3必须被截断以适合该类型.
结果的差异归结为12是否被视为INT
/ NUMERIC( 10, 0 )
或NUMERIC( 2, 0 )
因为这将直接影响结果的精度(小数位数):decimal(19,16)
或decimal(11,8)
.我已删除INT
并NUMERIC( 2, 0 )
运行以显示计算中使用的实际类型.
输入参数:
-- Note: on my machine "parameterization" option does not have any effect on below example
SELECT CONVERT( decimal (5, 3), 4.250 ) AS a, -- the type is explicitly defined in the table
0.01 AS b -- always becomes NUMERIC( 2, 2 )
12 AS c -- will either become NUMERIC( 2, 0 ) or NUMERIC( 10, 0 ) / INT
EXEC sp_describe_first_result_set N'SELECT CONVERT( decimal (5, 3), 4.250 ) AS a, 0.01 AS b, 12 AS c'
Run Code Online (Sandbox Code Playgroud)
在上述情况下,它被视为p1 + p2 + 1, s1 + s2
.
你可以"强迫"它被视为5 = x + 2 + 1, 2 = y + 2
:
-- Note: on my machine "parameterization" option does not have any effect on below example
SELECT 0.01 AS b, ( 12 * 0.01 ) AS c
EXEC sp_describe_first_result_set N'SELECT ( 12 * 0.01 ) AS c'
-- Result: 0.12 numeric(5,2)
Run Code Online (Sandbox Code Playgroud)
计算产品数据类型的公式:2, 0
.
找出起始类型解决:NUMERIC( 2, 0 )
得到ParameterizedPlanHandle
ieParameterizedText="(@1 numeric(2,2),@2 int)SELECT round(CONVERT([decimal](15,9),[val]*@1/@2),(9)) [val] FROM [value] [pr]"
结果的输出类型如下:
-- 12 is NUMERIC( 10, 0 ) / INT
SELECT CONVERT( decimal (5, 3), 4.250 ) * CONVERT( decimal (2, 2), 0.01 ) / CONVERT( decimal(10, 0), 12 )
EXEC sp_describe_first_result_set N'SELECT CONVERT( decimal (5, 3), 4.250 ) * CONVERT( decimal (2, 2), 0.01 ) / CONVERT( decimal(10, 0), 12 )'
-- Result: 0.0035416666666666 decimal(19,16) -> rounding to 9 decimal places: 0.003541667
-- 12 is NUMERIC( 2, 0 )
SELECT CONVERT( decimal (5, 3), 4.250 ) * CONVERT( decimal (2, 2), 0.01 ) / CONVERT( decimal(2, 0), 12 )
EXEC sp_describe_first_result_set N'SELECT CONVERT( decimal (5, 3), 4.250 ) * CONVERT( decimal (2, 2), 0.01 ) / CONVERT( decimal(2, 0), 12 )'
-- Result: 0.00354166 decimal(11,8) -> rounding to 9 decimal places: 0.003541660
Run Code Online (Sandbox Code Playgroud)
要查看计算结果类型的方式,请参阅精度,比例和长度(Transact-SQL).
将您的文字和/或中间结果投射到所需的类型,以避免意外,例如
SELECT CONVERT( decimal( 12, 7 ), CONVERT( decimal (5, 3), 4.250 ) * CONVERT( decimal (2, 2), 0.01 )) / CONVERT( decimal(2, 0), 12 )
EXEC sp_describe_first_result_set N'SELECT CONVERT( decimal( 12, 7 ), CONVERT( decimal (5, 3), 4.250 ) * CONVERT( decimal (2, 2), 0.01 )) / CONVERT( decimal(2, 0), 12 )'
-- Result: 0.0035416666 decimal(15,10) -> rounding to 9 decimal places: 0.003541660
Run Code Online (Sandbox Code Playgroud)
这个问题是一个复杂的案例:在SQL Server 2008R2中使用CAST函数划分2个数字.由于SQL Server可能在不同的场景中使用不同的数据类型,因此复杂性很高.
我只能找到一篇关于简单参数化的文章(http://www.sqlteam.com),当一个查询不能自动参数化时,它会实际提到.
注意:该文章来自2007年,因此可能不是最新的.
SQL Server对可以使用简单参数化参数化的查询类型进行以下限制:
- 单表 - 无JOIN
- 没有IN条款
- 没有UNION
- 没有选择
- 没有查询提示
- 没有DISTINCT或TOP
- 没有全文链接服务器或表变量
- 没有子查询
- 没有GROUP BY
- WHERE子句中没有<>
- 没有功能
- 使用FROM子句没有DELETE或UPDATE
- 参数值不能影响计划
TechNet - 简单参数化文章没有任何信息.
TechNet - 强制参数化确实有一些信息,但它适用于强制参数化