从范围中选择最低日期并排除另一个范围

时间:2013-06-26 09:32:56

标签: sql sql-server query-performance

我有一个表(让我们称之为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")内的记​​录数然后将其外部连接到related并按source分组。

问题是我只想在audit内包含特定日期范围内的记录,而且我也只想在最早的auditrelated之间加入每个recordId在该范围内的条目。此外,我希望忽略符合typestatus条件的所有记录,但要使用比日期范围更早的recordId条目。

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

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

我们提出以下建议:

recordId

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

  • 这个查询看起来不错吗?
  • 可以改善其性能吗?

1 个答案:

答案 0 :(得分:4)

您可以使用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;

我不确定这会比您当前的解决方案更好,但会解决依赖于时间顺序插入的问题。如果按时间顺序插入不是问题,您可以更改ORDER BY函数中的ROW_NUMBER()以使用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%,因此在这一小部分数据上,它似乎是一个性能改进。

enter image description here

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

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

enter image description here

添加此索引:

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

我能够将此减少到查询费用的18%。

enter image description here

然而,实际上在不知道更多的情况下我不能肯定地说这个索引会(a)帮助这个查询查询你的数据和(b)它不会通过减慢插入/更新来减少数据库的其他问题