首页 > 解决方案 > SQL Server 似乎舍入不正确

问题描述

我有一个存储课程结果的表。一门课程可能有多个考试,每个考试都有自己的权重。

在这个例子中,我有一个学生的 2 个考试分数,然后加权以给出课程分数。标记和权重都存储在 FLOAT 列中。

这是我提取考试成绩的代码:

WITH RawData AS 
(
    SELECT 
        lngRelatedScoreID AS [ID],
        cc.strName AS Exam,
        cc.dblWeighting, 
        sci.dblModeratedComponentScorePercent AS Mark
    FROM 
        tblStudentComponentInformation sci 
    INNER JOIN 
        tblCourseComponents cc ON sci.lngExamID = cc.lngExamID
    WHERE 
        sci.lngRelatedScoreID IN (73652)
)
SELECT * FROM RawData

结果显示如下:

ID 考试 dbl加权 标记
73652 飞行动力学和控制考试 0.75 0.905
73652 飞行动力学和控制课程 0.25 0.92

我现在将这两行合并,将权重乘以标记:

WITH RawData AS 
(
    SELECT 
        lngRelatedScoreID AS ID,
        cc.strName AS Exam,
        cc.dblWeighting, 
        sci.dblModeratedComponentScorePercent AS Mark
    FROM 
        tblStudentComponentInformation sci 
    INNER JOIN 
        tblCourseComponents cc ON sci.lngExamID = cc.lngExamID
    WHERE 
        sci.lngRelatedScoreID IN (73652)
)
SELECT 
    [ID], 
    SUM(Mark * dblWeighting) AS TotalWeightedMark
FROM 
    RawData
GROUP BY 
    [ID]

它返回以下 - 正如预期的那样:

ID 总加权标记
73652 0.90875

但是,我希望结果到小数点后 4 位,所以当我将标记乘以四舍五入并对结果求和时,我在ROUND函数中添加:

WITH RawData AS 
(
    SELECT 
        lngRelatedScoreID AS ID,
        cc.strName AS Exam,
        cc.dblWeighting, 
        sci.dblModeratedComponentScorePercent AS Mark
    FROM 
        tblStudentComponentInformation sci 
    INNER JOIN 
        tblCourseComponents cc ON sci.lngExamID = cc.lngExamID
    WHERE 
        sci.lngRelatedScoreID IN (73652)
)
SELECT 
    [ID], 
    ROUND(SUM(Mark * dblWeighting), 4) AS TotalWeightedMark
FROM 
    RawData
GROUP BY 
    [ID]

这就是我得到的回报:

ID 总加权标记
73652 0.9087

我的问题是为什么这似乎是截断而不是四舍五入,因为我没有指定除ROUND函数最终参数的默认值之外的任何内容。

我想知道这是否是因为使用FLOAT而不是DECIMAL用于列,但在这种情况下,计算中不需要任何舍入,除了我指定从 5 位四舍五入到 4 的计算。

任何人都可以建议吗?

如果相关,我使用的是 SQL Server 2017。

谢谢。

标签: sqlsql-server

解决方案


正如评论中提到的,“问题”是数据类型,而不是表达式。

如果我们看下面的例子:

SELECT [ID],
       SUM(FloatWeighting*FloatMark) AS Float,
       SUM(Decimalweighting*DecimalMark) AS Decimal,
       CONVERT(decimal(18,12),SUM(FloatWeighting*FloatMark)) AS ConvertedFloat,
       ROUND(SUM(FloatWeighting*FloatMark),4) AS RoundedFloat,
       ROUND(SUM(Decimalweighting*DecimalMark),4) AS RoundedDecimal
FROM (VALUES(73652,'Flight Dynamics and Control Exam      ',CONVERT(float,0.75),CONVERT(decimal(3,2),0.75),CONVERT(float,0.905),CONVERT(decimal(4,3),0.905)),
            (73652,'Flight Dynamics and Control Coursework',CONVERT(float,0.25),CONVERT(decimal(3,2),0.25),CONVERT(float,0.92),CONVERT(decimal(4,3),0.92)))V(ID,Exam,FloatWeighting,DecimalWeighting,FloatMark,DecimalMark)
GROUP BY ID;

如果你运行它,你会得到以下结果:

ID    Float   Decimal ConvertedFloat RoundedFloat RoundedDecimal
----- ------- ------- -------------- ------------ --------------
73652 0.90875 0.90875 0.908750000000 0.9088       0.90880

请注意,四舍五入的decimal值提供了您期望的值,但四舍五入的值float没有。这是因为,正如评论中所讨论的,浮点值不是以 10 为底的值,而是以 2 为底的值。结果,该float实际上0.90875不是,而是。请注意,该值实际上(仅)小于。 0.908750.9087499999999999467092948179924860596656799316406250.90875

因此,当您应用该函数时,将ROUND使用上述值,并且当您ROUND将该值保留到小数点后 4 位时,这0.9087 就是正确的答案。

因此,这里真正的解决方案是修复您的设计和ALTER表格以使列成为decimal. 例如,这可能是:

ALTER TABLE dbo.tblCourseComponents ALTER COLUMN Mark decimal(4,3);

请注意,您需要为实际值使用适当的比例和精度。

如果您不能更改数据类型,或者至少现在不能,那么您可以decimal在算术之前将值显式转换为第一个。例如:

ROUND(SUM(CONVERT(decimal(3,2),Weighting)*CONVERT(decimal(4,3),Mark)),4)

同样,确保您为数据使用适当的精度和比例。


推荐阅读