在开发过程中遇到了相当奇怪的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];
结果是:
答案 0 :(得分:14)
这似乎是因为您没有在硬编码该值的任何地方指定4.250的数据类型,以及在表声明和转换语句中混合数据类型decimal(5,3)
和decimal(15,9)
。 / p>
请注意,在任何地方指定相同的精度:
-- 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];
每行都会得到相同的结果:
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');
这将在我的本地SQL Server框中返回numeric(4,3)
。 (数字和小数是same thing)
编辑#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
进一步挖掘后,执行计划是不同的 - 第一个语句是参数化的,而子查询版本不是:
如果查看属性窗口:
它没有列出这些参数的数据类型,但是将值0.01
和12
填充到变量中的相同技巧最终会使用数据类型numeric(2,2)
和{{1分别。
如果将第二个语句中的硬编码值转换为这些数据类型:
int
两个语句都得到相同的结果。为什么它决定参数化select而不是子查询,参数的数据类型实际是什么,以及硬编码值在第二个语句中正常处理的数据类型......对我来说仍然是个谜。我们可能需要询问具有SQL Server引擎内部知识的人。
答案 1 :(得分:10)
如果我跑:
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
返回值0.003541660
。
如果我跑:
SELECT CAST(pr.val * 0.01 / 12 AS DECIMAL(15, 9)) AS val
FROM [value] AS pr
返回值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)
两个查询都返回0.003541660
。因此,执行计划的重用似乎是“错误”的地方。起源。 (注意:DBCC FREEPROCCACHE
没有相同的结果!)
额外注意:如果我将执行计划保存为xml,则无论有无OPTION (RECOMPILE)
,文件都是相同的。
修改强>
如果我将数据库设置为PARAMETERIZATION FORCED
,则子查询仍然在没有参数的情况下执行。如果我通过显式使用0.01
和12
作为变量来强制参数化,则返回的值也是相同的。我认为SQL Server以不同于预期的数据类型定义参数。虽然我还没有把结果逼到0.003541660。
这也解释了OPTION(RECOMPILE)
导致相同值的原因:如果使用RECOMPILE,则参数化将转为off。
答案 2 :(得分:10)
使用+, - ,*,/或%算术运算符执行时 int,smallint,tinyint或bigint的隐式或显式转换 float,real,decimal或numeric数据类型的常量值 SQL Server在计算数据类型时应用的规则 表达结果的精确度取决于是否 查询是否自动参数化。
因此,查询中的类似表达式有时会产生 不同的结果。当查询不是自动参数化时,常量 value首先转换为numeric,其精度很大 足以在转换为之前保持常量的值 指定的数据类型。例如,将常数值1转换为
numeric (1, 0)
,常数值250转换为numeric (3, 0)
。当查询自动参数化时,常量值始终为 在转换为最终数据之前转换为
numeric (10, 0)
类型。当涉及/运算符时,结果类型不仅可以 类似查询的精度不同,但结果值可以 也有所不同。例如,自动参数化的结果值 包含表达式SELECT CAST (1.0 / 7 AS float)
的查询 将与不相同的查询的结果值不同 autoparameterized,因为自动参数化查询的结果 将被截断以适合numeric (10, 0)
数据类型。
注意:强>
numeric (10, 0)
相当于INT
。
在上面的示例中,当被除数和除数都是整数时,该类型被视为INT
,例如INT
/ INT
= INT
另一方面,如果其中一种类型被迫成为"正确的" NUMERIC
类型表达式被视为NUMERIC( 10, 0 )
/ NUMERIC( 10, 0 )
= NUMERIC( 21, 11 )
。有关如何计算结果类型的说明,请参阅:Precision, scale, and Length (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'
注意: NUMERIC
数据类型只有一个固定的小数位数(比例)来存储小数。当除法产生具有(无限长)小数部分的结果时,这变得很重要,例如1/3必须被截断以适合该类型。
结果的差异归结为12是否被视为INT
/ NUMERIC( 10, 0 )
或NUMERIC( 2, 0 )
,因为这将直接影响结果的精度(小数位数):{ {1}}或decimal(19,16)
。我已移除decimal(11,8)
和CAST
函数以显示计算中使用的实际类型。
输入参数:
ROUND
在上述情况下,它被视为-- 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'
。
你可以强迫"它被视为INT
:
NUMERIC( 2, 0 )
计算产品数据类型的公式:-- 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)
。
要找出起始类型求解:p1 + p2 + 1, s1 + s2
以获取5 = x + 2 + 1, 2 = y + 2
,即2, 0
结果的输出类型如下:
NUMERIC( 2, 0 )
要了解如何计算结果类型,请参阅Precision, scale, and Length (Transact-SQL)。
将您的文字和/或中间结果投射到所需类型,以避免出现意外,例如
-- 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
这个问题是一个复杂的案例:Division of 2 numbers using CAST function in SQL server 2008R2。由于SQL Server可能在不同的场景中使用不同的数据类型,因此很复杂。
我只能找到一篇关于简单参数化的文章(http://www.sqlteam.com),当一个查询不会自动参数化时,它会实际提及。
注意:该文章来自2007年,因此可能不是最新文章。
SQL Server对哪些类型的查询设置了以下限制 可以使用简单参数化参数化:
- 单表 - 无JOIN
- No IN clause
- No UNION
- No SELECT INTO
- 无查询提示
- 没有DISTINCT或TOP
- 没有全文链接服务器或表变量
- 没有子查询
- No GROUP BY
- 否<>在WHERE子句
中- 无功能
- 没有使用FROM子句删除或更新
- 参数值不能影响计划
TechNet - Simple Parameterization文章没有任何信息。
TechNet - Forced Parameterization确实有一些信息,但它适用于强制参数化