6

我在 SQL 语句中使用 DATEDIFF。我正在选择它,我也需要在 WHERE 子句中使用它。这个说法不成立...

SELECT DATEDIFF(ss, BegTime, EndTime) AS InitialSave
FROM MyTable
WHERE InitialSave <= 10

它给出了消息:无效的列名“InitialSave”

但是这个说法很好用......

SELECT DATEDIFF(ss, BegTime, EndTime) AS InitialSave
FROM MyTable
WHERE DATEDIFF(ss, BegTime, EndTime) <= 10

我的程序员说这是低效的(好像我调用了两次函数)。

所以两个问题。为什么第一个语句不起作用?使用第二条语句来做这件事效率低吗?

4

5 回答 5

7

注意:当我最初写这个答案时,我说其中一个列上的索引可以创建一个比其他答案执行得更好的查询(并提到了 Dan Fuller 的)。但是,我并没有 100% 正确地思考。事实是,如果没有计算列或索引(物化)视图,则需要进行全表扫描,因为要比较的两个日期列来自同一个表!

我相信下面的信息仍然有价值,即 1) 在正确的情况下提高性能的可能性,例如在不同表中的列之间进行比较时,以及 2) 促进 SQL 开发人员遵循最佳实践和重塑的习惯他们的思路是正确的。

使条件变得可行

我所指的最佳实践是将一列单独移动到比较运算符的一侧,如下所示:

SELECT InitialSave = DateDiff(second, T.BegTime, T.EndTime)
FROM dbo.MyTable T
WHERE T.EndTime <= T.BegTime + '00:00:10'

正如我所说,这不会避免对单个表进行扫描,但是,在这种情况下,它可能会产生巨大的影响:

SELECT InitialSave = DateDiff(second, T.BegTime, T.EndTime)
FROM
   dbo.BeginTime B
   INNER JOIN dbo.EndTime E
      ON B.BeginTime <= E.EndTime
      AND B.BeginTime + '00:00:10' > E.EndTime

EndTime在这两种情况下,现在单独在比较的一侧。假设该BeginTime表的行数要少得多,并且该EndTime表在 column 上有一个索引EndTime,这将比使用DateDiff(second, B.BeginTime, E.EndTime). 它现在是sargable,这意味着有一个有效的“搜索参数”——所以当引擎扫描表时BeginTime,它可以搜索EndTime。需要仔细选择哪一列单独位于运算符的一侧 -BeginTime通过做一些代数切换到AND B.BeginTime > E.EndTime - '00:00:10'

DateDiff 的精度

我还应该指出,DateDiff它不会返回经过的时间,而是计算跨越的边界数。如果调用DateDiffusing seconds 返回1,这可能意味着3 ms经过的时间,或者可能意味着1997 ms!这本质上是 +- 1 个时间单位的精度。为了获得更好的 +- 1/2 时间单位精度,您需要以下查询0来比较EndTime - BegTime

SELECT DateDiff(second, 0, EndTime - BegTime) AS InitialSave
FROM MyTable
WHERE EndTime <= BegTime + '00:00:10'

现在,最大舍入误差总共只有一秒,而不是两秒(实际上是 floor() 操作)。请注意,您只能减去datetime数据类型 - 减去一个date或一个time值,您必须转换为datetime或使用其他方法来获得更好的精度(一大堆DateAddDateDiff可能还有其他垃圾,或者可能使用更高的精度时间单位和划分)。

在计算小时、天或月等较大单位时,这一原则尤其重要。A DateDiffof1 month可能相隔 62 天(想想 2013 年 7 月 1 日 - 2013 年 8 月 31 日)!

于 2009-05-15T18:52:54.503 回答
5

您无法在 where 语句中访问 select 语句中定义的列,因为它们直到 where 执行后才会生成。

但是你可以这样做

select InitialSave from 
(SELECT DATEDIFF(ss, BegTime, EndTime) AS InitialSave
FROM MyTable) aTable
WHERE InitialSave <= 10

作为旁注 - 这基本上将 DATEDIFF 移动到 where 语句中,就其首次定义的位置而言。在 where 语句中的列上使用函数会导致索引不能被有效地使用,如果可能的话应该避免,但是如果你必须使用 datediff 那么你必须这样做!

于 2009-05-15T18:43:58.517 回答
3

除了使它“工作”之外,您还需要使用索引

使用带有索引的计算列或带有索引的视图,否则您将进行表扫描。当你得到足够多的行时,你会感觉到扫描慢的痛苦!

计算列和索引:

ALTER TABLE MyTable ADD
    ComputedDate  AS DATEDIFF(ss,BegTime, EndTime)
GO
CREATE NONCLUSTERED INDEX IX_MyTable_ComputedDate  ON MyTable 
    (
    ComputedDate
    ) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

创建视图和索引:

CREATE VIEW YourNewView
AS
SELECT
    KeyValues
        ,DATEDIFF(ss, BegTime, EndTime) AS InitialSave
    FROM MyTable
GO
CREATE CLUSTERED INDEX IX_YourNewView
    ON YourNewView(InitialSave)
GO
于 2009-05-15T19:09:36.617 回答
2

您必须使用该函数而不是列别名 - 它与 count(*) 等 PITA 相同。

于 2009-05-15T18:40:42.630 回答
1

作为替代方案,您可以使用计算列

于 2009-05-15T18:42:19.067 回答