Внутреннее соединение во внешнем соединении, ведущее к снижению производительности, что представляет собой другой подход?

У меня есть представление в одной из моих баз данных, которое извлекает предыдущих и текущих офицеров (например, человека) из нескольких таблиц и представлений. Проблема в том, что эти записи связаны только по дате окончания (saoh.Date_TO), совпадающей с датой начала работы другого оперативного сотрудника (saoh.Date_FROM).

Чтобы создать соединение между этими записями, я в настоящее время делаю внешнее соединение с внутренним соединением внутри него. (Это можно увидеть в скрипте ниже). Проблема в том, что представление имеет ~ 3 миллиона записей. Это означает, что запрос этого представления занимает очень много времени (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]

person Community    schedule 14.01.2015    source источник
comment
Схема? План выполнения?   -  person Mitch Wheat    schedule 15.01.2015
comment
LEFT(aes.ESTATE_RID,3) = LEFT(saoh.ACTION_OFFICER_RID,3) проблематично.   -  person Felix Pamittan    schedule 15.01.2015
comment
@MitchWheat Хороший выбор, схема - это DBO. Это левое утверждение является ограничением исходных таблиц, в соответствии с которым повторно используются определенные идентификаторы (ужасная исходная система). и единственное изменение - это первые 3 символа обоих этих утверждений. Однако качество данных достаточно высокое, поскольку сравнение надежно для всех строк обеих таблиц.   -  person    schedule 15.01.2015
comment
@MichaelBetterton, опубликуйте определения таблиц.   -  person Felix Pamittan    schedule 15.01.2015
comment
Зачем вам производная таблица? Я забыл синтаксис, но я его видел.   -  person paparazzo    schedule 15.01.2015
comment
@wewestthemenace Я опубликовал определения таблиц, к сожалению, я не могу опубликовать примеры данных из-за чувствительности данных.   -  person    schedule 15.01.2015
comment
@Blam, как я уже сказал в вопросе, мне нужно найти текущего и предыдущего куратора для каждого поместья. т. е. 1 ряд на поместье на одного ответственного за дело   -  person    schedule 15.01.2015
comment
Итак, это не имеет ничего общего с необходимостью делать это с производной таблицей.   -  person paparazzo    schedule 15.01.2015
comment
Можете ли вы проверить, что является узким местом производительности? Можете ли вы удалить LEFT OUTER JOIN и saoch.USER_ID из SELECT, чтобы увидеть, значительно ли изменится производительность? Если это так, то мы можем рассмотреть возможность улучшения этой части.   -  person Vladimir Baranov    schedule 15.01.2015
comment
@VladimirBaranov Создав этот запрос, я могу подтвердить, что левое внешнее соединение с внутренним соединением внутри него само по себе вызывает проблему с производительностью. Я почти нашел решение, используя оператор with, а затем выполнив для него внешнее левое соединение. Запрос не является правильным с точки зрения данных, но время запроса сократилось с ~ 3 минут до ~ 15 секунд.   -  person    schedule 16.01.2015
comment
@MichaelBetterton, насколько велики Stage_App_Estate_Statuses и Stage_STAF_ACTION_OFFICERS? какие индексы у них? Почему в первом INNER JOIN вы присоединяетесь к ним через aes.TEAM_CODE = saoh.POSITION AND LEFT(aes.ESTATE_RID,3) = LEFT(saoh.ACTION_OFFICER_RID,3), а потом присоединяетесь через a.TEAM_CODE = staf.POSITION и saoch.ESTATE_RID = aes.ESTATE_RID. Второй раз вы не используете ACTION_OFFICER_RID, что странно. Было бы правильно соединить эти две таблицы один раз и использовать результаты в двух местах позже (используя другое представление или CTE)?   -  person Vladimir Baranov    schedule 16.01.2015


Ответы (3)


Трудно не иметь ничего для тестирования, поэтому просто рассматривайте это как отправную точку.


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

person G B    schedule 15.01.2015
comment
Спасибо за ваш вклад, я понимаю, что это невероятно сложно протестировать без набора данных, но, как я уже говорил, это слишком важно для публикации. Вы правы в своем предположении, что не всегда есть предшествующий оперативный сотрудник. Я рассматривал возможность использования оператора with вместо двойного соединения. У вас есть какие-нибудь мысли по этому поводу? - person ; 15.01.2015
comment
Я имел в виду отсутствие базы данных - я даже не могу проверить ее синтаксис ... я уже заметил, что есть несколько случайных запятых, а в комментариях * удалены. что касается общего табличного выражения (CTE) (я думаю, это то, что вы имеете в виду в отношении: WITH, это просто вопрос попытки. Мой сценарий - это всего лишь пара других вещей, которые вы могли бы попробовать. - person G B; 15.01.2015
comment
Учитывая, что это связано с производительностью, нам в любом случае понадобилось бы МНОГО данных... но для проблем с меньшим набором данных вы все равно можете подделать данные, нам не нужны фактические данные, а только что-то репрезентативное. - person G B; 15.01.2015
comment
@GB Хорошо, да, как я уже сказал, существует довольно много миллионов строк, и поэтому было бы довольно сложно сказать о производительности на небольшом наборе данных. Но для будущих вопросов я запомню это. Что касается утверждения With, я думаю, что почти дошел до него. Я опубликую это, если я приду к результату, который удовлетворяет. - person ; 15.01.2015
comment
круто, спасибо, не могли бы вы также запустить мой, посмотреть, на что похожа производительность, и опубликовать это ... Мне просто любопытно :-) - person G B; 15.01.2015
comment
Мне действительно удалось решить проблему, используя ваш код в качестве основы. Внутренние соединения нужно было заменить левыми внешними соединениями, и я использовал оператор where. Для справки, ваш запрос был немного быстрее, чем исходный, однако мой сократился примерно до 30 секунд. Я выберу ваш ответ как правильный, так как это не вопрос типа вопросов и ответов, но я опубликую свой ниже для справки. - person ; 19.01.2015
comment
Также я удалил ссылку snapshot_period в своем ответе, что на самом деле означает, что у меня был больший набор результатов. Это означает, что после включения ссылки snapshot_period мой код сократился до очень быстрого времени выполнения. - person ; 19.01.2015

В этом случае вы можете превратить «внутреннее соединение во внешнее соединение» в несколько левых соединений, хотя ваше предложение 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)

Я упростил таблицы, удалив ненужные биты, и добавил несколько фиктивных данных; с фиктивными данными, которые я использовал, этот запрос возвращает те же результаты, что и исходный, но другой план запроса - это означает, что вам придется протестировать его, чтобы определить, работает ли он лучше или нет.

Вот SQLFiddle, который я использовал.

person lheria    schedule 15.01.2015

Мой окончательный рабочий код после вдохновения из ответа выше.

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               
person Community    schedule 18.01.2015