如何用约束标记一大组“传递组”?

时间:2013-12-15 13:14:52

标签: sql algorithm

在@NealB解决方案之后编辑:@ NealB的解决方案非常快速地与any another one进行比较,并且分发了关于“添加约束以提高性能”的新问题。 @ NealB不需要任何改进, O(n)时间非常简单。


label transitive groups with SQL”的问题有an elegant solution using recursion and CTE ......但是此解决方案会消耗指数时间(!)。我需要使用10000个itens:1000个itens需要1秒钟,2000个需要1天......

约束:在我的情况下可以将问题分解为约100个或更少的部分,但只能选择一组~10个itens,并丢弃所有其他~90个标记为itens ...

有一个通用的algotithm来添加和使用这种“预选”,以减少二次, O(N ^ 2),时间?也许,如评论和@wildplasser所示, O(N log(N))时间;但我希望,“预选”可以减少到 O(N)时间。


(编辑)

我尝试使用alternative algorithm,但需要一些改进才能在此处作为解决方案使用;或者,为了真正提高性能( O(N)时间),需要使用“预选”。

“预选”(约束)基于“超集分组”......由原"How to label 'transitive groups' with SQL?" question t1表说明,

  table T1
  (original T1 augmented by "super-set grouping label" ssg, and more one row)
  ID1 | ID2 | ssg
  1   | 2   | 1
  1   | 5   | 1
  4   | 7   | 1
  7   | 8   | 1 
  9   | 1   | 1
  10  | 11  | 2

所以有三组,

  • g1:{1,2,5,9}因为“1 t 2”,“1 t 5”和“9 t 1“
  • g2:{4,7,8}因为“4 t 7”和“7 t 8”
  • g3:{10,11}因为“10 t 11”

超级组只是辅助分组,

  • ssg1:{g1,g2}
  • ssg2:{g3}

如果我们有 M 超级组项和 N 总计T1项,则平均组长度将小于N / M.我们可以假设(对于我的典型问题)ssg最大长度是~N / M.

因此, the "label algorithm"如果使用ssg约束,则需要使用~N / M项运行M次。

3 个答案:

答案 0 :(得分:2)

这里只有一个SQL问题似乎有点问题。在一些程序的帮助下 在SQL之上编程,解决方案似乎是简单而有效的失败。这是一个简要的概述 可以使用任何调用SQL的过程语言实现的解决方案。

使用主键R声明表ID,其中ID对应与表ID1的{​​{1}}和ID2相同的域。 表格T1包含另一个非关键列,R数字

使用Label中找到的值范围填充表格R。将T1设置为零(无标签)。 使用示例数据,Label的初始设置如下所示:

R

使用主机语言光标和辅助计数器,从Table R ID Label == ===== 1 0 2 0 4 0 5 0 7 0 8 0 9 0 读取每一行。在T1中查找ID1ID2。你会发现其中一个 四个案例:

R

在这种情况下,以前都没有“看到”这些 Case 1: ID1.Label == 0 and ID2.Label == 0 中的任何一个:向计数器添加1然后更新两个 ID行到计数器的值:R

update R set R.Label = :counter where R.ID in (:ID1, :ID2)

在这种情况下, Case 2: ID1.Label == 0 and ID2.Label <> 0 是新的,但已为ID1分配了标签。 ID2需要分配到。{1}} 与ID1相同的标签:ID2

update R set R.Lablel = :ID2.Label where R.ID = :ID1

在这种情况下, Case 3: ID1.Label <> 0 and ID2.Label == 0 是新的,但已为ID2分配了标签。 ID1需要分配到。{1}} 与ID2相同的标签:ID1

update R set R.Lablel = :ID1.Label where R.ID = :ID2

在这种情况下,该行包含冗余信息。 Case 4: ID1.Label <> 0 and ID2.Label <> 0 的两行都应包含相同的Label值。如果不, 存在某种数据完整性问题。啊......不太看见编辑...

编辑我刚刚意识到,这里的R值都可能非零且不同。如果两者都非零并且不同,则此时需要合并两个Label组。您只需选择一个Label并更新其他内容即可与Label匹配。现在,这两个组已合并为相同的update R set R.Label to ID1.Label where R.Label = ID2.Label值。

完成游标后,表Label将包含更新R所需的标签值。

T2

流程表Table R ID Label == ===== 1 1 2 1 4 2 5 1 7 2 8 2 9 1 使用类似T2的内容。最终结果应该是:

set T2.Label to R.Label where T2.ID1 = R.ID

这个过程是极其迭代的,应该毫不费力地扩展到相当大的表。

答案 1 :(得分:1)

我建议你检查一下并使用一些
解决它的通用语言。

http://en.wikipedia.org/wiki/Disjoint-set_data_structure

遍历图形,可以从每个节点运行DFS或BFS,
然后使用这个不相交的集合提示。我认为这应该有用。

答案 2 :(得分:0)

@NealB解决方案更快(!)见example of PostgreSQL implementation here

下面是另一个“暴力算法”的例子,仅用于好奇心!


正如@ peter.petrov和@RBarryYoung所建议的那样,可以避免一些性能问题放弃CTE递归...我do some issues at the basic labeler,并且,abover我添加了一个超集标签分组的约束。这个新的transgroup1_loop()函数正在运行!

PS:此解决方案仍有性能限制,请更好地发布您的答案,或者对此进行一些改编。


 -- DROP table transgroup1;
 CREATE TABLE transgroup1 (
   id serial NOT NULL PRIMARY KEY,
   items integer[], -- two or more items in the transitive relationship
   ssg_label varchar(12), -- the super-set gropuping label 
   dels integer[] DEFAULT array[]::integer[]
 );
 INSERT INTO transgroup1(items,ssg_label)  values
      (array[1, 2],'1'),
      (array[1, 5],'1'),
      (array[4, 7],'1'),
      (array[7, 8],'1'),
      (array[9, 1],'1'),
      (array[10, 11],'2');
 -- or  SELECT array[id1, id2],ssg_label FROM t1,  with 10000 items

他们,通过这两个功能,我们可以解决问题,

  CREATE FUNCTION transgroup1_loop(p_ssg varchar, p_max_i integer DEFAULT 100) 
  RETURNS integer AS $funcBody$
    DECLARE
       cp_dels integer[];
       i integer;
    BEGIN
       i:=1;
       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.ssg_label=$1 AND 
                t1.id>t2.id AND t1.items && t2.items;

           cp_dels := array(
               SELECT DISTINCT unnest(dels) FROM transgroup1
           ); -- ensures all itens to del

           RAISE NOTICE '-- bug, repeting dels, item-%; % dels!  %', i, array_length(cp_dels,1),  array_to_string(cp_dels,';','*');

           EXIT WHEN i>p_max_i OR array_length(cp_dels,1)=0;

           DELETE FROM transgroup1 
           WHERE ssg_label=$1 AND 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);
       RETURN i;
     END;
  $funcBody$ LANGUAGE plpgsql VOLATILE; 

运行并查看结果,可以使用

 SELECT transgroup1_loop('1');  -- run with ssg-1 items only
 SELECT transgroup1_loop('2');  -- run with ssg-2 items only

 -- show all with a sequential group label:
 SELECT *, dense_rank() over (ORDER BY id) AS group_label from transgroup1;

结果:

  id |   items   | ssg_label | dels | group_label 
 ----+-----------+-----------+------+-------------
   4 | {8,7,4}   | 1         | {}   |           1
   5 | {9,5,2,1} | 1         | {}   |           2
   6 | {11,10}   | 2         | {}   |           3

PS:函数array_uunion()same as original

 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;