如何从按索引存储数组元素的规范化 table 中获取数组?
How to get arrays from a normalised table that stores array elements by index?
我有一个 table 按数组元素所属的数组存储数组元素,并且
他们在数组中的索引。这看起来很聪明,因为阵列是
预计是稀疏的,并单独更新它们的元素。
假设这是 table:
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
现在我想将它们作为真正的数组获取,即 {17.5, 32.7, NULL, 53}
用于 A,{NULL, 13.5, 4.8, NULL, 89.1}
用于 B。
我原以为分组查询很容易实现
和适当的聚合函数。然而,原来有
没有这样的函数可以通过其索引将元素放入数组(或
下标,正如 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 来解决这个问题是否更合适?
编辑:看来我正在寻找的东西可以用多参数 unnest
和 array_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_i
和 arr_i
进行排序合并 JOIN
,尽管我需要付出更多努力才能真正理解 EXPLAIN
输出。 编辑2:其实是series_i
和arr_i
之间的hash join,只是外层聚合使用了"sorted"策略。
不确定这是否符合 "simpler" - 我个人认为它更容易理解:
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
值生成所有可能的索引值,然后使用它来聚合值
我认为这可以作为一个解决方案(比我最初的尝试要好得多)所以我将 post 它作为一个答案。从 我意识到我确实可以通过使用记录语法在 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;
它仍然使用散列左连接后跟排序而不是排序后跟合并连接,但也许查询规划器比我天真的假设做的优化更好。
Would it be more appropriate to solve this by defining a 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 毫秒,并且比针对常见 table 表达式的连接的 160 毫秒要快得多。
我有一个 table 按数组元素所属的数组存储数组元素,并且 他们在数组中的索引。这看起来很聪明,因为阵列是 预计是稀疏的,并单独更新它们的元素。 假设这是 table:
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
现在我想将它们作为真正的数组获取,即 {17.5, 32.7, NULL, 53}
用于 A,{NULL, 13.5, 4.8, NULL, 89.1}
用于 B。
我原以为分组查询很容易实现
和适当的聚合函数。然而,原来有
没有这样的函数可以通过其索引将元素放入数组(或
下标,正如 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 来解决这个问题是否更合适?
编辑:看来我正在寻找的东西可以用多参数 unnest
和 array_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_i
和 arr_i
JOIN
,尽管我需要付出更多努力才能真正理解 EXPLAIN
输出。 编辑2:其实是series_i
和arr_i
之间的hash join,只是外层聚合使用了"sorted"策略。
不确定这是否符合 "simpler" - 我个人认为它更容易理解:
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
值生成所有可能的索引值,然后使用它来聚合值
我认为这可以作为一个解决方案(比我最初的尝试要好得多)所以我将 post 它作为一个答案。从 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;
它仍然使用散列左连接后跟排序而不是排序后跟合并连接,但也许查询规划器比我天真的假设做的优化更好。
Would it be more appropriate to solve this by defining a 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 毫秒,并且比针对常见 table 表达式的连接的 160 毫秒要快得多。