我有一个表,其中的ID对具有传递关系 t ,即,如果“ A t B”和“ B t C”,则“ A t C”。样品:
table T1 ID1 | ID2 1 | 2 1 | 5 4 | 7 7 | 8 9 | 1
所以有两个小组,
g1
g2
我需要通过“纯标准SQL”生成一个新表或视图:
table T2 ID1 | ID2 | LABEL 1 | 2 | 1 1 | 5 | 1 4 | 7 | 2 7 | 8 | 2 9 | 1 | 1
PS -1:我们可以通过列出“传递组”
SELECT DISTINCT label, id FROM (SELECT id1 as id, * FROM T2) UNION (SELECT id2 as id, * FROM T2) ORDER BY 1,2;
PS -2:我使用的是PostgreSQL 9.1,但是如果有使用“标准SQL”的解决方案,我更喜欢。
现在,在2013年有一个新需求,我需要 使用10000 itens :使用 @GordonLinoff的优雅解决方案 (如上),使用1000 iten需要1秒,使用2000 iten 需要1天… 表现不好 。
(这是 最好的解决方案,这么快!)。该表T1与问题文本相同,第二个(临时)表R用于处理和显示结果,
T1
R
CREATE TABLE R ( id integer NOT NULL, -- PRIMARY KEY, label integer NOT NULL DEFAULT 0 ); CREATE FUNCTION t1r_labeler() RETURNS void AS $funcBody$ DECLARE label1 integer; label2 integer; newlabel integer; t t1%rowtype; BEGIN DELETE FROM R; INSERT INTO R(id) SELECT DISTINCT unnest(array[id1,id2]) FROM T1 ORDER BY 1; newlabel:=0; FOR t IN SELECT * FROM t1 LOOP -- -- BASIC LABELING: -- -- SELECT label INTO label1 FROM R WHERE id=t.id1; SELECT label INTO label2 FROM R WHERE id=t.id2; IF label1=0 AND label2=0 THEN newlabel:=newlabel+1; UPDATE R set label=newlabel WHERE ID in (t.id1,t.id2); ELSIF label1=0 AND label2!=0 THEN UPDATE R set label=label2 WHERE ID=t.id1; ELSIF label1!=0 AND label2=0 THEN UPDATE R set label=label1 WHERE ID=t.id2; ELSIF label1!=label2 THEN -- time consuming UPDATE tmp.R set label=label1 WHERE label = label2; END IF; END LOOP; END; $funcBody$ LANGUAGE plpgsql VOLATILE;
准备和运行,
-- same CREATE TABLE T1 (id1 integer, id2 integer); DELETE FROM T1; INSERT INTO T1(id1,id2) -- populate the standard input VALUES (1, 2), (1, 5), (4, 7), (7, 8), (9, 1); -- or SELECT id1, id2 FROM table_with_1000000_items; SELECT t1r_labeler(); -- run SELECT * FROM R ORDER BY 2; -- show
处理最坏的情况
最后一个条件,当时label1!=label2,是最耗时的操作,在高连接性的情况下(最坏的情况)必须避免或将其分开。
label1!=label2
要报告某种警报,您可以计算该过程运行最后一个条件的时间比例,和/或可以分隔最后一次更新。
如果分开,则可以对其进行分析和处理,因此,消除了最后一个ELSIF,并在第一个循环和第二个循环之后添加了:
ELSIF
-- ... first loop and checks here ... FOR t IN SELECT * FROM tmp.t1 LOOP -- -- MERGING LABELS: -- -- SELECT label INTO label1 FROM R WHERE id=t.id1; SELECT label INTO label2 FROM R WHERE id=t.id2; IF label1!=0 AND label2!=0 AND label1!=label2 THEN UPDATE R set label=label1 WHERE label=label2; END IF; END LOOP; -- ...
最坏情况的示例:组中有超过1000个(连接的)节点到10000个节点,平均长度为“每个标记组10个”(核心),只有很少的路径连接核心。
这个其他解决方案比较慢(是 蛮力算法 ),但是当您需要对数组进行直接处理并且不需要如此快速的解决方案(并且没有“最坏的情况”)时可以使用。
正如@peter.petrov和@RBarryYoung建议使用更适当的数据结构 …我回到阵列时是“更适当的数据结构”。毕竟, 以下解决方案(!)可以提高速度(与@GordonLinoff的算法相比) 。
第一步是t1将问题文本表转换为临时表transgroup1,我们可以在其中计算新流程,
t1
transgroup1
-- DROP table transgroup1; CREATE TABLE transgroup1 ( id serial NOT NULL PRIMARY KEY, items integer[], -- two or more items in the transitive relationship dels integer[] DEFAULT array[]::integer[] ); INSERT INTO transgroup1(items) SELECT array[id1, id2] FROM t1; -- now suppose t1 a 10000 items table;
他们,通过这两个功能,我们可以解决问题,
CREATE FUNCTION array_uunion(anyarray,anyarray) RETURNS anyarray AS $$ -- ensures distinct items of a concatemation SELECT ARRAY(SELECT unnest($1) UNION SELECT unnest($2)) $$ LANGUAGE sql immutable; CREATE FUNCTION transgroup1_loop() RETURNS void AS $BODY$ DECLARE cp_dels integer[]; i integer; max_i integer; BEGIN i:=1; max_i:=10; -- or 100 or more, but need some control to be secure LOOP UPDATE transgroup1 SET items = array_uunion(transgroup1.items,t2.items), dels = transgroup1.dels || t2.id FROM transgroup1 AS t1, transgroup1 AS t2 WHERE transgroup1.id=t1.id AND t1.id>t2.id AND t1.items && t2.items; cp_dels := array( SELECT DISTINCT unnest(dels) FROM transgroup1 ); -- ensures all itens to del EXIT WHEN i>max_i OR array_length(cp_dels,1)=0; DELETE FROM transgroup1 WHERE id IN (SELECT unnest(cp_dels)); UPDATE transgroup1 SET dels=array[]::integer[]; i:=i+1; END LOOP; UPDATE transgroup1 -- only to beautify SET items = ARRAY(SELECT unnest(items) ORDER BY 1 desc); END; $BODY$ LANGUAGE plpgsql VOLATILE;
当然,要运行并查看结果,您可以使用
SELECT transgroup1_loop(); -- not 1 day but some hours! SELECT *, dense_rank() over (ORDER BY id) AS group from transgroup1;
结果
id | items | ssg_label | dels | group ----+-----------+-----------+------+------- 4 | {8,7,4} | 1 | {} | 1 5 | {9,5,2,1} | 1 | {} | 2