【问题标题】:How to quickly select DISTINCT dates from a Date/Time field, SQL Server如何从日期/时间字段中快速选择 DISTINCT 日期,SQL Server
【发布时间】:2009-08-20 16:32:38
【问题描述】:

我想知道是否有一个性能良好的查询可以从 SQL Server 中具有日期时间字段的表中选择不同的日期(忽略时间)。

我的问题是没有让服务器真正做到这一点(我已经看到this question,并且我们已经使用 DISTINCT 进行了类似的操作)。问题是是否有任何技巧可以更快地完成它。使用我们正在使用的数据,我们当前的查询返回约 80 个不同的日期,其中有约 40,000 行数据(在另一个索引列上过滤后),日期列上有一个索引,并且查询总是设法采取5 秒以上。太慢了。

更改数据库结构可能是一种选择,但不太理想。

【问题讨论】:

    标签: sql-server datetime distinct


    【解决方案1】:

    我使用了以下内容:

    CAST(FLOOR(CAST(@date as FLOAT)) as DateTime);
    

    这会通过将日期转换为float 并截断“时间”部分(即float 的小数点)来删除日期中的时间。

    看起来有点笨拙,但在我整天重复使用的大型数据集(约 100,000 行)上效果很好。

    【讨论】:

      【解决方案2】:

      这对我有用:

      SELECT distinct(CONVERT(varchar(10), {your date column}, 111)) 
      FROM {your table name}
      

      【讨论】:

        【解决方案3】:

        涉及对日期时间字段进行 CAST 或 TRUNCATE 或 DATEPART 操作的每个选项都有相同的问题:查询必须扫描整个结果集(40k)才能找到不同的日期。不同实现之间的性能可能略有不同。

        您真正需要的是有一个可以在眨眼间产生响应的索引。您可以拥有一个带有索引的持久计算列(需要更改表结构)或索引视图(requires Enterprise Edition for QO to consider the index 开箱即用)。

        持久化计算列:

        alter table foo add date_only as convert(char(8), [datetimecolumn], 112) persisted;
        create index idx_foo_date_only on foo(date_only);
        

        索引视图:

        create view v_foo_with_date_only
        with schemabinding as 
        select id
            , convert(char(8), [datetimecolumn], 112) as date_only
        from dbo.foo;   
        create unique clustered index idx_v_foo on v_foo_with_date_only(date_only, id);
        

        更新

        要完全消除扫描,可以使用 GROUP BY 欺骗索引视图,如下所示:

        create view v_foo_with_date_only
        with schemabinding as 
        select
            convert(char(8), [d], 112) as date_only
            , count_big(*) as [dummy]
        from dbo.foo
        group by convert(char(8), [d], 112)
        
        create unique clustered index idx_v_foo on v_foo_with_date_only(date_only)
        

        查询select distinct date_only from foo 将使用此索引视图。在技​​术上仍然是扫描,但在已经“不同”的索引上,因此只扫描所需的记录。我认为它是一种 hack,我不建议将其用于实时生产代码。

        AFAIK SQL Server 不具备通过跳过重复扫描真实索引的能力,即。寻找顶部,然后寻找大于顶部,然后连续寻找大于最后找到的东西。

        【讨论】:

        • 有什么办法可以在SQL Server中使用SKIP SCAN吗?我刚刚在2M 表上尝试了您的解决方案,结果变得更糟(DISTINCT CAST(...)DATETIME 字段上使用850 msHash Match AggregateDISTINCT date 使用1800 msStream Aggregate)。 OracleMySQL 只会跳过索引中的不同字段,SQL Server 不会这样做。
        • 创建索引后需要选择不同的 date_only。
        • @Remus:我确实创建了一个索引,优化器确实使用了它。
        • 在我的 2M 记录测试中,扫描和聚合要快得多。但是,SKIP SCAN 是另外一回事,如果您在 (ColA, ColB) 上有索引 idx1 并且 ColB 上的谓词“跳过扫描”将使用 idx1,尽管 ColA 没有谓词。确实,尽管 SQL Server 中没有 SKIP SCAN。
        • 关于跳过扫描的 Microsoft Connect 项:connect.microsoft.com/SQLServer/feedback/details/695044/…
        【解决方案4】:

        最简单的方法是只为日期部分添加一个计算列,然后选择它。如果您不想更改表格,可以在视图中执行此操作。

        【讨论】:

          【解决方案5】:

          我不确定为什么您现有的查询会占用 40,000 行超过 5 秒的时间。

          我刚刚对一个有 100,000 行的表尝试了以下查询,它在不到 0.1 秒内返回。

          SELECT DISTINCT DATEADD(day, 0, DATEDIFF(day, 0, your_date_column))
          FROM your_table
          

          (请注意,此查询可能无法利用日期列上的任何索引,但它应该相当快,假设您没有每秒执行数十次。)

          【讨论】:

            【解决方案6】:

            更新:

            下面的解决方案在2M 表上测试了效率,但采用40 ms

            索引计算列上的普通DISTINCT 采用9 seconds

            有关性能详情,请参阅我的博客中的此条目:


            不幸的是,SQL Server 的优化器既不能做 Oracle 的 SKIP SCAN 也不能做 MySQLINDEX FOR GROUP-BY

            总是Stream Aggregate 需要很长时间。

            您可以使用递归 CTE 构建可能日期的列表并将其与您的表格连接:

            WITH    rows AS (
                    SELECT  CAST(CAST(CAST(MIN(date) AS FLOAT) AS INTEGER) AS DATETIME) AS mindate, MAX(date) AS maxdate
                    FROM    mytable
                    UNION ALL
                    SELECT  mindate + 1, maxdate
                    FROM    rows
                    WHERE   mindate < maxdate
                    )
            SELECT  mindate
            FROM    rows
            WHERE   EXISTS
                    (
                    SELECT  NULL
                    FROM    mytable
                    WHERE   date >= mindate
                            AND date < mindate + 1
                    )
            OPTION  (MAXRECURSION 0)
            

            这会比Stream Aggregate效率更高

            【讨论】:

            • 构建一个日期表,然后半连接到原始表是一个很好的解决方案。恕我直言,具有索引或索引视图的持久列的额外开销仅在您必须非常频繁地执行此操作时才有意义(任意猜测:每天数百次)。我总是更愿意首先尝试提出一个更好的查询,而不是向数据库结构添加更多的复杂性/开销。
            【解决方案7】:

            我用过这个

            SELECT
            DISTINCT DATE_FORMAT(your_date_column,'%Y-%m-%d') AS date
            FROM ...
            

            【讨论】:

            • 不确定效率,但这绝对是最漂亮的方法。
            【解决方案8】:

            您对其他过滤列的谓词是什么?您是否尝试过是否从另一个过滤列上的索引中获得改进,然后是日期时间字段?

            我在这里很大程度上是在猜测,但是 5 秒将一组可能 100000 行过滤到 40000 行,然后进行排序(这可能是发生的事情)对我来说似乎不是一个不合理的时间。为什么说它太慢?因为它不符合预期?

            【讨论】:

              【解决方案9】:

              只需转换日期:dateadd(dd,0, datediff(dd,0,[Some_Column]))

              【讨论】:

                【解决方案10】:

                如果您想避免步骤提取或重新格式化日期 - 这可能是延迟的主要原因(通过强制全表扫描) - 您别无选择,只能存储日期时间的一部分,不幸的是,这需要更改数据库结构。

                如果您使用的是 SQL Server 2005 或更高版本,那么持久化计算字段是可行的方法

                除非另有说明,否则计算列是虚拟列 没有物理存储在表中。他们的值每重新计算一次 在查询中引用它们的时间。数据库引擎使用 PERSISTED CREATE TABLE 和 ALTER TABLE 语句中的关键字以物理存储 表中的计算列。当任何列时更新它们的值 这是他们计算变化的一部分。通过将计算列标记为 PERSISTED,您可以在确定性的计算列上创建索引 但不精确。

                【讨论】:

                • 延迟的主要原因是扫描和排序产生不同的。除非在标量操作中发生非常复杂的事情,否则数据库中的延迟总是与数据访问有关,而不是与标量操作有关。
                • 这是延迟的主要原因,因为它会强制进行全表扫描 - 抱歉,应该说清楚
                猜你喜欢
                • 1970-01-01
                • 2015-05-14
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2010-12-10
                • 2014-08-09
                相关资源
                最近更新 更多