【问题标题】:Select the lowest date from a range and exclude another range从一个范围中选择最低的日期并排除另一个范围
【发布时间】:2013-06-26 09:32:55
【问题描述】:

我有一张桌子(我们称之为audit),看起来像这样:

+--------------------------------------------------------------------------+
| id | recordId | status | mdate                   | type  | relatedId     |
+--------------------------------------------------------------------------+
| 1  | 3006     | A      | 2013-04-03 23:59:01.275 | type1 | 1             |
| 2  | 3025     | B      | 2013-04-04 00:00:02.134 | type1 | 1             |
| 3  | 4578     | A      | 2013-04-04 00:04:30.033 | type2 | 1             |
| 4  | 7940     | C      | 2013-04-04 00:04:32.683 | type1 | <NULL>        |
| 5  | 3006     | D      | 2013-04-04 00:04:32.683 | type1 | <NULL>        |
| 6  | 4822     | E      | 2013-04-04 00:04:32.683 | type2 | <NULL>        |
| 7  | 3006     | A      | 2013-04-04 00:06:54.033 | type1 | 2             |
| 8  | 3025     | C      | 2013-04-04 00:06:54.033 | type1 | 2             |

...以及数百万行。还有一张我们称之为related的表:

+-------------+
| id | source |
+-------------+
| 1  | src_X  |
| 2  | src_Y  |
| 3  | src_Z  |
| 4  | src_X  |
| 5  | src_X  |

...以及数十万行。

两个表上的列都比这些列多,但这就是我们描述问题所需的全部内容。 relatedId 列连接到 related 表。 recordId 也加入了另一个表,audit 中会有多个条目具有相同的recordId

我正在尝试创建一个将产生以下输出的查询:

+-----------------+
| source  | count |
+-----------------+
| src_X   | 1643  |
| src_Y   | 255   |
| NULL    | 729   |
+-----------------+

计数是audit 中具有给定type(例如"type1")并且处于一组状态(例如"A", "B", "C")内的记录数,然后将这些状态从外部连接到@ 987654335@ 并按source 分组。

问题是我只想包含来自audit 中某个日期范围内的记录,并且我也只想加入从auditrelated 的每个在该范围内最旧的条目recordId。此外,我想忽略任何符合 typestatus 条件的记录,但有一个比日期范围更早的 recordId 条目。

所以,从上面的示例数据中澄清一下:假设我想要type1 的类型和"A", "B", "C" 的状态值,日期范围为2013-04-042013-04-05。第 2 行和第 4 行将包括在计数中。第 3 行被排除在外,因为它的 type 不正确。由于状态不正确,第 5 行被排除在外。第 6 行被排除,因为状态和类型都不正确。第 1 行被排除在外,因为它超出了日期范围。第 7 行也被排除在外,因为还有另一行(第 1 行)与具有相同 recordId 的状态和类型标准匹配,该 recordId 早于日期范围的开始。第 8 行被排除在外,因为第 8 行和第 2 行具有相同的recordId 并符合条件,但我们只计算范围内两者中最旧的记录。

换句话说,我只想计算给定recordId的条目第一次出现在表中并且在目标日期范围内。

我们提出了以下建议:

WITH data (recordId, id) AS (
    SELECT a.recordId, MIN(a.id)
    FROM audit a
    WHERE a.status in ('A','B','C')
        AND type = 'type1'
    GROUP BY a.recordId
)
SELECT r.source, COUNT(*)
FROM data d
    JOIN audit a ON d.id = a.id
    LEFT JOIN related r ON a.relatedId = r.id
WHERE a.mdate >= '2013-04-04 00:00:00.000'
    and a.mdate < '2013-04-05 00:00:00.000' 
GROUP BY r.source

这将在 MSSQL Server 2008 上运行,目前依赖于审计表 ID 是自动生成的这一事实。由于 id 是在插入记录时生成的,并且 mdate 也是插入时间戳,并且记录一旦插入就永远不会更新,我认为这是可以的。该查询似乎在一组有限的测试数据上给出了正确的输出,但我希望得到第二个意见。

  • 这个查询看起来正常吗?
  • 可以提高性能吗?

【问题讨论】:

  • 计算表表达式中的日期范围可能会提高性能。
  • 好点。将AND a.mdate &lt; '2013-04-05 00:00:00.000' 添加到计算表将有助于限制它返回的记录数。
  • 为了提高查询性能也要注意索引。在 WHERE 子句字段上使用索引,加入字段,并再次测试性能。..

标签: sql sql-server query-performance


【解决方案1】:

您可以使用ROW_NUMBER() 函数根据 RecordId 和 mDate 对记录进行排名,然后将结果限制在您指定日期之间第一次出现的位置。

WITH data  AS 
(   SELECT  a.relatedId, a.mdate, rn = ROW_NUMBER() OVER(PARTITION BY a.RecordId ORDER BY a.mdate)
    FROM    audit a
    WHERE   a.status in ('A','B','C')
    AND     type = 'type1'
)
SELECT  r.source, [Count] = COUNT(*)
FROM    data d
        LEFT JOIN related r 
            ON d.relatedId = r.id
WHERE   d.rn = 1
AND     d.mdate >= '2013-04-04 00:00:00.000'
AND     d.mdate < '2013-04-05 00:00:00.000' 
GROUP BY r.source;

我不确定这是否会比您当前的解决方案更好,但会解决依赖按时间顺序插入的问题。如果按时间顺序插入不是问题,您可以将ROW_NUMBER() 函数中的ORDER BY 更改为使用ID,因为对聚集键的排序会更快。

从外部看性能调优是非常困难的,为了猜测它,我们需要查看相关表上的索引,以及查询的执行计划。然后您可以确定瓶颈,以及哪些索引可以提高性能。

This SQL Fiddle 显示两个查询(我的和你的)最终得到相同的结果,但是当您查看 IO 统计信息时,您可以看到您的查询:

(2 row(s) affected)
Table 'Related'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Audit'. Scan count 2, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

使用 ROW_NUMBER() 你会得到:

(2 row(s) affected)
Table 'Related'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Audit'. Scan count 1, logical reads 1, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

关键因素是更少的逻辑阅读。快速查看执行计划表明 ROW_NUMBER() 解决方案少了一个分支,估计为批处理成本的 37%,而您的解决方案为 63%,因此在这一小组数据上,它似乎是一个性能改进。

但是我只能从这么小的数据样本中看出这么多,一些解决方案不能很好地扩展,正如我所说,这将取决于您的数据大小和分布。我的建议是尝试不同的解决方案,通过检查 IO 统计信息和执行计划找到瓶颈。

例如,查看 CTE 的执行计划,这占我查询的查询成本的 50%:

通过添加这个索引:

CREATE INDEX IX_Audit_ALL ON Audit (recordId, MDate, RelatedID, status, type)

我能够将其降低到查询成本的 18%。

但是,实际上,在不了解更多信息的情况下,我不能肯定地说该索引会 (a) 帮助查询您的数据,并且 (b) 它不会通过减慢插入/更新速度而导致数据库出现其他问题.

【讨论】:

  • 感谢您的全面回答!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-03
  • 1970-01-01
相关资源
最近更新 更多