Использование функции generate_series в Postgres с ObjectionJS / KnexJS - PullRequest
0 голосов
/ 15 ноября 2018

Я пытаюсь выполнять расширенные аналитические запросы для управления веб-приложением.Я использую Hapi, Objection, Knex и Postgres с TimescaleDB .Все это хорошо работает для типичных реляционных запросов.Однако я не могу понять, как выполнить этот запрос агрегации, который включает соединение с анонимной таблицей, сгенерированной из Postgres generate_series.Мне пришлось прибегнуть к написанию необработанного SQL, а не к построителю запросов Objection / Knex.Я использую несколько встроенных функций Postgres, а также time_bucket из Timescale.time_bucket по существу создает свертку данных на основе интервала, указанного в аргументе.Проверьте эту ссылку для получения дополнительной информации о том, что я пытаюсь сделать Заполнение пробела .

Вот запрос, который работает с использованием необработанного метода в модели возражений.Я полагаю, что выполнение такой интерполяции строк приведет к потенциальной инъекции SQL.Однако я надеялся преобразовать это в методы построителя запросов, которые использует Objection / Knex, так что это больше JavaScript, чем SQL, что решило бы проблему внедрения SQL.

let errorHistorgram = await Errors
    .raw(`SELECT period AS daily, coalesce(count,0) AS count
    FROM generate_series(date '${startTS}', date '${today}', interval '1d') AS period
      LEFT JOIN (
        SELECT time_bucket('1d',timestamp)::date AS date, count(timestamp)
        FROM my_error_table
        WHERE severity = 'HIGH'
          AND timestamp >= '${startTS}' AND timestamp < '${today}'
          AND device_id = ${deviceId}
        GROUP BY date
      ) t ON t.date = period;`)
      .debug();

Я сделал несколько попытокна это с возражением / Knex.Это была моя самая успешная попытка создать этот запрос.Однако я считаю, что предложение where находится не в правильном месте.

let errorHistorgram = await Errors
    .query()
    .select(raw(`time_bucket('1 day', timestamp) AS daily, count(timestamp)`))
    .where('device_id', deviceId)
    .andWhere('timestamp', '>', startTS)
    .andWhere('severity', 'HIGH')
    .leftJoin(`generate_series(date ${startTS}, date ${today}, interval 1d) AS series`, 'series.date', 'my_error_table.timestamp')
    .debug();

С помощью .debug() я могу увидеть вывод запроса, который опубликован ниже.

select time_bucket('1 day', timestamp) AS daily, count(timestamp)
from my_error_table
left join "generate_series(date 2018-11-08T15:35:33"."050Z, date 2018-11-15T15:35:33"."133Z, interval 1d)" as "series"
  on "series"."date" = my_error_table."timestamp"
where "device_id" = ? and "timestamp" > ? and "severity" = ?'

Любая помощь приветствуется, поскольку я не использовал для этого возражения и не могу найти какую-либо документацию по ней.

ОБНОВЛЕНИЕ 11/15/2018

Я получил еевыполнить запрос с возражением.Тем не менее, я получаю пустой массив в результате.В отличие от необработанного SQL-запроса, который я создал выше (который дает ожидаемые результаты), я просто получаю пустой массив в качестве вывода для построителя запросов.Любая идея относительно того, что я делаю неправильно.Я попытался перевернуть соединение на правильное, но безуспешно.

 let errorHistorgram = await Errors
  .query()
  .select(raw(`time_bucket('1 day', timestamp) AS daily, count(timestamp)`))
  .where('device_id', deviceId)
  .andWhere('timestamp', '>', startTS)
  .andWhere('severity', 'HIGH')
  .groupBy('timestamp')
  .rightJoin(raw(`generate_series(date '${startTS}', date '${today}', interval '1d') AS series`), 'series.date', 'my_error_table.timestamp')
  .debug();

Прилагается вывод SQL отладки.

select time_bucket('1 day', timestamp) AS daily, count(timestamp) 
from my_errors_table
right join generate_series(date '2018-11-08', date '2018-11-15', interval '1d') AS series
on series = my_errors_table.timestamp
where device_id = ? and timestamp > ? and severity = ? 
group by timestamp

1 Ответ

0 голосов
/ 20 июня 2019

Timescale выпустила новую функцию под названием Time Bucket Gapfill .Это сделало это намного проще, потому что вам больше не нужно делать левое соединение с generate_series для заполнения пробела.

Я включил пример того, как реализовать это с моделью ObjectionJS под названием Errors.Входными данными для функции time_bucket_gapfill являются размер сегмента, имя столбца метки времени, startTS и endTS.Переменная размера корзины должна быть строкой с "" (не одинарными кавычками), которая соответствует размеру корзины (например: "10 seconds", "30 minutes", "1 hour", "1 day").startTS и stopTS должны быть строками даты ISO.Второй оператор выбора требует COALESCE, поэтому он будет выводить 0, если генерируется сегмент, в котором нет данных, содержащихся в сегменте.group by требуется для правильного суммирования данных в разбивке по категориям на основе агрегатной функции SQL, которую вы предоставляете в операторе select.

import { raw } from 'objection';

const errors = await Errors
  .query()
  .select(
    raw("time_bucket_gapfill(?, timestamp, ?, ?) AS bucket", [bucketWidth, startTS, endTS]),
    raw('COALESCE(count(timestamp), 0) AS count'),
  ).where('device_id', deviceId)
  .andWhere('timestamp', '>=', startTS)
  .andWhere('timestamp', '<=', endTS)
  .groupBy('bucket')
  .orderBy('bucket');
...