使用 between 运算符 SQL Server 2008 时的索引策略

woo*_*gie 5 index sql-server optimization

我有一个大表 ~2500 万行的结构

CREATE TABLE [dbo].[rx](
            [pat_id] [int] NOT NULL,
            [fill_Date] [date] NOT NULL,
            [script_End_Date]  AS (dateadd(day,[dayssup],[filldate])) persisted,
            [drug_Name] [varchar](50) NULL,
            [days_Sup] [int] NOT NULL,
            [quantity] [float] NOT NULL,
            [drug_Class] [char](3) NOT  NULL,
            CHECK(fill_Date <=script_End_Date
PRIMARY KEY NONCLUSTERED 
(
          [clmid]
)


create clustered index ix_rx_temporal on rx(fill_date asc, script_end_date asc, pat_id asc)
Run Code Online (Sandbox Code Playgroud)

永远不会查询此表上的主键。迄今为止,该表最常用于涉及日期范围的查询。我有一个带有结构的日历表

CREATE TABLE [dbo].[Calendar](
             [cal_date] [date] PRIMARY KEY,
[Year] AS YEAR(cal_date) PERSISTED,
[Month] AS MONTH(cal_date) PERSISTED,
[Day] AS DAY(cal_date) PERSISTED,
             [julian_seq] AS 1+DATEDIFF(DD, CONVERT(DATE, CONVERT(varchar,YEAR(cal_date))+'0101'),cal_date));
Run Code Online (Sandbox Code Playgroud)

我试图加速的查询是:

;WITH x 
     AS ( 
        --join finds the amount of distinct drugs that a person was prescribed on a given day
        SELECT rx.pat_id, 
               c.cal_date, 
               Count(DISTINCT rx.drug_name) AS distinctDrugs 
         FROM   rx, 
                calendar AS c 
         WHERE  c.cal_date BETWEEN rx.fill_date AND rx.script_end_date 
         GROUP  BY rx.pat_id, 
                   c.cal_date), 
     y 
     AS ( 
        --makes a sequence so that contiguous dates can be grouped together as a date range 
        SELECT x.pat_id, 
               x.distinctdrugs, 
               c2.julian_seq- Row_number() 
                         OVER( 
                           partition BY x.pat_id, distinctdrugs 
                           ORDER BY x.cal_date) AS rn, 
               x.cal_date 
         FROM   x, 
                calendar AS c2 
         WHERE  c2.cal_date = x.cal_date) 
--finds the max and minimum dates which a person was taking X amoung of drugs 
SELECT y.pat_id, 
       Min(y.cal_date)    AS min_overlap, 
       Max(y.cal_date)    AS max_overlap, 
       Min(distinctdrugs) AS distinctDrugs 
FROM   y 
GROUP  BY y.pat_id, 
          rn 
Run Code Online (Sandbox Code Playgroud)

我尝试了以下几个索引

CREATE NONCLUSTERED INDEX [ix_rx2] ON [dbo].[rx] 
(
[drug_name] ASC,
[drug_class] ASC,
[fill_date] ASC,
[script_end_date] ASC
)
Run Code Online (Sandbox Code Playgroud)

CREATE NONCLUSTERED INDEX [ix_rx3] ON [dbo].[rx] 
(
[fill_date] ASC,
[script_end_date] ASC
)
INCLUDE ( [pat_id],
[drug_class],
[drug_name])
Run Code Online (Sandbox Code Playgroud)

每当我检查执行计划时,都没有使用 rx 表上的任何索引。执行计划还有其他方面,但大部分看起来像 在此处输入图片说明

我什至尝试删除当前的聚集索引并仅使用 (fill_date,admi_date) 但我仍然无法找到一种方法来避免在执行计划中遇到占用绝大多数资源的哈希匹配运算符这个查询。rx 表上的主键永远不会被查询,我将主要使用这个表进行涉及日期范围的查询。表上的索引具有名义上的碎片,并且索引的密度都非常低。我能做些什么来加速这个查询,或者我会受到硬件的限制吗?

A-K*_*A-K 2

这不是一个完整的答案,我现在没有时间,所以让我分享一些想法。完整的答案将是巨大的,我不确定你想知道细节。

我已经处理各种时态查询好几年了,并在这个过程中学到了很多东西。因此,我宁愿不必在我的生产系统中优化您的查询。我会尽力避免使用 T-SQL 来解决它。这是一个复杂的问题。Itzik Ben-Gan 曾多次撰写有关“间隙和孤岛”的文章,其中包括他最新书中有关 OLAP 函数的一章。你的问题是间隙和岛屿的变化。

首先,我会考虑将所有数据读取到客户端并使用循环来解决它。我知道,它需要通过网络发送数据,但 Java/C++/C# 中的快速循环在大多数情况下对我来说效果很好。例如,有一次我正在努力处理涉及时间序列和时态数据的查询。当我将大部分逻辑移至客户端时,C# 解决方案的长度缩短了数倍,并且运行速度提高了 20,000 倍。这不是拼写错误——快了两万倍

在T-SQL中解决此类问题还有另一个问题——你的性能可能不稳定。如果一个查询很复杂,突然优化器可以选择另一个计划,它的运行速度会慢很多倍,我们必须再次优化它。

或者,我会考虑以不同的方式存储数据。现在我看到两种可能的方法。

首先,我们可以使用此表来代替存储间隔:

ClientId,
PrescriptionId,
DrugId,
Date
Run Code Online (Sandbox Code Playgroud)

我们可以使用可信约束来确保每个 PrescriptionId 涵盖一系列日期,没有间隙和重叠,以便一个间隔存储为一个完整的日期序列。

注意:我知道您在第一个子查询中使用 DISTINCT,因此您假设一个人可以在一天内从多个处方中服用一种药物。为了简单起见,我不会做出这个假设。你确定这是一个正确的假设吗?如果是的话,我们将不得不改变设计。

一旦我们有了这个表,我们基本上就可以将您的第一个子查询具体化为索引视图:

SELECT ClientId,Date,COUNT_BIG(*) AS DistinctDrugs
GROUP BY ClientId,
Date
Run Code Online (Sandbox Code Playgroud)

完成后,您可以使用第二个子查询将数据点分组为间隔,或者只是在客户端上运行它,其中可以将其作为一个简单的循环来解决。

第二种方法:我将存储事件序列,而不是间隔。会有两种事件:间隔开始和间隔结束。对于每个事件,我都会存储一个运行总数,即该事件发生后打开的事件的数量。本质上,这个运行总数是事件发生后有效的处方数量。

与前面的方法一样,每次运行查询时动态计算的大部分数据都是在此表中预先计算的。我们可以使用可信约束来确保预先计算的数据的完整性。

如果您有兴趣,我稍后可以更详细地描述它。