将 Postgres 的 generate_series 函数与 ObjectionJS/KnexJS 一起使用

Using Postgres's generate_series Function with ObjectionJS/KnexJS

我正在尝试执行高级分析查询来驱动 Web 应用程序。我在 TimescaleDB. It all is working well for typical relational queries. However, I cannot figure out how to perform this aggregation query that involves joining with an anonymous table that is generated from Postgres's generate_series. I have had to resort to writing the raw SQL, rather than the Objection/Knex query builder. I am using a few of Postgres's built in functions, along with time_bucket from Timescale. time_bucket essentially creates a roll up of the data based on the interval specified in the argument. Check this link out for more information about what I'm attempt to do Gap Filling.

中使用 Hapi、Objection、Knex 和 Postgres

这是查询,它使用对象模型上的原始方法工作。我相信像这样进行字符串插值会导致潜在的 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();

我已经用 Objection/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" = ?'

感谢任何帮助,因为我没有使用 Objection 来执行此操作,也找不到任何相关文档。

2018 年 11 月 15 日更新

我得到它来执行带有 Objection 的查询。但是,结果我得到一个空数组。与我在上面制作的原始 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

Timescale 发布了一项名为 Time Bucket Gapfill 的新功能。它使这变得容易得多,因为您不再需要使用 generate_series 进行左连接来填充间隙。

我已经包含了一个示例,说明如何使用名为 Errors 的 ObjectionJS 模型来实现它。 time_bucket_gapfill 函数的输入是桶大小、时间戳列名称、startTS 和 endTS。 bucket size 变量应该是一个字符串 "" (不是单引号)对应于 bucket 大小(例如:"10 seconds", "30 minutes", "1 hour", "1 day"). startTSstopTS 应该是 ISO 日期字符串。第二个 select 语句需要 COALESCE 以便如果在桶中没有数据的情况下生成一个桶,它将输出 0。需要 group by 才能根据您在 select 语句中提供的聚合 SQL 函数正确汇总数据。

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');