Как оптимизировать запрос?

У меня есть запрос T-SQL, который выполняется в течение 6 минут.

В одной таблице есть несколько подзапросов. Я думаю, что это причина проблемы. Но у меня нет идей по оптимизации.

SELECT  dateheure, bac, presence, reponse
    ,(select top 1 dateheure from LogEvents where bac = t1.bac and dateheure < t1.dateheure order by id desc) as dateheure_precedente
    ,(select top 1 presence from LogEvents where bac = t1.bac and dateheure < t1.dateheure order by id desc) as presence_precedente 
    ,(select top 1 reponse from LogEvents where bac = t1.bac and dateheure < t1.dateheure order by id desc) as reponse_precedente   
    ,(select top 1 dateheure from LogEvents where bac = t1.bac and dateheure > t1.dateheure order by id asc) as dateheure_suivante
    ,(select top 1 presence from LogEvents where bac = t1.bac and dateheure > t1.dateheure order by id asc) as presence_suivante
    ,(select top 1 reponse from LogEvents where bac = t1.bac and dateheure > t1.dateheure order by id asc) as reponse_suivante
FROM [alpla_log].[dbo].[LogEvents] t1 
WHERE 
    t1.presence = 7845
    AND dateheure BETWEEN '11/07/2024 00:00:00' AND '11/07/2024 23:59:59.997' 
ORDER BY id DESC

Единственная используемая таблица — «LogEvents».

CREATE TABLE LogEvents (
    id int IDENTITY NOT NULL PRIMARY KEY,
    dateheure datetime NULL,
    type varchar(50) NULL,
    msg varchar(MAX) NULL,
    presence int NULL,
    destination int NULL,
    status_prt int NULL,
    reponse int NULL,
    bac int NULL
);

План выполнения вы можете найти здесь.

Я попытался добавить индекс в таблицу, но это не имеет значения. CREATE NONCLUSTERED INDEX ON dbo.Logevents (bac, dateheure) INCLUDE (reponse)

Других индексов нет.

Таблица представляет собой журнал событий на автоматическом складе. Это сохраняет положение ящиков в разных местах. Я хочу найти предыдущую и следующую позицию каждого ящика по сравнению с конкретным местом.

РЕДАКТИРОВАТЬ :

Благодаря использованию функций LEAD и LAG время выполнения запроса теперь составляет около 500 мс.

Для вопросов о производительности запросов нам нужны как минимум: задействованные таблицы и индексы. Пожалуйста, поделитесь планом запроса через brentozar.com/pastetheplan. Иначе это не несет никакой ответственности.

Charlieface 11.07.2024 14:27

Почему бы вам не «выбрать топ-1, дату, присутствие, ответ и т. д. из LogEvents ГДЕ» один раз? Каждый из этих избранных делает одно и то же. А вычисление даты по частям в значительной степени гарантирует сканирование индекса. Вы хотите полностью избежать подобных вещей. Однако в корне вам необходимо получить план выполнения и выяснить, как SQL-сервер обрабатывает запрос за вас. Это приведет вас к пониманию того, что должно измениться.

Grant Fritchey 11.07.2024 14:28

Комбинация: LEADLAG оконных функций, исправления WHERE для использования диапазона и удаления ORDER BY и индекса (bac, dateheure) INCLUDE (presence, reponse), вероятно, исправит это за вас dbfiddle.uk/LJcTmZ7k но опять же трудно сказать без дополнительной информации.

Charlieface 11.07.2024 14:31

Прочтите о OUTER APPLY, нет необходимости повторять этот коррелированный подзапрос TOP 1 несколько раз.

siggemannen 11.07.2024 14:41

ТАКЖЕ AND DATEFROMPARTS(DATEPART(year,dateheure),DATEPART(month,datehe‌​ure),DATEPART(day,da‌​teheure)) = '07.11.2024' , почему бы просто не сделать это: year,dateheure between '20240711' and '20240711 23:59:59.997'

siggemannen 11.07.2024 14:42
>= ... < логика была бы лучше, @siggemannen : dateheure >= '20240711' AND dateheure < '20240712' Это полностью инклюзивно.
Thom A 11.07.2024 15:03

@ThomA да, хороший звонок, я так привык к тому, что часть даты представляет собой неизменяемую строку, но твой вариант определенно лучше

siggemannen 11.07.2024 15:11

Вы можете увидеть разницу с моими предложениями dbfiddle.uk/Ex0uXTTI одно сканирование индекса, без поиска ключей и сортировки. Индекс, который вы пробовали, не содержал presence в INCLUDE.

Charlieface 11.07.2024 23:06
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
8
80
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Ваш запрос может быть значительно улучшен.

  • Хотя вы можете заменить шесть подзапросов двумя APPLY, гораздо лучшим решением будет использование оконных функций LEAD и LAG.
    • Обратите внимание, что LEAD и LAG не дадут результата для последней/первой строки раздела. Возможно, вам придется поместить все это в производную таблицу с более широким диапазоном дат, а затем отфильтровать ее обратно.
  • Предложение WHERE не является «поддерживаемым» (не может использовать индексы). Вместо этого используйте диапазон дат, желательно полуоткрытый интервал >= AND <
  • Удалите ORDER BY, если в этом нет абсолютной необходимости, так как это другой порядок, чем WHERE и PARTITION BY в LEAD.
SELECT
  dateheure,
  bac,
  presence,
  reponse,
  LAG(t1.dateheure) OVER (PARTITION BY t1.bac ORDER BY t1.dateheure, t1.id) as dateheure_precedente,
  LAG(t1.presence ) OVER (PARTITION BY t1.bac ORDER BY t1.dateheure, t1.id) as presence_precedente,
  LAG(t1.reponse  ) OVER (PARTITION BY t1.bac ORDER BY t1.dateheure, t1.id) as reponse_precedente,
  LEAD(t1.dateheure) OVER (PARTITION BY t1.bac ORDER BY t1.dateheure, t1.id) as dateheure_suivante,
  LEAD(t1.presence ) OVER (PARTITION BY t1.bac ORDER BY t1.dateheure, t1.id) as presence_suivante,
  LEAD(t1.reponse  ) OVER (PARTITION BY t1.bac ORDER BY t1.dateheure, t1.id) as reponse_suivante
FROM LogEvents t1 
WHERE 
    t1.presence = 7845
    AND dateheure >= '20240711'
    AND dateheure < '20240712';

Наконец, добавьте правильный индекс для поддержки этого запроса. Индекс, который у вас был, не учитывал presence.

CREATE INDEX IX ON LogEvents (presence, bac, dateheure, id) INCLUDE (reponse);

Из этой скрипты вы можете видеть, что теперь это приводит к одному сканированию базовой таблицы, без соединений, без поиска ключей и сортировок.

Хороший ответ. Я собирался опубликовать решение на основе OUTER APPLY, но оно должно было иметь гораздо лучшую производительность. Единственным недостатком является то, что первая/последняя строка для каждого bac может не отображать предыдущие/следующие значения, если они выходят за пределы отфильтрованного диапазона дат. Кроме того, если dateheure может иметь повторяющиеся значения, может потребоваться использование ORDER BY t1.dateheure, T1.id.

T N 12.07.2024 03:25

Справедливые замечания, спасибо

Charlieface 12.07.2024 03:44

Одна из самых основных ошибок заключается в том, что вы запрашиваете одну и ту же большую таблицу несколько раз. Вместо этого вам следует поместить основной результат в таблицу #temp, а затем выполнить запрос к таблице Temp. Это даст вам прирост производительности. После этого вы можете применить функцию Window или Outer Apply во втором шаге запроса.

Есть 2 шага, но есть прирост производительности

SELECT  id,dateheure, bac, presence, reponse
    --,(select top 1 dateheure from LogEvents where bac = t1.bac and dateheure < t1.dateheure order by id desc) as dateheure_precedente
    --,(select top 1 presence from LogEvents where bac = t1.bac and dateheure < t1.dateheure order by id desc) as presence_precedente 
    --,(select top 1 reponse from LogEvents where bac = t1.bac and dateheure < t1.dateheure order by id desc) as reponse_precedente   
   -- ,(select top 1 dateheure from LogEvents where bac = t1.bac and dateheure > t1.dateheure order by id asc) as dateheure_suivante
   -- ,(select top 1 presence from LogEvents where bac = t1.bac and dateheure > t1.dateheure order by id asc) as presence_suivante
   -- ,(select top 1 reponse from LogEvents where bac = t1.bac and dateheure > t1.dateheure order by id asc) as reponse_suivante
into #temp
FROM [alpla_log].[dbo].[LogEvents] t1 
WHERE 
    t1.presence = 7845
    AND dateheure BETWEEN '11/07/2024 00:00:00' AND '11/07/2024 23:59:59.997' 
--ORDER BY id DESC

затем шаг 2нс,

Select * 
--write other query here
from #temp
order by id desc

Другие вопросы по теме