Рекурсивный запрос MySQL

0

Моя схема базы данных выглядит следующим образом:

Table t1:
    id
    valA
    valB

Table t2:
    id
    valA
    valB

Что я хочу сделать, это для заданного набора строк в одной из этих таблиц, найдите строки в обеих таблицах, которые имеют одинаковые valA или valB (сравнивая valA с valA и valB с valB, а не valA с valB). Затем, я хочу искать строки с теми же 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: уровень 1 для t1/2 и t2/1, уровень 2 для t1/5,...) A ограниченная глубина рекурсии в порядке. Со временем я, возможно, захочу включить в запрос больше таблиц, следующих за той же схемой. Было бы неплохо, если бы было легко расширить запрос для этой цели.

Но самое главное, производительность. Можете ли вы сказать мне, как можно быстрее выполнить это?

Спасибо заранее!

  • 1
    MySQL не имеет поддержки рекурсивных запросов.
  • 0
    Вам нужно будет либо использовать внешнее приложение для создания и выполнения запросов, либо написать хранимую процедуру.
Показать ещё 1 комментарий
Теги:
database
optimization
recursion

2 ответа

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
    нерекурсивный метод, который не так эффективен, хмммм .....
  • 0
    ВАУ! Большое спасибо за ваш ответ! Это действительно очень помогло мне. Производительность в порядке. Ранее я выполнял отдельный запрос для каждого шага рекурсии и выполнял промежуточную работу в PHP, поэтому это уже означает огромное увеличение производительности :) Остается только один (возможно, простой) вопрос: можно ли вызвать эту функцию в набор пар TID / ID одновременно? Затем результат должен отображаться в одном наборе строк.
Показать ещё 4 комментария
2

Вы можете сделать это с помощью хранимых процедур (см. списки 7 и 7а):

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

Вам просто нужно выяснить запрос для этапа рекурсии - взять уже найденные строки и найти еще несколько строк.

Если у вас была база данных, которая поддерживала рекурсивные общие выражения таблиц SQL-99 (например, PostgreSQL или Firebird, подсказка подсказки), вы могли бы использовать тот же подход, что и в приведенной выше ссылке, но используя rCTE в качестве основы, поэтому избегайте необходимо записать хранимую процедуру.

EDIT: я решил сделать это с помощью rCTE в PostgreSQL 8.4, и хотя я могу найти строки, я не могу найти способ обозначить их глубиной, на которой они были найдены. Во-первых, я создаю представление для унификации таблиц:

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 в списке столбцов), и вы не можете использовать агрегатную функцию в списке столбцов (поэтому no max(p.depth) + 1 в списке столбцов в сочетании с group by c.* на выборке).

Вам также необходимо добавить ограничение на запрос, чтобы исключить уже выбранные строки; вам не нужно делать это в базовой версии из-за отличительного эффекта объединения, но если вы добавите столбец count, то строка может быть включена в результаты несколько раз с разными значениями, и вы будете получить декартовский взрыв. Но вы не можете легко предотвратить это, потому что у вас не может быть подзапросов против rCTE, что означает, что вы не можете сказать ничего вроде and not exists (select * from descendants d where d.tbl = c.tbl and d.id = c.id)!

Я знаю, что все эти вещи о рекурсивных запросах вам бесполезны, но я нахожу это клепкой, поэтому, пожалуйста, извините меня.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню