У меня есть большое представление, которое я использую из приложения. Я думаю, что сузил свою проблему с производительностью, но я не уверен, как ее исправить. Упрощенная версия представления выглядит так:
SELECT ISNULL(SEId + '-' + PEId, '0-0') AS Id,
*,
DATEADD(minute, Duration, EventTime) AS EventEndTime
FROM (
SELECT se.SEId, pe.PEId,
COALESCE(pe.StaffName, se.StaffName) AS StaffName, -- << Problem!
COALESCE(pe.EventTime, se.EventTime) AS EventTime,
COALESCE(pe.EventType, se.EventType) AS EventType,
COALESCE(pe.Duration, se.Duration) AS Duration,
COALESCE(pe.Data, se.Data) AS Data,
COALESCE(pe.Field, se.Field) AS Field,
pe.ThisThing, se.OtherThing
FROM PE pe FULL OUTER JOIN SE se
ON pe.StaffName = se.StaffName
AND pe.Duration = se.Duration
AND pe.EventTime = se.EventTime
WHERE NOT(pe.ThisThing = 1 AND se.OtherThing = 0)
) Z
Это, вероятно, не оправдывает всей причины структуры запроса, но, возможно, дает вам представление - это представление объединяет две очень плохо спроектированные таблицы, которые я не контролирую, и пытается синтезировать из них некоторую информацию.
Итак, так как это представление используется из приложения, при попытке оптимизации я обертываю его в другой SELECT, например так:
SELECT * FROM (
-- … above code …
) Q
WHERE StaffName = 'SMITH, JOHN Q'
потому что приложение ищет конкретных сотрудников в результате.
Проблема, кажется, в COALESCE(pe.StaffName, se.StaffName) AS StaffName
разделе, и что я выбираю из представления StaffName
. Если я изменю это на pe.StaffName AS StaffName
или se.StaffName AS StaffName
, проблемы с производительностью исчезнут (но см. Обновление 2 ниже) . Но это не сработает, потому что одна или другая сторона FULL OUTER JOIN
могут отсутствовать, поэтому одно или другое поле может иметь значение NULL.
Могу ли я выполнить рефакторинг, заменив его COALESCE(…)
чем-то еще, что будет переписано в подзапрос?
Другие заметки:
- Я уже добавил несколько индексов, чтобы исправить проблемы с производительностью в остальной части запроса - без
COALESCE
него это очень быстро. - К моему удивлению, просмотр плана выполнения не поднимает никаких флагов, даже если
WHERE
включены подзапрос и оператор переноса . Моя общая стоимость подзапроса в анализаторе равна0.0065736
. Хммм. Выполнение занимает четыре секунды. - Изменение приложения для запроса по-другому
(например, возвратможет работать, но в крайнем случае - я действительно надеюсь, что смогу оптимизировать представление, не прибегая к прикосновению к приложению.pe.StaffName AS PEStaffName, se.StaffName AS SEStaffName
и выполнениеWHERE PEStaffName = 'X' OR SEStaffName = 'X'
) - Хранимая процедура, вероятно, имела бы для этого больше смысла, но приложение построено с использованием Entity Framework, и я не мог понять, как заставить его хорошо работать с SP, который возвращает тип таблицы (еще одна тема).
Индексы
Индексы, которые я добавил до сих пор, выглядят примерно так:
CREATE NONCLUSTERED INDEX [IX_PE_EventTime]
ON [dbo].[PE] ([EventTime])
INCLUDE ([StaffName],[Duration],[EventType],[Data],[Field],[ThisThing])
CREATE NONCLUSTERED INDEX [IX_SE_EventTime]
ON [dbo].[SE] ([EventTime])
INCLUDE ([StaffName],[Duration],[EventType],[Data],[Field],[OtherThing])
Обновить
Хм ... Я попытался смоделировать пораженные изменения выше, и это не помогло. Т.е. до того как ) Z
я добавил AND (pe.StaffName = 'SMITH, JOHN Q' OR se.StaffName = 'SMITH, JOHN Q')
, но производительность такая же. Теперь я действительно не знаю, с чего начать.
Обновление 2
Комментарий @ypercube о необходимости полного объединения заставил меня осознать, что мой синтезированный запрос исключил, вероятно, важный компонент. Хотя да, мне нужно полное объединение, тест, который я провел выше, отбрасывая COALESCE
и проверяя только одну сторону соединения на ненулевое значение, сделал бы другую сторону полного соединения неактуальной , и оптимизатор, вероятно, использовал это факт, чтобы ускорить запрос. Кроме того, я обновил пример, чтобы показать, что StaffName
это на самом деле один из ключей соединения - что, вероятно, имеет большое значение для вопроса. Я также сейчас склоняюсь к его предположению, что разделение этого на трехсторонний союз вместо полного объединения может быть ответом и упростит изобилие тех, COALESCE
что я делаю в любом случае. Пробую сейчас.
KeyField
, оба индекса INCLUDE
в StaffName
поле и некоторых других областях. Я могу опубликовать определения индекса в вопросе. Я работаю над этим на тестовом сервере, поэтому могу добавить любые индексы, которые, по вашему мнению, могут быть полезны для проб!
WHERE pe.ThisThing = 1 AND se.OtherThing = 0
условие, которое отменяет FULL OUTER
соединение и делает запрос эквивалентным внутреннему соединению. Вы уверены, что вам нужно ПОЛНОЕ присоединение?
INNER JOIN
, LEFT JOIN
с WHERE IS NULL
чеком, RIGHT JOIN с IS NULL) , а затем UNION ALL
три части. Таким образом, вам не нужно будет его использовать, COALESCE()
и это может (просто может) помочь оптимизатору разобраться в переписывании.