我有一个表(让我们称之为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
内包含特定日期范围内的记录,而且我也只想在最早的audit
到related
之间加入每个recordId
在该范围内的条目。此外,我希望忽略符合type
和status
条件的所有记录,但要使用比日期范围更早的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也是插入时间戳,并且记录在插入后永远不会更新,我认为这没关系。查询似乎在一组有限的测试数据上给出正确的输出,但我希望得到第二个意见。
答案 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%,因此在这一小部分数据上,它似乎是一个性能改进。
然而我从这么小的数据样本中可以看出这么多,有些解决方案不能很好地扩展,正如我所说,它将取决于你的数据大小和分布。我的建议是尝试不同的解决方案,通过检查IO统计数据和执行计划找到瓶颈。
例如,查看CTE的执行计划,这占我查询的查询成本的50%:
添加此索引:
CREATE INDEX IX_Audit_ALL ON Audit (recordId, MDate, RelatedID, status, type)
我能够将此减少到查询费用的18%。
然而,实际上在不知道更多的情况下我不能肯定地说这个索引会(a)帮助这个查询查询你的数据和(b)它不会通过减慢插入/更新来减少数据库的其他问题