为啥 SQL 计数(*)与 SQL 计数(数字)存在行为差异

Posted

技术标签:

【中文标题】为啥 SQL 计数(*)与 SQL 计数(数字)存在行为差异【英文标题】:Why there is a behavior difference of SQL count(*) vs SQL count(numeric)为什么 SQL 计数(*)与 SQL 计数(数字)存在行为差异 【发布时间】:2017-05-03 13:17:54 【问题描述】:

我知道 count(*) - 将返回包括空值在内的所有行的总数。 count(colName) - 将返回 colName 不为空的所有行的总数。

今天我的一所大学遇到了 SQL 中的 count() 问题。在应用了一些日期过滤器后,他试图从视图中获取行数。

查看返回数据结构

 [Year]                 VARCHAR(4)        
,[Month]                VARCHAR(4)        
,AType                  VARCHAR(20)       
,PActualsID             INT               
,EID                    VARCHAR(12)       
,CID                    INT               
,CGId                   INT               
,EMargin                NUMERIC(17,3)     
,Period                 DATETIME          
,PLID                   INT               
,PCID                   INT               
,PSID                   INT               
,VPID                   INT               
,VSID                   INT               
,STID                   INT               

查询 1

SELECT * from vw_ActualAllocation_New
where   EntityId = '442105'       and 
        Period   >= '01-Jan-2017' AND  Period  < '01-Jan-2018'

这会返回大约 94 条记录。

查询 2

SELECT Count(*) from vw_ActualAllocation_New
where   EID = '442105'    and 
        Period   >= '01-Jan-2017' AND  Period  < '01-Jan-2018'

这会返回一个错误

Msg 241, Level 16, State 1, Line 1 转换时转换失败 字符串中的日期和/或时间。

查询 3

SELECT Count(EMargin) from vw_ActualAllocation_New
where   EID = '442105'    and 
        Period   >= '01-Jan-2017' AND  Period  < '01-Jan-2018'

这将返回我的计数为 94。

请注意 EMargin 是 NUMERIC 数据类型和所有其他类型 例如 int 和 varchar 返回相同的错误。

请分享您对这两种行为之间差异的看法。

SQL Server 环境:Microsoft SQL Server 2012(Build 7601:Service Pack 1)(管理程序)

更新 - 查看代码

CREATE VIEW [dbo].[vw_ActualAllocation_New]
SELECT D.Year, D.Month, A.AType, A.PAID, D.EntityID, D.CustomerID
,B.CGId, SUM(A.EBITRMargin) AS EBITRMargin
,CONVERT(DATETIME,D.Month + '-01-' + D.Year) AS Period, D.PLID, D.PCID
,D.PSID, D.VPID, D.VSID,D.STID 
FROM  dbo.AAllocations AS A 
INNER JOIN dbo.PActuals AS D ON D.PActualsID = A.PActualsID AND D.Active = 1 
INNER JOIN dbo.Customer AS B ON D.CustomerID = B.CustomerID AND D.EntityID = 
B.EntityID 
INNER JOIN dbo.AStatus AS C ON  A.ASID = C.ASID
WHERE (A.Active = 1) AND (C.Active = 1) AND (C.Reference = 'Actuals') AND 
(C.Status = 1)
GROUP BY D.Year, D.Month, A.AType, A.PAID, D.EntityID, D.CustomerID, B.CGId, 
D.PLID, D.PCID, D.PSID, D.VPID, D.VSID, D.STID 

结论更新

根据Gordon的建议得出结论,如果您觉得自己可能有其他想法,请在此处发布 我还尝试将视图中的数据放入一个新表中,并且工作正常。直接从视图访问时会发生问题。视图生成发生在大量数据中,由于其庞大的规模和隐私协议,无法在此处发布。感谢您了解我的局限性并帮助我

【问题讨论】:

Period 的数据类型是什么?你真的应该使用符合 ANSI 的日期字符串。 该错误与您在count 中包含的内容无关。如错误消息所示,日期比较失败。 我有点同意 vkp 但如果它与下面的查询相同,它应该不会失败。老实说很困惑。 答案总是一样的。这两个查询生成不同的执行计划(因为一个只需要测试是否存在行,一个需要找到非空的EMargin 值)。反过来,在其中一个计划中,谓词被推到比另一个“更深”的地方,因此日期时间和字符串值之间的比较发生在 早于的某个“守卫”谓词之前防止发生不适当的比较(当字符串不包含类似日期的内容时) 可重现的例子,否则它没有发生。 【参考方案1】:

问题的最可能原因是这行代码:

CONVERT(DATETIME, D.Month + '-01-' + D.Year) AS Period

在 SQL Server 中,如果没有指定格式或使用标准格式(SQL Server 首选 YYYYMMDD,但我认为 YYYY-MM-DD 也是可以接受的),则永远不应在字符串到日期中使用 CONVERT()

在旧版本的 SQL Server 中,您可以:

CONVERT(DATE, d.Year + RIGHT('00' + D.Month, 2) + '01') as period

此转换将始终有效。在较新的版本中,使用datefromparts()

DATEFROMPARTS(d.Year, d.Month, 1) as Period

为什么会这样?我推测日期格式被解释为 DD-MM-YYYY 而不是 MM-DD-YYYY。换句话说,你认为的 2 月 1 日实际上是 1 月 2 日。

此外,实体“442105”的期间值都转换为合理的日期。 WHERE 子句过滤掉错误值。问题在于其他实体和问题,正如 Damien 指出的那样,是在执行引擎中评估值的位置。

【讨论】:

我在从视图中选择 * 时获取值,但在应用 count(*) Cannot construct data type date, some of the arguments have values which are not valid.时出现错误@ 之前的时间段是日期时间,现在是日期。 我正在尝试这样SELECT count(*) from vw_ActualAllocation_New_Edit WHERE EntityId = '442105' and Period &gt;= '2017-01-01' AND Period &lt; '2018-01-01' 我也以你的方法结束

以上是关于为啥 SQL 计数(*)与 SQL 计数(数字)存在行为差异的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的 SQL 数据集总是返回 0 计数?

如何把SQL server 表里插入转换科学计数法的方法。

如何禁止oracle采用科学计数法

为啥这个记录计数返回 1?

PL/SQL 查询结果采用科学计数法,如何改变属性。

分组值在 SQL (maria DB) 中按时间存储直到零,并与计数一起进行求和