2017-02-08 9 views
3

Я следующий MySQL таблицы с данными выборки следующим образом:SQL - Рекурсивный запрос

id location   parentid 
1  UK     0 
2  East Anglia   1 
3  Cambridgeshire  2 
4  Norfolk    2 
5  Suffolk    2 
6  East Midlands  1 
7  Derbyshire   6 
8  Leicestershire  6 
9  EU Countries  0 
10 Austria    9 
11 Belgium    9 

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

Поиски folk должны вернуться:

id location 
4 Norfolk, East Anglia, UK 
5 Suffolk, East Anglia, UK 

Поиски East должны вернуться:

id location 
2 East Anglia, UK 
6 East Midlands, UK 

Поиски Bel должны вернуться:

id location 
11 Belgium 

В приведенном выше мы исключают конкатенацию EU countries

Очевидно следующее не работает:

select c.id, CONCAT_WS(', ', c.location, p.location, pp.location) as location 
from tbl_locations c 
    outer left join tbl_locations p on p.id = c.parentid 
    outer left join tbl_locations pp on pp.id = p.parentid 
where c.location like '%whatever%' 
+1

вы можете сделать функцию, которая возвращает текст данного locationid. edit: функция должна вызывать себя до тех пор, пока не назовешь ничего (это также определение рекурсии) – Doruk

+0

http://guilhembichot.blogspot.co.uk/2013/11/with-recursive-and-mysql.html –

+0

что такое максимальная глубина? – sumit

ответ

3

Если вы хотите только родительское расположение это делается с автообъединение:

select c.id, c.location, p.id, p.location 
from tbl_locations c 
    outer left join tbl_locations p on p.id = c.parentid 
where c.location like '%whatever%' 

Это может быть продлен (через внешние соединения) на произвольное количество уровней, но запрос будет длинным. Например. до трех уровней:

select c.id, c.location, p.id, p.location, pp.id, pp.location 
from tbl_locations c 
    outer left join tbl_locations p on p.id = c.parentid 
    outer left join tbl_locations pp on pp.id = p.parentid 
where c.location like '%whatever%' 

Более общие рекурсивные запросы зависят от деталей РСУБД. Самый простой подход - использовать выражение Common Table (CTE). Но MySQL их не поддерживает (по крайней мере, пока). Могут использоваться другие подходы: Generating Depth based tree from Hierarchical Data in MySQL (no CTEs).

+0

позволяет сказать, что у меня есть 4 уровня, будет ли вышеупомянутое иметь огромные последствия для производительности? – adam78

+0

@ adam78: как всегда зависит, и вам действительно нужно измерить реалистичным набором данных. Даже со всеми местоположениями Великобритании на подробном уровне (тысячи строк) и индексированными 'id' и' parentid' я бы ожидал, что это будет довольно быстро. Но единственный способ убедиться в том, что это тестирование с реалистичными данными, и подумать, как часто вы будете использовать такие запросы. – Richard

1

1) Нет стандартного SQL-запроса, который может рассчитать транзитивное замыкание транзитивного отношения. Если вы хотите вставлять команды выбора, вы всегда будете иметь максимальную глубину, которая может быть достигнута.

2) Нет стандартного SQL-запроса, который будет возвращать строку с переменным числом столбцов. Поэтому вам придется форматировать ваши результаты так или иначе (например, csv).

Однако, вы можете выполнить, что в MySQL с хранимой процедурой:

1 CREATE DATABASE IF NOT EXISTS test; 
2 USE test; 
3 
4 
5 DROP TABLE IF EXISTS location; 
6 CREATE TABLE location (id INT UNSIGNED PRIMARY KEY, name VARCHAR(30) NOT NULL, parent_id INT UNSIGNED NULL REFERENCES location(id)); 
7 
8 INSERT INTO location VALUES 
9 (1,"UK",0), 
10 (2,"East Anglia",1), 
11 (3,"Cambridgeshire",2), 
12 (4,"Norfolk",2), 
13 (5,"Suffolk",2), 
14 (6,"East Midlands",1), 
15 (7,"Derbyshire",6), 
16 (8,"Leicestershire",6); 
17 
18 
19 
20 
21 DROP FUNCTION IF EXISTS location_with_parents; 
22 DELIMITER // 
23 CREATE FUNCTION location_with_parents(location_id INT UNSIGNED) RETURNS VARCHAR(255) READS SQL DATA 
24 BEGIN 
25  DECLARE LOC_STR VARCHAR(255) DEFAULT NULL; 
26  DECLARE LOC_ADD VARCHAR(255) DEFAULT NULL; 
27  DECLARE PAR_ID INT UNSIGNED DEFAULT location_id; 
28  
29  SELECT name INTO LOC_STR FROM location where id=PAR_ID; 
30  loop_label: LOOP 
31   SELECT parent_id INTO PAR_ID FROM location where id=PAR_ID; 
32   
33   IF PAR_ID = 0 THEN 
34    LEAVE loop_label; 
35   ELSE 
36    SELECT name INTO LOC_ADD FROM location where id=PAR_ID; 
37    SET LOC_STR = CONCAT(LOC_STR, ', ', LOC_ADD); 
38    ITERATE loop_label; 
39   END IF; 
40  END LOOP loop_label; 
41  RETURN LOC_STR; 
42  
43 END; 
44 // 
45 
46 DELIMITER ; 
47 
48 
49 
50 SELECT location_with_parents(id) FROM location WHERE name LIKE "%folk%"; 
51 
52 DROP DATABASE test; 

работает для меня с MySQL 5.6.35

Надеется, что это помогает!

1

Внизу запрос дает точный результат, который вы хотите использовать методом Recursion.

Select S.ID , 
    concat(S.location,',', Group_concat 
    (distinct A.location ORDER BY A.location SEPARATOR ',')) as location 
    from 

     ( SELECT distinct @r AS _id ,location, 
         (
         SELECT @r := parentid 
         FROM tbl_locations 
         WHERE id = _id 
         ) AS parentid, 
         @l := @l + 1 AS level 
       FROM (
         SELECT @r := h.ID, 
           @l := 0, 
           @cl := 0 
         from tbl_locations h 
         where location like '%folk%' 

         ) vars, 
         tbl_locations h 
       WHERE @r <> 0 


     )A , tbl_locations S 
       where s.location like '%folk%' 
       group by S.ID 

Выход:

location like '%East%' : 

enter image description here

location like '%Folk%' 

enter image description here

его хороший вопрос, и проверить и спросить, если у вас есть какие-либо проблемы.

+0

@ adam78 вы проверили это. Его работа или нет? –