【问题标题】:Improve WITH CTE Query Optimization SQL Server 2008 R2改进 WITH CTE 查询优化 SQL Server 2008 R2
【发布时间】:2014-03-07 18:11:40
【问题描述】:

我有一个查询,其中有多个查询,它们在最后捆绑在一起。我正在优化它,让它运行得更快。我正在使用 3 个查询来创建表并将记录插入其中。我查看了我的执行计划,发现其中一个消耗了相对于批次的 100% 的成本,因此我将其更改为 ;WITH CTE AS() 的形式,这使其成本降低到批次的 64%。

现在我看到这个查询中的Hash Match (Aggreagate) 代表了这个查询成本的 41%。从估计的计划中还说Missing Index (Impact 71.7682): CREATE NONCLUSTERED INDEX.... 这是可以在WITH 查询中完成的吗?

这是我试图提高效率的部分

;WITH ERCNT AS (
    SELECT A.MRN
    , A.VISIT_ID
    , A.VISIT_DATE
    , COUNT(B.VISIT_ID) AS VISIT_COUNT

    FROM 
    (
    SELECT MED_REC_NO AS MRN, vst_start_dtime AS VISIT_DATE, PT_NO AS VISIT_ID
    FROM smsdss.BMH_PLM_PtAcct_V
    WHERE
    ((
        Plm_Pt_Acct_Type = 'I'
        AND Adm_Source NOT IN 
            (
            'RP'
            )
        )
        OR pt_type = 'E')
    AND vst_start_dtime >= @SD 
    AND vst_start_dtime < @ED
    )A

    LEFT JOIN
    (
    SELECT MED_REC_NO AS MRN, VST_START_DTIME AS VISIT_DATE, PT_NO AS VISIT_ID
    FROM smsdss.BMH_PLM_PtAcct_V
    WHERE
    ((
        Plm_Pt_Acct_Type = 'I'
        AND Adm_Source NOT IN
            (
            'RP'
            )
        )
        OR pt_type = 'E')
    AND vst_start_dtime >= @SD 
    AND vst_start_dtime < @ED
)B
ON A.MRN = B.MRN
AND A.VISIT_DATE > B.VISIT_DATE
--AND A.VISIT_DATE < B.VISIT_DATE

GROUP BY A.MRN, A.VISIT_ID, A.VISIT_DATE
)

如果我需要提供更多信息,请告诉我。

谢谢,

【问题讨论】:

  • 这里有几个Links 可能是Help
  • @Zane 感谢您的链接
  • 您对这些代码有任何控制权吗?如果它们是 INT 值就好了。
  • sigh...无法控制我只是一个user

标签: sql optimization sql-server-2008-r2 query-optimization common-table-expression


【解决方案1】:

所以问题是 A 和 B 之间的左连接。我假设您无法创建查询引擎建议的索引。那么使用表变量来存储子查询的结果并在那里有索引呢?然后,您可以在 CTE 中使用该表。下面的伪代码应该会给你这个想法:

DECLARE @tmp TABLE
(
  MRN ???, VISIT_DATE ???, visit_id ???, PRIMARY KEY(MRN, VISIT_DATE)
)

INSERT INTO @tmp
    SELECT MED_REC_NO AS MRN, vst_start_dtime AS VISIT_DATE, PT_NO AS VISIT_ID
    FROM smsdss.BMH_PLM_PtAcct_V
    WHERE
    ((
        Plm_Pt_Acct_Type = 'I'
        AND Adm_Source NOT IN 
            (
            'RP'
            )
        )
        OR pt_type = 'E')
    AND vst_start_dtime >= @SD 
    AND vst_start_dtime < @ED

;WITH ERCNT AS (
    SELECT A.MRN
    , A.VISIT_ID
    , A.VISIT_DATE
    , COUNT(B.VISIT_ID) AS VISIT_COUNT

FROM @tmp A
LEFT JOIN @tmp B
ON A.MRN = B.MRN
AND A.VISIT_DATE > B.VISIT_DATE
GROUP BY A.MRN, A.VISIT_ID, A.VISIT_DATE
)

【讨论】:

  • 这是一个有趣的想法,我会试一试,看看会发生什么
  • 为什么要使用表变量?恕我直言,一个实际的临时表也可以工作,您永远不必担心数据类型(假设您使用SELECT INTO 方法)。此外,#temp 表允许更轻松地进行索引,并且可能最重要的是:它具有统计信息,而表变量则严重缺乏这些信息。
  • 我得到一个日期时间的主键约束错误,所以我现在将 visit_date 从主键参数中取出
  • @MCP_infiltrator 如果 PK 失败,这意味着您当天有多次访问。将所有列添加到 PK:PRIMARY KEY(MRN, VISIT_DATE, visit_id),应该可以解决问题。
  • @deroby PK 隐式创建聚集索引。这是您在该查询中实际需要的唯一索引。仅当查询引擎选择要使用的索引时,统计信息才有效,这里不需要。最后,表变量具有明确定义的范围。您可以在 Management Studio 的同一窗口中重新运行此查询,而无需发出 drop table #temp。
【解决方案2】:

我花了一段时间才明白(...)A(...)B 之间的区别。 使用这样的相关子查询不是更易读吗? => 恕我直言,它甚至可能更有效,因为要聚合的数据量会更小。如果你可以 shortcut Plm_Pt_Acct_Type 这可能会使其更快一些,但我没有足够的信息在表格上知道这是否会导致相同的结果..(可能不是我思考)。还有另一种可能性以某种方式shortcut B.vst_start_dtime, @SD, B.vst_start_dtime, @EDA.vst_start_dtime 在子查询中,但我现在没有时间考虑=)

PS:获得有关实际表的更多信息(约束、索引、行数等)+ 实际执行计划的打印屏幕可能会有很大帮助。

;WITH ERCNT AS (SELECT MRN         = MED_REC_NO, 
                       VISIT_ID    = PT_NO,
                       VISIT_DATE  = vst_start_dtime, 
                       VISIT_COUNT = ( SELECT COUNT(*)
                                         FROM smsdss.BMH_PLM_PtAcct_V B
                                        WHERE -- can we simply assume B.Plm_Pt_Acct_Type = A.Plm_Pt_Acct_Type ??
                                            ((
                                                B.Plm_Pt_Acct_Type = 'I'
                                                AND B.Adm_Source NOT IN
                                                    (
                                                    'RP'
                                                    )
                                                )
                                                OR B.pt_type = 'E')
                                          AND B.vst_start_dtime >= @SD 
                                          AND B.vst_start_dtime < @ED
                                          -- 'join'
                                          AND A.MED_REC_NO = B.MED_REC_NO
                                          AND A.PT_NO = B.PT_NO
                                          AND A.vst_start_dtime > B.vst_start_dtime
                                       )

                FROM smsdss.BMH_PLM_PtAcct_V A
                WHERE
                    ((
                        A.Plm_Pt_Acct_Type = 'I'
                        AND A.Adm_Source NOT IN 
                            (
                            'RP'
                            )
                        )
                        OR A.pt_type = 'E')
                    AND A.vst_start_dtime >= @SD 
                    AND A.vst_start_dtime < @ED
                )
SELECT * FROM ERCNT

【讨论】:

  • 我们不能假设 plm_pt_acct_types 是相等的,我需要指定“我”,因为以前的访问实际上可能是别的东西。我会尝试发布更多对您有帮助的信息。
  • 表格本身是一个视图,我尝试点击索引 (+) 符号并没有出现任何内容,所以我确信这将是一个限制
  • 好吧,如果它是一个比索引更复杂的视图(并且可能有不需要的副作用),但它仍然可能值得查看视图的定义(使用sp_helptext)并查看基础表是否具有正确的索引。 SQL 并不真正查询视图,而是在基础表上构建其查询计划。出于好奇,接受的答案真的比子查询快吗? (我并不特别关心这些点,只是想知道他们的执行计划可能是什么样的。)
  • 我的速度更快,不多,但是是的,我必须制定某些规范,因为我不能假设一次访问总是这样或那样,所以我必须指定每次访问看起来at 必须明确满足这些条件
猜你喜欢
  • 1970-01-01
  • 2014-05-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多