【问题标题】:How to get arrays from a normalised table that stores array elements by index?如何从按索引存储数组元素的规范化表中获取数组?
【发布时间】:2020-01-21 09:22:18
【问题描述】:

我有一个表,按它们所属的数组存储数组元素,并且 他们在数组中的索引。它看起来很聪明,因为阵列是 预计是稀疏的,并且它们的元素会单独更新。 假设这是表格:

CREATE TABLE values (
    pk TEXT,
    i INTEGER,
    value REAL,
    PRIMARY KEY (pk, i)
);

 pk | i | value
----+---+-------
 A  | 0 | 17.5
 A  | 1 | 32.7
 A  | 3 | 5.3
 B  | 1 | 13.5
 B  | 2 | 4.8
 B  | 4 | 89.1

现在我想将这些作为真正的数组,即 A 的 {17.5, 32.7, NULL, 53} 和 B 的 {NULL, 13.5, 4.8, NULL, 89.1}

我原以为分组查询很容易实现 和适当的聚合函数。然而,事实证明那里 没有这样的函数可以通过索引将元素放入数组中(或 下标,就像 postgres 所说的那样)。如果 元素是连续的 - 我本来可以使用array_agg ORDER BY i。但我想要结果中的空值 数组。

我最终得到的是这个怪物:

SELECT
  pk,
  ARRAY( SELECT
    ( SELECT value
      FROM values innervals
      WHERE innervals.pk = outervals.pk AND i = generate_series
    )
    FROM generate_series(0, MAX(i))
    ORDER BY generate_series -- is this really necessary?
  )
FROM values outervals
GROUP BY pk;

不得不SELECT … FROM values 两次是丑陋的,查询规划器似乎无法优化这一点。

有没有一种简单的方法可以在子查询中将分组的行作为关系引用,这样我就可以SELECT value FROM generate_series(0, MAX(i)) LEFT JOIN ???

通过定义custom aggregate function 来解决这个问题会更合适吗?


编辑:看起来我一直在寻找的可能是多参数unnestarray_agg,虽然它不是特别优雅:

SELECT
  pk,
  ARRAY( SELECT val
    FROM generate_series(0, MAX(i)) AS series (series_i)
    LEFT OUTER JOIN
      unnest( array_agg(value ORDER BY i),
              array_agg(i ORDER BY i) ) AS arr (val, arr_i)
      ON arr_i = series_i
    ORDER BY series_i
  )
FROM values
GROUP BY pk;

查询计划器甚至似乎 意识到它可以对已排序的 series_iarr_i 进行排序合并 JOIN,尽管我需要付出更多努力才能真正理解EXPLAIN 输出。 编辑 2:实际上是 series_iarr_i 之间的哈希连接,只有外部组聚合使用“排序”策略。

【问题讨论】:

  • 我不认为它可以做得更好。请参阅array_agg 了解另一种方法。你不会摆脱generate_series
  • @LaurenzAlbe 没有generate_series: with t(a,b,c) as (values('a',1,1),('a',3,3),('a',4,4),('b',5,5)), cte as (select *, unnest(array_fill(null::int, array[c - coalesce(lag(b) over (partition by a order by b), -1) - 1]) || c) as d from t) select a, array_agg(d) as d from cte group by a;
  • @LaurenzAlbe 请看我的编辑,array_agg 的用法是您的想法吗?我想知道是否可以只写一次array_aggORDER BY i,通过array_aggunnest 放置多个列。

标签: sql arrays postgresql aggregate-functions generate-series


【解决方案1】:

不确定这是否符合“更简单”的条件 - 但我个人觉得更容易理解:

with idx as (
  select pk, 
         generate_series(0, max(i)) as i
  from "values"
  group by pk
)
select idx.pk, 
       array_agg(v.value order by idx.i) as vals
from idx 
  left join "values" v on v.i = idx.i and v.pk = idx.pk
group by idx.pk;

CTE idx 为每个 PK 值生成所有可能的索引值,然后使用它来聚合值

Online example

【讨论】:

  • 感谢您的回答,不幸的是,这是最慢的解决方案 :-)
【解决方案2】:

通过定义custom aggregate function 来解决这个问题会更合适吗?

它至少显着简化了查询:

SELECT pk, array_by_subscript(i+1, value)
FROM "values"
GROUP BY pk;

使用

CREATE FUNCTION array_set(arr anyarray, index int, val anyelement) RETURNS anyarray
AS $$
BEGIN
    arr[index] = val;
    RETURN arr;
END
$$ LANGUAGE plpgsql STRICT;

CREATE FUNCTION array_fillup(arr anyarray) RETURNS anyarray
AS $$
BEGIN
   -- necessary for nice to_json conversion of arrays that don't start at subscript 1
   IF array_lower(arr, 1) > 1 THEN
       arr[1] = NULL;
   END IF;
   RETURN arr;
END
$$ LANGUAGE plpgsql STRICT;

CREATE AGGREGATE array_by_subscript(int, anyelement) (
 sfunc = array_set,
 stype = anyarray,
 initcond = '{}',
 finalfunc = array_fillup
);

Online example。它还有一个很好的查询计划,可以对 values 进行简单的线性扫描,我必须对 array_set 增长数组的效率进行基准测试
这实际上是最快的解决方案,根据在合理大小的测试数据集上的EXPLAIN ANALYZE 基准。与 ARRAY + UNNEST 解决方案的大约 80 毫秒相比,它花费了 55 毫秒,并且比针对公用表表达式的连接的 160 毫秒要快得多。

【讨论】:

    【解决方案3】:

    我认为这可以作为一种解决方案(比我最初的尝试要好得多),所以我会将其作为答案发布。从this answer我意识到我确实可以通过使用记录语法在array_agg中放入多个值,它只强制我在列定义中声明类型:

    SELECT
      pk,
      ARRAY( SELECT val
        FROM generate_series(0, MAX(i)) AS series (series_i)
        LEFT OUTER JOIN
          unnest(array_agg( (value, i) )) AS arr (val real, arr_i integer)
    --                      ^^^^^^^^^^                ^^^^        ^^^^^^^
          ON arr_i = series_i
        ORDER BY series_i
      )
    FROM values
    GROUP BY pk;
    

    它仍然使用哈希左连接后排序而不是排序后合并连接,但查询规划器的优化可能比我天真的假设更好。

    【讨论】:

      猜你喜欢
      • 2022-01-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-24
      • 2011-04-15
      • 2021-05-13
      • 1970-01-01
      • 2012-09-06
      相关资源
      最近更新 更多