外部联接中的内部联接导致性能不足,有啥不同的方法?

Posted

技术标签:

【中文标题】外部联接中的内部联接导致性能不足,有啥不同的方法?【英文标题】:An Inner join in an Outer Join leading to peformance shortfalls, what is a different approach?外部联接中的内部联接导致性能不足,有什么不同的方法? 【发布时间】:2015-01-14 23:39:17 【问题描述】:

我的一个数据库中有一个视图,该视图正在从几个表和视图中检索以前和当前的案例官员(想想人)。问题是这些记录仅通过结束日期(saoh.Date_TO)与另一个案件官员的开始日期(saoh.Date_FROM)相同。

为了在这些记录之间创建连接,我目前正在执行外部连接和内部连接。 (这可以在下面的脚本中看到)。问题是视图有大约 300 万条记录。这意味着查询此视图需要很长时间(2-3 小时)。

有没有人对如何改进下面的 SQL 的基本设计有任何建议。

更多信息环境: MSSQL server 2008其他信息: Snapshot_Period 是一种报告工具,未链接到办案人员的日期。

   ALTER VIEW [dbo].[vw_Stage_Estate_Case_Officer_Source] AS
    SELECT  sp.SNAPSHOT_PERIOD_START_DATETIME,
            sp.SNAPSHOT_PERIOD_END_DATETIME,
            aes.APPLICATION_RID,
            aes.ESTATE_RID,
            aes.TRUSTEE_NUMBER,
            aes.TRUSTEE_TYPE,
            aes.TEAM_CODE,
            saoh.POSITION,
            saoh.DATE_FROM,
            saoh.DATE_TO,
            saoh.ERROR_CONDITION,
            saoch.USER_ID as PRIOR_CASE_OFFICER
    FROM    Stage_App_Estate_Statuses aes
            /*Standard snapshot period new join for MonthlyITS and yearlyTIS*/
            INNER JOIN  Stage_Snapshot_Period_New sp ON
                        change_date < sp.SNAPSHOT_PERIOD_END_DATETIME and
                        sp.SNAPSHOT_PERIOD_IS_FINALISED_INDICATOR = 'No'and 
                       (sp.SNAPSHOT_PERIOD_TYPE_NAME = 'MonthlyITS' or sp.SNAPSHOT_PERIOD_TYPE_NAME = 'YearlyITS')
            /*This should be inner joining to the staging table that links Case officers to team codes by region*/
            INNER JOIN  [DEV_STAGING].[dbo].[STAGE_STAF_ACTION_OFFICERS] saoh ON
                        aes.TEAM_CODE = saoh.POSITION AND LEFT(aes.ESTATE_RID,3) = LEFT(saoh.ACTION_OFFICER_RID,3)
            /*This should be inner joining to App_Estate_Statues again to get the previous UserID*/
            LEFT OUTER JOIN (SELECT staf.USER_ID,
                                staf.DATE_FROM, 
                                staf.DATE_TO,
                                a.TEAM_CODE,
                                a.ESTATE_RID
                        FROM [DEV_STAGING].[dbo].[STAGE_STAF_ACTION_OFFICERS] staf
                        INNER JOIN  Stage_App_Estate_Statuses a ON
                                    a.TEAM_CODE = staf.POSITION) saoch  ON
                         saoh.DATE_TO = saoch.DATE_FROM AND saoch.ESTATE_RID = aes.ESTATE_RID

    GO

表定义 快照时间段:不相关/必须保持原样。 Stage App Estate 状态:

CREATE TABLE [dbo].[Stage_App_Estate_Statuses](
    [ESTATE_STATUS_RID] [nvarchar](20) NOT NULL,
    [ESTATE_RID] [nvarchar](30) NULL,
    [CATEGORY] [decimal](1, 0) NULL,
    [CHANGE_DATE] [datetime2](0) NULL,
    [CHANGE_TYPE] [nvarchar](3) NULL,
    [STATUS] [nvarchar](1) NULL,
    [TEAM_CODE] [nvarchar](4) NULL,
    [TRUSTEE_NUMBER] [decimal](22, 0) NULL,
    [TRUSTEE_TYPE] [nvarchar](10) NULL,
    [APPLICATION_RID] [nvarchar](30) NULL,
    [DML_TYPE] [nvarchar](1) NOT NULL,
    [AUDIT_KEY] [int] NOT NULL
) ON [PRIMARY]

Stage_STAF_ACTION_OFFICERS

CREATE TABLE [dbo].[Stage_STAF_ACTION_OFFICERS](
    [ACTION_OFFICER_RID] [nvarchar](15) NOT NULL,
    [POSITION] [nvarchar](13) NULL,
    [DATE_FROM] [date] NULL,
    [DATE_TO] [date] NULL,
    [USER_ID] [nvarchar](32) NULL,
    [ERROR_CONDITION] [nvarchar](100) NULL,
    [EXTRACTED_DATE] [date] NULL,
    [DML_TYPE] [nvarchar](1) NULL,
    [AUDIT_KEY] [int] NOT NULL
) ON [PRIMARY]

【问题讨论】:

架构?执行计划? LEFT(aes.ESTATE_RID,3) = LEFT(saoh.ACTION_OFFICER_RID,3) 有问题。 @MitchWheat 好皮卡,模式是 DBO。左边的语句是来自源表的限制,即某些标识符被重用(可怕的源系统)。唯一的变化是这两个语句的前 3 个字符。尽管两个表的所有行的比较都是可靠的,但数据质量相当高。 @MichaelBetterton,请发布表格定义。 为什么需要派生表?我忘记了语法,但我看过。 【参考方案1】:

很难没有任何东西可以测试,所以把它当作一个起点


SELECT
    sp.SNAPSHOT_PERIOD_START_DATETIME,
    sp.SNAPSHOT_PERIOD_END_DATETIME,
    aes.APPLICATION_RID,
    aes.ESTATE_RID,
    aes.TRUSTEE_NUMBER,
    aes.TRUSTEE_TYPE,
    aes.TEAM_CODE,
    saoh.POSITION,
    saoh.DATE_FROM,
    saoh.DATE_TO,
    saoh.ERROR_CONDITION,
    (select USER_ID
        from [DEV_STAGING].[dbo].[STAGE_STAF_ACTION_OFFICERS] sao
        where sao.DATE_FROM = saoh.DATE_TO
            and (select ESTATE_RID from Stage_App_Estate_Statuses where TEAM_CODE = sao.POSITION) = aes.ESTATE_RID
    )as PRIOR_CASE_OFFICER --Even if this approach doesnt help, keep the original join as outer because there might not be a prior_case_officer
FROM
    Stage_App_Estate_Statuses aes
    /This should be inner joining to the staging table that links Case officers to team codes by region/
    INNER JOIN [DEV_STAGING].[dbo].[STAGE_STAF_ACTION_OFFICERS] saoh 
        ON aes.TEAM_CODE = saoh.POSITION 
        AND LEFT(aes.ESTATE_RID,3) = LEFT(saoh.ACTION_OFFICER_RID,3)
            /Standard snapshot period new join for MonthlyITS and yearlyTIS/
    INNER JOIN (select
                    SNAPSHOT_PERIOD_START_DATETIME,
                    SNAPSHOT_PERIOD_END_DATETIME,
                from Stage_Snapshot_Period_New
                where 
                    SNAPSHOT_PERIOD_IS_FINALISED_INDICATOR = 'No'
                    and SNAPSHOT_PERIOD_TYPE_NAME in ('MonthlyITS','YearlyITS')
                ) sp
        ON change_date < sp.SNAPSHOT_PERIOD_END_DATETIME 

【讨论】:

感谢您的意见,我知道在没有数据集的情况下进行测试非常困难,但正如我之前所说,它对发布过于敏感。您的假设是正确的,即并不总是有一名先前的案件官员。我正在考虑使用 with 语句而不是双连接。你对此有什么想法吗? 我的意思是没有数据库 - 我什至无法对其进行语法检查...我已经注意到有一些杂散的逗号,并且 cmets 已经删除了它们的 *。至于公用表表达式(CTE)(我认为这就是您的意思:“WITH”,只需尝试一下即可。我的脚本实际上只是您可以尝试的其他几件事。 鉴于这是一个性能问题,我们无论如何都需要大量数据......但是对于较小数据集的问题,您仍然可以捏造数据,我们不需要实际数据只是一些东西那是有代表性的。 @GB 好的,是的,正如我所说,有几百万行,因此很难在小数据集上判断性能。但对于未来的问题,我会记住这一点。关于 With 声明,我想我已经差不多完成了。如果我得到满意的结果,我会发布它。 cool thx,你能不能也试一试我的表现,然后发布它......我只是好奇:-)【参考方案2】:

在这种情况下,您可以将“外连接中的内连接”转换为多个左连接,尽管您的 where 子句将需要处理子查询中的内连接处理的情况,例如:

... query same up to the left join ...
            /*This should be inner joining to App_Estate_Statues again to get the previous UserID*/
            LEFT OUTER JOIN [dbo].[STAGE_STAF_ACTION_OFFICERS] saoch
               ON saoh.DATE_TO = saoch.DATE_FROM
                 AND saoch.POSITION = aes.TEAM_CODE
            LEFT OUTER JOIN Stage_App_Estate_Statuses a ON
                 a.TEAM_CODE = saoch.POSITION
                 AND a.ESTATE_RID = aes.ESTATE_RID
    WHERE (saoch.ACTION_OFFICER_RID IS NULL)
       OR (saoch.ACTION_OFFICER_RID IS NOT NULL AND a.ESTATE_STATUS_RID IS NOT NULL)

我简化了表格以删除不相关的位并添加了一些虚拟数据;对于我使用的虚拟数据,此查询返回与原始数据相同的结果,但查询计划不同——这意味着您必须对其进行测试以确定其性能是否更好。

这里是the SQLFiddle I used。

【讨论】:

【参考方案3】:

从上面的答案中获得灵感后,我的最终工作代码。

WITH curr AS
    (SELECT est.ESTATE_RID
        ,est.TEAM_CODE
        ,staff.POSITION
        ,ISNULL(staff.USER_ID, su.System_User_ID) as USERID
        ,est.CHANGE_DATE
        ,ROW_NUMBER() OVER (ORDER BY est.ESTATE_RID DESC, est.CHANGE_DATE ASC) AS RowNum
        FROM Stage_App_Estate_Statuses est
            LEFT OUTER JOIN Stage_STAF_ACTION_OFFICERS staff ON
            est.TEAM_CODE = staff.POSITION and
            est.CHANGE_DATE BETWEEN staff.DATE_FROM AND staff.DATE_TO
            LEFT OUTER JOIN Stage_System_User_Mapping su on
            est.TEAM_CODE = su.System_User_ID
        WHERE CHANGE_TYPE LIKE '%T%'
    )
SELECT  curr.ESTATE_RID, 
        curr.TEAM_CODE,
        ISNULL(curr.USERID,curr.TEAM_CODE) AS Current_UserID,
        prev.USERID AS Previous_UserID,
        curr.CHANGE_DATE as Date_From,
        helper.CHANGE_DATE as Date_To
FROM curr
LEFT OUTER JOIN curr prev ON    curr.RowNum = (prev.RowNum + 1) and
                                curr.ESTATE_RID = prev.ESTATE_RID                           
LEFT OUTER JOIN curr helper ON curr.RowNum = (helper.RowNum - 1) and
                                curr.ESTATE_RID = helper.ESTATE_RID

ORDER BY ESTATE_RID ASC, curr.CHANGE_DATE ASC               

【讨论】:

以上是关于外部联接中的内部联接导致性能不足,有啥不同的方法?的主要内容,如果未能解决你的问题,请参考以下文章

外部联接中的内部异常

详解T-SQL的联接机制

在多列上组合 sql 内部和外部联接

内部联接与内部联接(SELECT . FROM)

有啥方法可以避免一个查询中的多个联接

外部连接的位置vs ON