Как я могу эффективно разбить на страницы результаты сложного SQL запроса? - PullRequest
3 голосов
/ 24 января 2020

У меня довольно сложный запрос SQL, который сначала извлекает некоторые данные в CTE, а затем выполняет несколько самосоединений в CTE для вычисления значения. Вот сокращенный пример с некоторыми упрощениями нашего приложения:

WITH subset AS (
  SELECT time, value, device_id FROM raw_data
  WHERE device_id IN (1, 2, 3)
  AND time BETWEEN '2019-01-01 00:00:00'::timestamp AND '2019-01-15 00:00:00'::timestamp
)

SELECT
  time,
  (("device_1".value + "device_2".value) / "device_3".value) as value
FROM 

(
  SELECT * FROM subset 
  WHERE device_id = 1
) "device_1"

INNER JOIN
(
  SELECT * FROM subset 
  WHERE device_id = 2
) "device_2"
ON "device_1".time = "device_2".time

INNER JOIN
(
  SELECT * FROM subset 
  WHERE device_id = 3
) "device_3"
ON "device_3".time = "device_2".time

Запрос генерируется автоматически и может масштабироваться до сложных вычислений по значениям потенциально десятков устройств. По соображениям производительности мы хотели бы разбить на страницы результаты этого запроса, поскольку используемый временной диапазон может быть большим. Ключевым ограничением является то, что данные могут иметь промежутки во времени, но мы хотим возвращать постоянное количество строк на странице.

Мы рассмотрели использование LIMIT per_page OFFSET start в конце запроса, который будет стандартным подход, но это не дает нам никакой скорости и запрос выполняет то же самое. Это имеет смысл, потому что в этом случае LIMIT / OFFSET выполняется после того, как все данные были извлечены, объединены и вычислены, и он просто возвращает фрагмент данных, который уже вычислен. Это существенно не уменьшает скорость выполнения запроса.

Мы рассмотрели разбиение на страницы данных, извлеченных в CTE, то есть вычисление того, какой временной диапазон соответствует интересующей странице, и затем использование этого временного диапазона в BETWEEN. пункт CTE. Это сработало бы, но проблема в том, что мы не можем надежно вычислить этот временной интервал, поскольку некоторые переменные могут содержать пробелы. Поэтому, если мы вычисляем 100 строк, чтобы они были окном 2 дня, и выбираем 2 дня, есть вероятность, что мы получим менее 100 строк, если device_2 не записал данные в какой-то момент в этом окне. Для вычисления эти точки данных будут отброшены во ВНУТРЕННИХ СОЕДИНЕНИЯХ.

Вопрос в том, существует ли эффективный способ разбить на страницы этот запрос или реструктурировать его, чтобы обеспечить быструю разбивку на страницы, учитывая эти ограничения? Например, есть ли способ указать планировщику запросов «присоединиться, пока вы не сопоставите 100 результатов, соответствующих условиям объединения, и остановитесь на этом». Мы запускаем это на PostgreSQL, если это имеет значение.

Ответы [ 2 ]

2 голосов
/ 24 января 2020

Предполагая, что ваш сценарий использования допускает отсутствие абсолютных последних данных все время, вы можете рассмотреть возможность создания материализованного представления:

WITH subset AS ( ... )

CREATE MATERIALIZED VIEW yourView AS SELECT ...

Разбиение на материализованное представление с использованием LIMIT и OFFSET должно быть быстрее, чем выполнять полный запрос с нуля каждый раз. Недостатком здесь будет то, что вы будете возвращать данные из представления, которые должны будут обновляться с некоторой частотой, отвечающей вашим требованиям.

В качестве альтернативы / в дополнение к материализованному представлению, вы можете рассмотреть настройку запрос с использованием индексов. Например, индекс, который ускорит запрос subset CTE, может быть:

CREATE INDEX idx1 ON raw_data (time, device_id, value);

Или, может быть:

CREATE INDEX idx2 ON raw_data (device_id, time, value);
1 голос
/ 21 февраля 2020

1) Создайте составной индекс со следующим порядком: device_id и time des c.

2) Попробуйте сгенерировать запрос следующим образом

select device_1.time,
 (("device_1".value + "device_2".value) / "device_3".value) as value 
from raw_data as device_1 ,raw_data as device_2 ,raw_data as device_3 
where device_1.devise_id = 1 
and device_2.devise_id = 2 
and device_3.devise_id = 3 
and device_1.time BETWEEN '2019-01-01 00:00:00'::timestamp AND '2019-01-15 00:00:00'::timestamp 
and device_2.time BETWEEN '2019-01-01 00:00:00'::timestamp AND '2019-01-15 00:00:00'::timestamp 
and device_3.time BETWEEN '2019-01-01 00:00:00'::timestamp AND '2019-01-15 00:00:00'::timestamp 
and device_1.time = device_2.time 
and device_2.time = device_3.time
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...