我在SQL Server 2005中有三个表(包括相关行):
Patient (560K rows, has many Appts)
ID (PK, Clustered)
Appt (5.8M rows, has many ApptCPTs)
ID (PK, Unique, Non-clustered)
PatientID (Indexed, Non-unique, Non-clustered)
ApptCPT (13.4M rows)
ID (PK, Clustered)
ApptID (Indexed, Non-unique, Non-clustered)
CPTID (Indexed, Non-unique, Non-clustered)
我想为每位患者获取每个CPT代码的最新ApptCPT,但到目前为止我的查询大约需要一分钟才能返回~7M行。
我当前的查询:
SELECT
p.ID AS PatientID,
MAX(ac.ID) AS ApptCPTID,
ac.CPTID
FROM
Patient p
INNER JOIN Appt a ON a.PatientID = p.ID
INNER JOIN ApptCPT ac ON ac.ApptID = a.ID
GROUP BY
p.ID, ac.CPTID
ORDER BY
p.ID, ac.CPTID
示例输出:
PatientID ApptCPTID CPTID
123456789 18627724 3088
123456789 7647238 3388
123456789 18627723 3419
123456789 9989220 3419
123456789 12190141 3448
123456789 18627721 3551
123456789 17264224 71020
123456789 15933265 77052
123456789 10095897 77057
123456789 5258166 77080
123456789 18627813 80053
123456789 18627814 80061
如果我取出MAX并将ac.ID放在GROUP BY子句中,我可以看到我使用原始查询得到了正确的数据,因为它正在为该患者获取包含该CPTID的最后一个ApptCPT,但是它非常慢。作为参考,这里是不使用MAX子句的相同输出:
PatientID ApptCPTID CPTID
123456789 18126508 3088
123456789 4596004 3088
123456789 18627724 3088
123456789 7647238 3388
123456789 18627723 3419
123456789 9989220 3419
123456789 12190141 3448
123456789 4595928 3551
123456789 9989218 3551
123456789 18627721 3551
123456789 17264224 71020
123456789 15933265 77052
123456789 10095897 77057
123456789 5258050 77057
123456789 5258166 77080
123456789 4595932 80053
123456789 18126505 80053
123456789 9989223 80053
123456789 18627813 80053
123456789 18627814 80061
任何人都有任何想法(希望)更快,更快?
答案 0 :(得分:5)
你想要每个12字节的700万条记录(假设每个ID列有32位整数 - 你的CPT代码可能更大,因为我的经验说它们在某些情况下可以有文本组件)。这是84兆字节的原始数据,忘记了协议中的任何管理开销。我认为该数据的一分钟非常好 - 只需将这么多信息从服务器通过网络线路传输到您的计算机,这意味着您的查询必须几乎立即分发结果。减少你的结果集,你的时间会有所改善。
答案 1 :(得分:3)
一分钟可以回到7米的行?我非常怀疑你能够做得更好,除非你能说服你的DBA在这些表中添加一些索引和/或做一些重构。
虽然我很确定你无法改变事物,但我认为你的数据模型不正确。从E-R建模角度来看:
从属实体的主键是它所依赖的实体/实体的主键的组合,以及对从属实体的唯一性做出贡献的任何其他属性。
这意味着PatientID分配到Appt并成为其主键的一部分,新Appt主键的所有组件都分配到ApptCPT,成为其主键的一部分。完成重构后,您的主键应如下所示:
现在您的查询更加简单:您只需查看单个表(ApptCpt),您的查询几乎肯定会有覆盖索引。
干杯!
答案 2 :(得分:1)
首先,如果您可以更改数据库,我会鼓励从ApptCPT中删除ID列。它没用,即使把它作为PK保存,使它成为聚簇索引也是一个坏主意。相反,我认为该表应该集中在ApptID和CPTID上(无论哪一个最常访问)。然后在列表中第二个的索引上放一个索引。
如果ApptID已完全暗示患者,我认为您不应该将患者添加到Appt表中。这样做的原因是这样做违反了规范化规则,允许ApptCPT行同时指向患者和不相关的约会。您将混合使用两种不同级别/粒度的数据。
在我看来,如果不将列命名为“ID”,则可以避免大量的痛苦,因为这样您就不必处理别名和列混淆。为了找出下面的查询需要一些真实的盯着,因为有很多ID列。在任何地方都将列命名为相同的名称!
您的查询可能已经是最快的了。如果你的ApptCPT上有一个CPTID和ApptID的索引,那么它将是一个覆盖索引,主表可以完全避免,这可以加快你的查询速度(因为现在引擎必须访问索引两次从该表)。你的执行计划是什么样的?您是否正在寻找或扫描什么?
另外,为什么需要返回700万行?这不适合坐在那里的用户准备好消费它,所以我不明白为什么1分钟的响应时间都那么糟糕。
如果您只是选择某些患者或某些CPT,那么此查询可能是对整个表查询的改进。但如果你确实需要所有700万行,这可能会非常糟糕:
SELECT
P.ID PatientID,
X.ID ApptCPTID,
X.CPTID
FROM
Patient P
CROSS JOIN CPTs C -- your master CPT table listing all CPTs
CROSS APPLY (
SELECT TOP 1 AC.ID, AC.CPTID
FROM
Appt A
INNER JOIN ApptCPT AC ON A.ID = AC.ApptID
WHERE
A.PatientID = P.ID
AND C.CPTID = AC.CPTID
ORDER BY AC.ID DESC
) X
WHERE
P.Something = 'Z'
AND CPT.Code IN ('A', 'B')
但是,此查询的一个好处是,用于选择最新项的列不必是SELECT子句中的列。如果你愿意,你可以说ORDER BY AC.Date DESC
。我不知道你正在做什么才能确切地知道,但这对你来说很重要。
注意:我刚刚意识到使用OUTER APPLY的CROSS JOIN会返回一堆虚假的行。所以我将它切换为CROSS APPLY,一切都应该没问题。可能还有其他措施可以让不合格的患者参与进来,但我现在没有时间考虑它。
答案 3 :(得分:1)
您是否尝试使用ROW_NUMBER()而不是GROUP BY来查找第一行?有时我发现它给了我更快的结果,特别是如果表有正确的索引。
SELECT *
FROM
(
SELECT p.ID AS PatientID,
ApptCPTID
, ac.CPTID
, RowId = ROW_NUMBER() OVER (PARTITION BY PatientId, CptId, ORDER BY PatientId, CptId, ApptCptId desc )
FROM Patient p
INNER JOIN Appt a
ON a.PatientID = p.ID
INNER JOIN ApptCPT ac
ON ac.ApptID = a.ID
) qq
WHERE qq.RowId = 1
ORDER BY Id, CPTId