2010-09-05 132 views
0

我的數據庫架構是這樣的:遞歸MySQL查詢

Table t1: 
    id 
    valA 
    valB 

Table t2: 
    id 
    valA 
    valB 

我想要做什麼,是,在這些表中的一個給定的行集,發現有相同的兩個表中的行valA或valB(比較valA與valA和valB與valB,而不valA與valB)。 Then,我想查找與上一個查詢的結果中的行相同的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,電平2爲第1級t1/5,...)A 遞歸的有限深度沒問題。隨着時間的推移,我可能希望在同一個模式中包含更多的表到查詢中。如果爲此目的很容易地擴展查詢將會很好。

但是最重要的是,的性能。你能告訴我最快的方法來完成這個嗎?

在此先感謝!

+1

MySQL沒有遞歸查詢的支持。 – 2010-09-05 19:12:06

+0

您將需要使用外部應用程序來構建和運行查詢,或編寫存儲過程。 – Mchl 2010-09-05 19:13:20

+0

@OMG小馬:我知道。這就是爲什麼我說「有限的遞歸深度沒問題」。複製和粘貼是醜陋的,但它是一個解決方案。 @湯姆斯的回答聽起來有趣,更優雅,我會看看它。 – eWolf 2010-09-05 20:02:40

回答

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); 
+0

一個非遞歸方法,不是性能,嗯..... – 2010-09-06 16:41:16

+0

哇!非常感謝您的回答!它真的幫了我很多。表現還可以。我之前正在爲每個遞歸步驟執行一個單獨的查詢,並在PHP中進行了兩者之間的工作,因此它已經意味着性能的巨大增加:)仍然只有一個(可能很容易)的問題:是否可以調用此函數一組tid/id對嗎?結果應該全部顯示在一個行集中。 – eWolf 2010-09-06 20:04:13

+0

是否正確索引?也許你應該發佈一個解釋計劃,以便我們可以看到發生了什麼:)你可以傳入儘可能多的tid/id參數 - 只需將它們插入深度爲0的臨時子表中,它將從那裏工作(if這就是你的意思??) – 2010-09-07 06:11:40

2

您可以使用存儲過程做到這一點(見列表7和7A):

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

你只需要弄清楚一個查詢遞歸的步驟 - 以已經發現的行找到更多的行。

如果你有哪些支持SQL-99遞歸公用表表達式(如PostgreSQL或火鳥,暗示暗示)的數據庫,你可以採取同樣的方法在上面的鏈接,但使用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,在select上加上group by c.*)。

您還需要爲查詢添加限制以排除已選擇的行;你不需要在基本版本中這樣做,因爲聯合會有明顯的效果,但是如果你添加一個計數列,那麼一行可以不止一次被包含在結果中,而且你會得到笛卡兒爆炸。但是你不能輕易地阻止它,因爲你無法對rCTE進行子查詢,這意味着你不能說任何像and not exists (select * from descendants d where d.tbl = c.tbl and d.id = c.id)

我知道所有這些關於遞歸查詢的東西對你來說都沒用,但是我發現它鉚接了,所以請原諒我。