【发布时间】:2012-08-16 23:50:31
【问题描述】:
我有一个这种形式的 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 条记录。
谁能给我指出正确的方向?
编辑: Craig:我有 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 行(修饰符数组中最多 7 项)开始,查询花费了 250 毫秒来填充输出表中的 2630 行。太棒了。
【问题讨论】:
-
这是用于排列,但很接近:wiki.postgresql.org/wiki/Permutations
-
在尝试任何事情之前:您使用的是哪个 PostgreSQL 版本?对于这样的问题至关重要,其中某些功能仅在较新版本中可用。例如,如果您没有 CTE 或 array_unnest,那么我会说“安装 plperl;用 plperl 编写”。
-
OK,9.0是合理的;您可以使用窗口函数、递归 CTE、数组取消嵌套等等。还有几个问题:集合总是 3 个元素吗?或者它是可变大小的?另外,是否需要以任何方式订购组合 ID?
-
感谢克雷格的帮助。数组是可变大小的,可以为空(在这种情况下,函数应该只写入一条记录,设置 base_id 但 mods 为空)。 combo_id 应该是一个序列号(我认为——我们在其他数据库中称之为自动增量)。
-
好的,我的意思是:combo_id 是否必须从每组组合的 1 开始并向上计数,就像在您的示例中一样?或者它可以是 any 唯一编号吗?如果它必须计数,你需要一个
row_number窗口函数;如果它必须是唯一的,可以使用普通的serial。
标签: postgresql plpgsql combinatorics