有效地为每位患者选择最新费用

时间:2011-01-21 23:05:20

标签: sql sql-server sql-server-2005

我在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

任何人都有任何想法(希望)更快,更快?

4 个答案:

答案 0 :(得分:5)

你想要每个12字节的700万条记录(假设每个ID列有32位整数 - 你的CPT代码可能更大,因为我的经验说它们在某些情况下可以有文本组件)。这是84兆字节的原始数据,忘记了协议中的任何管理开销。我认为该数据的一分钟非常好 - 只需将这么多信息从服务器通过网络线路传输到您的计算机,这意味着您的查询必须几乎立即分发结果。减少你的结果集,你的时间会有所改善。

答案 1 :(得分:3)

一分钟可以回到7米的行?我非常怀疑你能够做得更好,除非你能说服你的DBA在这些表中添加一些索引和/或做一些重构。

虽然我很确定你无法改变事物,但我认为你的数据模型不正确。从E-R建模角度来看:

  • 约会是依赖于患者的实体(例如,约会的存在取决于进行预约的患者的存在。如果特定患者从患者表中删除,则任何与该患者相关的预约必须也被删除。)
  • 在我看来,ApptCPT表实际上是一个关联实体,依赖于Appt和CPT,它实现了Appt和CPT之间的多个关系(有一个CPT表,正确?,如实体关系模型中所定义。

从属实体的主键是它所依赖的实体/实体的主键的组合,以及对从属实体的唯一性做出贡献的任何其他属性。

这意味着PatientID分配到Appt并成为其主键的一部分,新Appt主键的所有组件都分配到ApptCPT,成为其主键的一部分。完成重构后,您的主键应如下所示:

  • 患者表。 PatientID
  • Appt。 PatientID + ID。
  • ApptCPT。 PatientID + ID + CptID。
    这假设appt + cpt的组合是唯一的。如果给定的CPT代码可以与单个Appt多次关联,那么当然,您需要添加ID列。

现在您的查询更加简单:您只需查看单个表(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