我有一个这种形式的PostgreSQL表:
base_id int | mods smallint[]
3 | {7,15,48}
我需要填写此表格的表格:
combo_id int | base_id int | mods smallint[]
1 | 3 |
2 | 3 | {7}
3 | 3 | {7,15}
4 | 3 | {7,48}
5 | 3 | {7,15,48}
6 | 3 | {15}
7 | 3 | {15,48}
8 | 3 | {48}
我认为我可以使用几乎完全相同的函数来完成此操作,迭代第一个表并将组合写入第二个表: Generate all combinations in SQL
但是,我是Postgres的新手,不能为我的生活找出如何使用plpgsql做到这一点。它不需要特别快;它只会在后端定期运行。第一个表有大约80个记录,粗略计算表明我们可以预期第二个表的大约2600条记录。
至少有人能指出我正确的方向吗?
编辑:克雷格:我有PostgreSQL 9.0。我成功地使用了UNNEST():
FOR messvar IN SELECT * FROM UNNEST(mods) AS mod WHERE mod BETWEEN 0 AND POWER(2, @n) - 1
LOOP
RAISE NOTICE '%', messvar;
END LOOP;
但后来不知道下一步该去哪里。
编辑:作为参考,我最终使用了Erwin的解决方案,添加了一行以向每个集添加空结果('{}'),并且特殊情况Erwin引用已删除:
CREATE OR REPLACE FUNCTION f_combos(_arr integer[], _a integer[] DEFAULT '{}'::integer[], _z integer[] DEFAULT '{}'::integer[])
RETURNS SETOF integer[] LANGUAGE plpgsql AS
$BODY$
DECLARE
i int;
j int;
_up int;
BEGIN
IF array_length(_arr,1) > 0 THEN
_up := array_upper(_arr, 1);
IF _a = '{}' AND _z = '{}' THEN RETURN QUERY SELECT '{}'::int[]; END IF;
FOR i IN array_lower(_arr, 1) .. _up LOOP
FOR j IN i .. _up LOOP
CASE j-i
WHEN 0,1 THEN
RETURN NEXT _a || _arr[i:j] || _z;
ELSE
RETURN NEXT _a || _arr[i:i] || _arr[j:j] || _z;
RETURN QUERY SELECT *
FROM f_combos(_arr[i+1:j-1], _a || _arr[i], _arr[j] || _z);
END CASE;
END LOOP;
END LOOP;
ELSE
RETURN NEXT _arr;
END IF;
END;
$BODY$
然后,我使用该函数填充我的表:
INSERT INTO e_ecosystem_modified (ide_ecosystem, modifiers)
(SELECT ide_ecosystem, f_combos(modifiers) AS modifiers FROM e_ecosystem WHERE ecosystemgroup <> 'modifier' ORDER BY ide_ecosystem, modifiers);
在我的源表中的79行中,modifiers数组中最多有7个项,查询花了250ms来填充输出表中的2630行。奇妙。
答案 0 :(得分:4)
在我睡过之后,我有了一个全新的,更简单,更快的想法:
CREATE OR REPLACE FUNCTION f_combos(_arr anyarray)
RETURNS TABLE (combo anyarray) LANGUAGE plpgsql AS
$BODY$
BEGIN
IF array_upper(_arr, 1) IS NULL THEN
combo := _arr; RETURN NEXT; RETURN;
END IF;
CASE array_upper(_arr, 1)
-- WHEN 0 THEN -- does not exist
WHEN 1 THEN
RETURN QUERY VALUES ('{}'), (_arr);
WHEN 2 THEN
RETURN QUERY VALUES ('{}'), (_arr[1:1]), (_arr), (_arr[2:2]);
ELSE
RETURN QUERY
WITH x AS (
SELECT f.combo FROM f_combos(_arr[1:array_upper(_arr, 1)-1]) f
)
SELECT x.combo FROM x
UNION ALL
SELECT x.combo || _arr[array_upper(_arr, 1)] FROM x;
END CASE;
END
$BODY$;
呼叫:
SELECT * FROM f_combos('{1,2,3,4,5,6,7,8,9}'::int[]) ORDER BY 1;
512行,总运行时间:2.899 ms
NULL
和空数组处理特殊情况。非常简单,一旦你得到它。
这假定array subscripts从 1 开始(默认)。如果您不确定自己的值,请调用此函数进行标准化:
SELECT * FROM f_combos(_arr[array_lower(_arr, 1):array_upper(_arr, 1)]);
不确定是否有更优雅的方法来规范化数组下标。我发布了一个问题:
Normalize array subscripts for 1-dimensional array so they start with 1
CREATE OR REPLACE FUNCTION f_combos2(_arr int[], _a int[] = '{}', _z int[] = '{}')
RETURNS SETOF int[] LANGUAGE plpgsql AS
$BODY$
DECLARE
i int;
j int;
_up int;
BEGIN
IF array_length(_arr,1) > 0 THEN
_up := array_upper(_arr, 1);
FOR i IN array_lower(_arr, 1) .. _up LOOP
FOR j IN i .. _up LOOP
CASE j-i
WHEN 0,1 THEN
RETURN NEXT _a || _arr[i:j] || _z;
WHEN 2 THEN
RETURN NEXT _a || _arr[i:i] || _arr[j:j] || _z;
RETURN NEXT _a || _arr[i:j] || _z;
ELSE
RETURN NEXT _a || _arr[i:i] || _arr[j:j] || _z;
RETURN QUERY SELECT *
FROM f_combos2(_arr[i+1:j-1], _a || _arr[i], _arr[j] || _z);
END CASE;
END LOOP;
END LOOP;
ELSE
RETURN NEXT _arr;
END IF;
END;
$BODY$;
呼叫:
SELECT * FROM f_combos2('{7,15,48}'::int[]) ORDER BY 1;
适用于1维整数数组。
这可以进一步优化,但这个问题的范围当然不需要
ORDER BY
强制显示问题中显示的顺序。
提供NULL或空数组,因为注释中提到了NULL
。
使用PostgreSQL 9.1进行测试,但应该适用于任何中途现代版本。
array_lower()
and array_upper()
至少从PostgreSQL 7.4开始。只有参数默认值是8.4版中的新增功能。很容易被替换。
表现不错。
SELECT DISTINCT * FROM f_combos('{1,2,3,4,5,6,7,8,9}'::int[]) ORDER BY 1;
511行,总运行时间:7.729 ms
它建立在简单形式之上,只创建相邻元素的所有组合:
CREATE FUNCTION f_combos(_arr int[])
RETURNS SETOF int[] LANGUAGE plpgsql AS
$BODY$
DECLARE
i int;
j int;
_up int;
BEGIN
_up := array_upper(_arr, 1);
FOR i in array_lower(_arr, 1) .. _up LOOP
FOR j in i .. _up LOOP
RETURN NEXT _arr[i:j];
END LOOP;
END LOOP;
END;
$BODY$;
但是对于具有两个以上元素的子数组,这将失败。所以:
对于具有3个元素的任何子数组,添加一个仅包含外部两个元素的数组。这是此特殊情况的快捷方式,可提高性能,并非严格需要。
对于任何超过3个元素的子数组,我将外部两个元素用用相同函数构建的所有内部元素组合填充的递归强>
答案 1 :(得分:3)
一种方法是使用递归CTE。但是,Erwin的更新递归函数明显更快,并且可以更好地扩展,所以这对于一种有趣的不同方法非常有用。 Erwin的更新版本更加实用。
我尝试了一种计数方法(参见结尾),但没有快速从数组中提取任意元素,它被证明比递归方法慢。
CREATE OR REPLACE FUNCTION combinations(anyarray) RETURNS SETOF anyarray AS $$
WITH RECURSIVE
items AS (
SELECT row_number() OVER (ORDER BY item) AS rownum, item
FROM (SELECT unnest($1) AS item) unnested
),
q AS (
SELECT 1 AS i, $1[1:0] arr
UNION ALL
SELECT (i+1), CASE x
WHEN 1 THEN array_append(q.arr,(SELECT item FROM items WHERE rownum = i))
ELSE q.arr END
FROM generate_series(0,1) x CROSS JOIN q WHERE i <= array_upper($1,1)
)
SELECT q.arr AS mods
FROM q WHERE i = array_upper($1,1)+1;
$$ LANGUAGE 'sql';
这是一个多态函数,所以它适用于任何类型的数组。
逻辑是使用工作表迭代unnested输入集中的每个项目。从工作表中的空数组开始,其生成数为1.对于输入集中的每个条目,将两个新数组插入到工作表中,并使用递增的世代号。其中一个是上一代输入数组的副本,另一个是输入数组,其中附加了输入集中的(generation-number)项。当世代号超过输入集中的项目数时,返回上一代。
您可以使用combinations(smallint[])
函数生成所需的结果,并将其作为具有row_number
窗函数的组合返回函数。
-- assuming table structure
regress=# \d comb
Table "public.comb"
Column | Type | Modifiers
---------+------------+-----------
base_id | integer |
mods | smallint[] |
SELECT base_id, row_number() OVER (ORDER BY mod) AS mod_id, mod
FROM (SELECT base_id, combinations(mods) AS mod FROM comb WHERE base_id = 3) x
ORDER BY mod;
regress=# SELECT base_id, row_number() OVER (ORDER BY mod) AS mod_id, mod
regress-# FROM (SELECT base_id, combinations(mods) AS mod FROM comb WHERE base_id = 3) x
regress-# ORDER BY mod;
base_id | mod_id | mod
---------+--------+-----------
3 | 1 | {}
3 | 2 | {7}
3 | 3 | {7,15}
3 | 4 | {7,15,48}
3 | 5 | {7,48}
3 | 6 | {15}
3 | 7 | {15,48}
3 | 8 | {48}
(8 rows)
Time: 2.121 ms
零元素数组产生null结果。如果您希望combinations({})
返回一行{}
,那么带有UNION ALL
的{{1}}即可完成此任务。
您似乎想要the k-combinations for all k in a k-multicombination,而不是简单的组合。请参阅number of combinations with repetition。
换句话说,你想要你的集合中所有元素的k组合,对于从0到n的所有k,其中n是集合大小。
相关SO问题:SQL - Find all possible combination,它有关于位计数的非常有趣的答案。
Pg中存在Bit operations,因此应该可以采用bit counting方法。你期望它更有效率,但是因为从数组中选择一个分散的元素子集这么慢,它实际上可以解决更慢。
{}
如果您能找到更快的方式来编写CREATE OR REPLACE FUNCTION bitwise_subarray(arr anyarray, elements integer)
RETURNS anyarray AS $$
SELECT array_agg($1[n+1])
FROM generate_series(0,array_upper($1,1)-1) n WHERE ($2>>n) & 1 = 1;
$$ LANGUAGE sql;
COMMENT ON FUNCTION bitwise_subarray(anyarray,integer) IS 'Return the elements from $1 where the corresponding bit in $2 is set';
CREATE OR REPLACE FUNCTION comb_bits(anyarray) RETURNS SETOF anyarray AS $$
SELECT bitwise_subarray($1, x)
FROM generate_series(0,pow(2,array_upper($1,1))::integer-1) x;
$$ LANGUAGE 'sql';
,那么bitwise_subarray
会非常快。比如说,一个小的C扩展函数,但是I'm only crazy enough to write one of those for an SO answer。