【问题标题】:SQL - Proper "where" clause for current monthSQL - 当前月份的正确“where”子句
【发布时间】:2011-11-30 11:53:07
【问题描述】:

在 SQL Server 上检查当月事件的正确方法是什么?为什么?

1) WHERE (DATEDIFF(month, EventTime, GETDATE())=0))

2) WHERE (YEAR(EventTime) = YEAR(GETDATE()) AND MONTH(EventTime) = MONTH(GETDATE()))

表格中的日期格式为EventTime: 2011-11-30 15:68:25.000

【问题讨论】:

  • 注意:数据类型 DATETIME 等没有这样的格式。这只是用于在屏幕上表示它们的格式,因为我们人类不喜欢阅读二进制文件等。但是,如果它们实际上作为字符串(NVARCHAR 等)存储在表中,则格式确实很重要,这会严重影响性能。
  • @Dems 啊,不,这是日期时间格式,不是 varchar,不用担心 :)

标签: sql sql-server tsql datetime


【解决方案1】:

我无法访问带有探查器的 SQL Server,因此我实际上无法给出我想要的详细答案。

问题基本上是关于哪一个允许最少的计算和最有效地使用索引。

使用字符串操作的变体具有最高的计算负载,并且根本不使用索引。所以我会跳过那些。剩下四个常用的表达方式...

SELECT * FROM date_sargable
WHERE  YEAR(value) = YEAR (getDate())
  AND MONTH(value) = MONTH(getDate())
;

SELECT * FROM date_sargable
WHERE DATEDIFF(MONTH, value, getDate()) = 0
;

SELECT * FROM date_sargable
WHERE DATEDIFF(MONTH, 0, value) = DATEDIFF(MONTH, 0, getDate())
;


SELECT * FROM date_sargable
WHERE value >= DATEADD(MONTH, DATEDIFF(MONTH, 0, getDate())    , 0)
  AND value <  DATEADD(MONTH, DATEDIFF(MONTH, 0, getDate()) + 1, 0)
;

前三个使用 INDEX SCAN,但最后一个使用 INDEX SEEK。不同之处在于查询的格式允许优化器知道您需要特定范围的数据,它们在索引的一个块中彼此相邻,并且很容易找到该块。

如果在查看执行计划时,您在一个版本中看到 SEEK,而在另一个版本中看到 SCAN,则您更有可能从 SEEK 中受益。

【讨论】:

  • 谢谢。所以简单地说,这是一个 BETWEEN 条件的问题?不是比较每条记录而是得到一个范围?
  • BETWEEN 是 &gt;=&lt;=。我更喜欢使用&gt;=&lt;。那是因为我不必关心数据是否存储到天、分钟、毫秒等。无论准确性如何,它都可以工作。但除此之外,是的,根据我的经验,范围通常表现最好。
  • @Seph :定义毫无意义?如果没有 DateAdd(),您只有 DATEDIFF() 提供的月数。您甚至不能将其隐式转换为日期。另外,请注意:并非所有函数都会混淆优化器的索引。从回忆中;例如 DATEDIFF() 仍然是 sargable。
  • 如果投反对票的人看到了这篇文章,请您评论一下原因,以便我处理这个问题?
【解决方案2】:

将连续周期的查询写为明确的范围条件。

http://use-the-index-luke.com/sql/where-clause/obfuscation/dates

因此,使用这样的东西:

WHERE EventTime between <begin-of-month> and <end-of-month>

示例代码可在同一页面上找到,但每季度进行一次过滤:

http://use-the-index-luke.com/sql/where-clause/obfuscation/dates?dbtype=sqlserver#sample_quarter_begin_end

为什么?让索引更容易。

【讨论】:

  • 我不明白这如何让事情变得更容易。如果按照这个例子,sql server 需要执行 4 次查询,而不是一次:执行 Quarter_begin 两次,Quarter_end 一次,最后是实际查询 ofc。虽然我没有足够的数据,也许还没有足够的知识来正确测试这个,但我怀疑这实际上比 OP 提供的任何一种解决方案都要慢。除了任何速度问题之外 - 它确实使整个查询变得更加复杂恕我直言。
  • @Sascha Hennig - 在实际查询开始之前,函数 (quater_end/begin) 只执行一次。如果您愿意,还可以在您的应用程序中计算边界日期(季末/开始)。这不会产生真正的性能差异。它使索引更容易,因为查询可以在 EventTime 上使用直接索引,反过来,将优于任何其他建议的变体。
  • 同意:计算范围的标量常量可能会使用“大量”文本,但它几乎不使用任何查询成本。数据库中最大的成本往往是从磁盘读取,这种方法保证了最小的磁盘读取量,将冗余读取减少到尽可能接近 0 (SEEKing)。替代方案有许多冗余读取(SCANing)。
  • @Markus Winand - 是的,即使这些功能是额外执行的,它们也只会增加一小部分性能成本。我遇到的主要问题是相当的复杂性 - 但后来我的示例在一个很小的 ​​4 元组表上进行了尝试和测试 =D 所以是的,现在对我来说确实有意义。
【解决方案3】:

从逻辑上讲,它们都是。

但是,我认为第一个更简单(一个条件而不是两个)、更容易理解并且更可能是 sargable - 所以我建议使用那个。

【讨论】:

    【解决方案4】:

    如果您在该列上有一个索引,那么两个计算都会绕过该索引,因为您没有使用该列的索引,而只是索引的一部分

    WHERE (DATEDIFF(month, EventTime, GETDATE())=0))
    WHERE (YEAR(EventTime) = YEAR(GETDATE()) AND MONTH(EventTime) = MONTH(GETDATE()))
    

    你最好使用这样的东西

    WHERE EventTime BETWEEN Cast (DATEADD(dd,-(DAY(GetDate())-1),GetDate()) as Date)
                    AND Cast (DATEADD(dd,-(DAY(DATEADD(mm,1,GetDate()))),DATEADD(mm,1,GetDate())) as Date)
    

    您也可以将相同的概念与 &gt;=&lt;= 一起用于日期

    【讨论】:

      【解决方案5】:

      一个非常简单的使用to_char函数的技巧,看:

      日期示例:

      2011-11-30 15:68:25.000

      你可以这样做:

      to_char(your_field , 'MM') = 11

      或系统日期的当前月份:

      to_char(sysdate , 'MM') = 11

      一个细节,to_char函数是由sql语言支持的,而不是由一个特定的数据库支持的。

      【讨论】:

        猜你喜欢
        • 2021-04-05
        • 2022-12-05
        • 2021-07-27
        • 2016-08-29
        • 2017-04-19
        • 1970-01-01
        • 1970-01-01
        • 2011-09-15
        • 1970-01-01
        相关资源
        最近更新 更多