建议将带有聚合函数的动态 SQL 查询解析为 JSON 组数组

Advice parsing dynamic SQL query with aggregate functions into JSON set of arrays

我需要将 time_bucket (TimescaleDB function derivated from postgres date_trunc) 的输出解析为数组的 JSON 对象。我是第一次这样做,经过大量研究,我得出了这样的结论:

CREATE OR REPLACE FUNCTION timescale.get_chronograph_json_time_bucket(
    _ts_start timestamp without time zone,
    _ts_end timestamp without time zone,
    _y1_name text,
    _y2_name text,
    _tb_name text,
    _time_bucket interval)
    RETURNS json
    LANGUAGE 'plpgsql'
    COST 100
    VOLATILE PARALLEL UNSAFE
AS $BODY$
DECLARE
    chronograph json;
BEGIN
EXECUTE '
    CREATE TEMP TABLE tmp_table ON COMMIT DROP AS
    SELECT
        time_bucket(,timestamp) AS bucket,
        avg('|| quote_ident(_y1_name) ||') AS y1,
        avg('|| quote_ident(_y2_name) ||') AS y2
    FROM timescale.' || quote_ident(_tb_name) ||'
    WHERE timestamp BETWEEN  AND 
    GROUP BY bucket ORDER BY bucket'
USING _ts_start, _ts_end, _time_bucket;
SELECT json_build_object(
    'ts',json_agg(bucket),
    'y1',json_agg(y1),
    'y2',json_agg(y2))
FROM tmp_table
INTO chronograph;
RETURN chronograph;
END
$BODY$;

它return是指定时间戳范围(_ts_start、[=17=)内用户请求变量(_y1_name_y2_name)的所有行]) 与请求的项目 (_tb_name) 请求的分辨率 (_time_bucket)。

它正在做这项工作,但有一些“问题”困扰着我:

  1. 是否有必要创建临时table?如果有两个同时调用函数怎么办,他们不会共享相同的时间 table?
  2. INTO chronograph; RETURN chronograph; 看起来也有点奇怪。没有更直接的方法 return JSON 对象?
  3. 这个例子有两个变量的请求,但是,我如何概括这个方案才能回答从 1 到 n 个变量的请求?

不,没有必要创建临时 table。是的,execute '...' into some_variable 是将(动态)SQL 语句的结果存储到变量中的唯一方法。

构建动态时 SQL 通常最好使用 format() 函数。它使实际的 SQL 更易于阅读,并且还可以正确处理标识符。

可以使用不同的标签嵌套美元引号字符串,这使得在实际 SQL 字符串中嵌入单引号变得更加容易。

CREATE OR REPLACE FUNCTION timescale.get_chronograph_json_time_bucket(
    _ts_start timestamp without time zone,
    _ts_end timestamp without time zone,
    _y1_name text,
    _y2_name text,
    _tb_name text,
    _time_bucket interval)
  RETURNS json
  LANGUAGE plpgsql
AS 
$BODY$
DECLARE
    chronograph json;
BEGIN
  EXECUTE format($sql$
    SELECT json_build_object(
        'ts',json_agg(bucket),
        'y1',json_agg(y1),
        'y2',json_agg(y2))
    FROM (
      SELECT
          time_bucket(,timestamp) AS bucket,
          avg(%I) AS y1,
          avg(%I) AS y2
      FROM timescale.%I
      WHERE timestamp BETWEEN  AND 
      GROUP BY bucket ORDER BY bucket
    ) tmp
    $sql$, _y1_name, _y2_name, _tb_name)
    INTO chronograph
    USING _ts_start, _ts_end, _time_bucket;
    
  RETURN chronograph;
END
$BODY$;

理论上,这可以扩展为遍历列名列表并在该循环中构建整个 SQL 字符串以处理可变的列列表。