递归MySQL查询

时间:2010-09-05 19:10:05

标签: sql mysql database optimization recursion

我的数据库架构如下所示:

Table t1:
    id
    valA
    valB

Table t2:
    id
    valA
    valB

我想要做的是,对于其中一个表中的给定行集,在两个表中找到具有相同valA或valB 的行(将valA与valA和valB进行比较valB,而不是valB的valA)。 然后,我想查找与上一个查询结果中的行具有相同valA或valB的行,依此类推

Example data:

t1 (id, valA, valB):
    1, a, B
    2, b, J
    3, d, E
    4, d, B
    5, c, G
    6, h, J

t2 (id, valA, valB):
    1, b, E
    2, d, H
    3, g, B


Example 1:

Input: Row 1 in t1
Output: 
    t1/4, t2/3
    t1/3, t2/2
    t2/1
    ...


Example 2:

Input: Row 6 in t1
Output:
    t1/2
    t2/1

我希望级别的搜索,结果中的行(例如,示例1:t1 / 2和t2的级别1) / 1,t1 / 5的级别2,...)有限深度的递归是可以的。随着时间的推移,我可能希望在查询中包含更多遵循相同模式的表。如果为此目的扩展查询很容易就会很好。

但重要的是,表现。你能告诉我实现这个目标的最快方法吗?

提前致谢!

2 个答案:

答案 0 :(得分:2)

您可以使用存储过程(参见清单7和7a):

http://www.artfulsoftware.com/mysqlbook/sampler/mysqled1ch20.html

你只需要找出递归步骤的查询 - 获取已经找到的行并找到更多的行。

如果您有一个支持SQL-99递归公用表表达式的数据库(如PostgreSQL或Firebird,提示提示),您可以采用与上述链接相同的方法,但使用rCTE作为框架,因此避免使用需要编写存储过程。

编辑:我在PostgreSQL 8.4中使用rCTE进行此操作,虽然我可以找到行,但我找不到用它们被发现的深度标记它们的方法。首先,我创建一个统一表格的视图:

create view t12 (tbl, id, vala, valb) as (
  (select 't1', id, vala, valb from t1)
  union
  (select 't2', id, vala, valb from t2)
)

然后执行此查询:

with recursive descendants (tbl, id, vala, valb) as (
  (select *
  from t12
  where tbl = 't1' and id = 1) -- the query that identifies the seed rows, here just t1/1
  union
  (select c.*
  from descendants p, t12 c
  where (p.vala = c.vala or p.valb = c.valb)) -- the recursive term
)
select * from descendants;

您可以想象捕获深度就像向rCTE添加深度列一样简单,在种子查询中设置为零,然后在递归步骤中以某种方式递增。但是,我无法找到任何方法,因为你不能在递归步骤中针对rCTE编写子查询(所以没有像列列表中的select max(depth) + 1 from descendants那样),并且你不能使用列列表中的聚合函数(列列表中没有max(p.depth) + 1,而选择中的group by c.*也是如此。

您还需要为查询添加限制以排除已选择的行;你不需要在基本版本中这样做,因为联合的区别效果,但是如果你添加一个计数列,那么结果可以在不同的计数中多次包含在结果中,你将会得到笛卡尔爆炸。但是你不能轻易地阻止它,因为你不能对rCTE有子查询,这意味着你不能说and not exists (select * from descendants d where d.tbl = c.tbl and d.id = c.id)之类的东西!

我知道所有这些关于递归查询的东西对你没用,但我发现它很吸引人,所以请原谅。

答案 1 :(得分:2)

试试这个,虽然它没有经过全面测试,但看起来有效:P(http://pastie.org/1140339

drop table if exists t1;
create table t1
(
id int unsigned not null auto_increment primary key,
valA char(1) not null,
valB char(1) not null
)
engine=innodb;

drop table if exists t2;
create table t2
(
id int unsigned not null auto_increment primary key,
valA char(1) not null,
valB char(1) not null
)
engine=innodb;

drop view if exists t12;
create view t12 as
select 1 as tid, id, valA, valB from t1
union
select 2 as tid, id, valA, valB from t2;

insert into t1 (valA, valB) values 
('a','B'),
('b','J'),
('d','E'),
('d','B'),
('c','G'),
('h','J');

insert into t2 (valA, valB) values 
('b','E'),
('d','H'),
('g','B');

drop procedure if exists find_children;

delimiter #

create procedure find_children
(
in p_tid tinyint unsigned,
in p_id int unsigned 
)
proc_main:begin

declare done tinyint unsigned default 0;
declare dpth smallint unsigned default 0;


create temporary table children(
 tid tinyint unsigned not null,
 id int unsigned not null,
 valA char(1) not null,
 valB char(1) not null,
 depth smallint unsigned default 0,
 primary key (tid, id, valA, valB)
)engine = memory;

insert into children select p_tid, t.id, t.valA, t.valB, dpth from t12 t where t.tid = p_tid and t.id = p_id; 

create temporary table tmp engine=memory select * from children;

/* http://dec.mysql.com/doc/refman/5.0/en/temporary-table-problems.html */

while done <> 1 do

    if exists(
    select 1 from t12 t 
      inner join tmp on tmp.valA = t.valA or tmp.valB = t.valB and tmp.depth = dpth) then

        insert ignore into children
        select 
        t.tid, t.id, t.valA, t.valB, dpth+1 
      from t12 t
      inner join tmp on tmp.valA = t.valA or tmp.valB = t.valB and tmp.depth = dpth;

        set dpth = dpth + 1;            

        truncate table tmp;
        insert into tmp select * from children where depth = dpth;

    else
        set done = 1;
    end if;

end while;

select * from children order by depth;

drop temporary table if exists children;
drop temporary table if exists tmp;

end proc_main #


delimiter ;


call find_children(1,1);

call find_children(1,6);