Достижение поведения, подобного оконной функции, с использованием пользовательской функции PostgreSQL? - PullRequest
0 голосов
/ 01 марта 2020

Допустим, что для таблицы observations_tbl с атрибутами date (день) и value я хочу создать новый атрибут prev_day_value, чтобы получить следующую таблицу:

|---------------------|-------|----------------|
|     date            | value | prev_day_value |
|---------------------|-------|----------------|
| 01.01.2015 00:00:00 | 5     | 0              |
| 02.01.2015 00:00:00 | 4     | 5              |
| 03.01.2015 00:00:00 | 3     | 4              |
| 04.01.2015 00:00:00 | 2     | 3              |
|---------------------|-------|----------------|

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

Рассмотрим этот шаблон запроса:

SELECT *, lag(value,1) AS prev_day_value -- or lag(record,1) or lag(date,value,1) or lag(date,1) or lag(observations_tbl,1), etc.
FROM observations_tbl

Я использую функцию lag с параметром 1, чтобы найти значение, которое предшествует текущей строке на 1 - расстояние 1 строки. Мне все равно, какие другие параметры может иметь функция lag (имя таблицы, другие атрибуты) - как может выглядеть функция lag для достижения такой функциональности? Функция может быть любого языка, SQL, PL/pgSQL и даже C с использованием PostgreSQL API / backend.

Я понимаю, что один ответ может заключаться в WINDOW запросе внутри lag пользовательская функция. Но я думаю, что это будет довольно дорогостоящей операцией, если мне придется сканировать всю таблицу дважды (один раз внутри функции lag и один раз снаружи). Я думал, что, возможно, каждая запись PostgreSQL будет иметь указатель на свою предыдущую запись, которая доступна напрямую? Или что я могу каким-то образом открыть курсор на этом указанном c номере строки / строки без необходимости сканирования всей таблицы? Или то, что я спрашиваю, невозможно?

Ответы [ 2 ]

2 голосов
/ 02 марта 2020

Ваш запрос невозможно решить с помощью реляционных инструментов (оконные функции не являются реляционным расширением в SQL). На языке C вы можете написать собственную альтернативу функции lag. Вы можете выполнять ту же работу на языке PL8 (Javascript). К сожалению, API для оконных функций не существует для PL / pg SQL. Вы не можете написать простую функцию PL / pg SQL, которая имеет доступ к строке, отличной от обрабатываемой.

Одной из возможных альтернатив (но с некоторым риском для производительности) является запись табличной функции. Там у вас есть контроль над всем обработанным набором данных, и вы можете выполнить эту операцию просто.

CREATE OR REPLACE FUNCTION report()
RETURNS TABLE(d date, v int, prev_v int) $$
DECLARE r RECORD;
BEGIN
  prev_v := 0;
  FOR r IN SELECT date, value FROM observations_tbl t ORDER BY 1
  LOOP
    d := r.date; v := r.value;
    RETURN NEXT;
    prev_v := v;
  END LOOP;
END;
$$ LANGUAGE plpgsql;

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

1 голос
/ 03 марта 2020

Что Павел отправил , только с меньшим количеством заданий. Должно быть быстрее:

CREATE OR REPLACE FUNCTION report()
  RETURNS TABLE(d date, v int, prev_v int) AS
$func$
BEGIN
   prev_v := 0;
   FOR d, v IN
      SELECT date, value FROM observations_tbl ORDER BY 1
   LOOP
      RETURN NEXT;
      prev_v := v;
   END LOOP;
END
$func$  LANGUAGE plpgsql;

Общая идея может окупиться, если она на самом деле заменяет несколько сканирований таблицы одним. Как здесь:

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...