2016-05-04 4 views
1

У меня есть таблица categories, с некоторой рекурсией. Некоторые категории являются подкатегориями и поэтому имеют значение в поле parent_category_id. Категории верхнего уровня имеют нулевое значение в поле parent_category_id.Сортировать по: по рекурсивному столу

Требование является список категорий, так что они выглядят как:

Parent category 1 
    Sub category 1 
    Sub category 2 

Parent category 2 
    Subcategory 3 
    Subcategory 4 

Возможно ли это с помощью одного оператора порядка запроса, или мне нужно сделать отдельные запросы?

Некоторые образцы данных, запрошенных: enter image description here

+1

Пожалуйста, добавьте некоторые примеры данных и ожидаемых результатов. Кроме того, пометьте вопрос с помощью РСУБД, которую вы используете - иерархические запросы, подобные этим, не всегда переносимы между различными РСУБД. –

+1

Рекурсивное общее табличное выражение (ORACLE, MS SQL, Postgresql) точно соответствует задаче. – Serg

+0

Есть ли только одна подкатегория? Или могут быть более глубокие уровни? Пожалуйста, укажите структуру вашей таблицы и RDBMS (поставщик и версия) – Shnugo

ответ

2

В MYSQL, которая не поддерживает рекурсивное CTE-х:

select id, path(id) from mytable order by 2, где path(id) определяется

DELIMITER $$ 
CREATE FUNCTION path(v_id INT(10)) RETURNS varchar(255) 
begin 
declare v_path varchar(255); 
declare v_parent_id INT(10); 
declare MAX_ITERS int; 
declare iters int; 
set v_path = ''; 
set MAX_ITERS = 20; 
set iters = 0; 

if not exists (select * from node where id = v_id) then 
    return 'no such node'; 
end if; 

if not exists (select * from node where parent_id < 0) then 
    return 'no root node in table'; 
end if; 

select parent_id into v_parent_id from node where id = v_id; 
while (v_parent_id >= 0) do 
    set iters = iters + 1; 
    if iters >= MAX_ITERS then 
     return 'path too long'; 
    end if; 
    select parent_id, concat(id, '.', v_path) into v_parent_id, v_path from node where id = v_id; 
    set v_id = v_parent_id; 
end while; 
return trim(both '.' from v_path); 

end$$ 


DELIMITER ; 
; 

Примечание, в моем примере , вместо нулевого родительского узла для корня, я использую идентификатор узла -1.

Для обеспечения работы поддерживайте вторую таблицу (используя триггеры), которая хранит «путь» каждого узла, где путь (узел) определяется вышеуказанным UDF.

+0

Это * процедурный код * ... SQL должен быть * установленным кодом на основе * ... Вы используете очень плохо выполняющий многозадачный UDF и очень плохо исполняющий цикл. Это лучше сделать с рекурсивным CTE ... – Shnugo

+0

Выше для MYSQL http://stackoverflow.com/questions/8833535/how-to-transform-a-mssql-cte-query-to-mysql –

+0

Привет, Джош, да, это правда, если нет rec CTE, вы должны каким-то образом пройти через дерево. Вы отредактировали подсказку MySQL ... Поскольку мы (все еще) не знаем фактические РСУБД, это стоит +1 :-) – Shnugo

2

Это синтаксис SQL Server:

DECLARE @category TABLE(category_id INT,parent_category_id INT,caption varchar(100)); 
INSERT INTO @category VALUES 
(1,NULL,'Top1') 
,(2,1,'Sub11') 
,(3,1,'Sub12') 
,(4,2,'Sub111') 
,(5,2,'Sub112') 
,(6,NULL,'Top2') 
,(7,6,'Sub21') 
,(8,6,'Sub22') 
,(9,8,'Sub221'); 

WITH rCTE AS 
(
    SELECT c.category_id 
      ,c.parent_category_id 
      ,c.caption 
      ,CAST(ROW_NUMBER() OVER(ORDER BY c.category_id) AS VARCHAR(MAX)) + '.' AS [Level] 
      ,REPLACE(STR(ROW_NUMBER() OVER(ORDER BY c.category_id),4),' ','0') + '.' AS [Sortable] 
    FROM @category AS c WHERE parent_category_id IS NULL 

    UNION ALL 

    SELECT c.category_id 
      ,c.parent_category_id 
      ,c.caption 
      ,rCTE.[Level] + CAST(ROW_NUMBER() OVER(ORDER BY c.category_id) AS VARCHAR(MAX)) + '.' 
      ,rCTE.Sortable + REPLACE(STR(ROW_NUMBER() OVER(ORDER BY c.category_id),4),' ','0') + '.' 
    FROM rCTE 
    INNER JOIN @category AS c ON c.parent_category_id=rCTE.category_id 
) 

SELECT * 
FROM rCTE 
ORDER BY Sortable 

Результат

+-------------+--------------------+---------+--------+-----------------+ 
| category_id | parent_category_id | caption | Level | Sortable  | 
+-------------+--------------------+---------+--------+-----------------+ 
| 1   | NULL    | Top1 | 1.  | 0001.   | 
+-------------+--------------------+---------+--------+-----------------+ 
| 2   | 1     | Sub11 | 1.1. | 0001.0001.  | 
+-------------+--------------------+---------+--------+-----------------+ 
| 4   | 2     | Sub111 | 1.1.1. | 0001.0001.0001. | 
+-------------+--------------------+---------+--------+-----------------+ 
| 5   | 2     | Sub112 | 1.1.2. | 0001.0001.0002. | 
+-------------+--------------------+---------+--------+-----------------+ 
| 3   | 1     | Sub12 | 1.2. | 0001.0002.  | 
+-------------+--------------------+---------+--------+-----------------+ 
| 6   | NULL    | Top2 | 2.  | 0002.   | 
+-------------+--------------------+---------+--------+-----------------+ 
| 7   | 6     | Sub21 | 2.1. | 0002.0001.  | 
+-------------+--------------------+---------+--------+-----------------+ 
| 8   | 6     | Sub22 | 2.2. | 0002.0002.  | 
+-------------+--------------------+---------+--------+-----------------+ 
| 9   | 8     | Sub221 | 2.2.1. | 0002.0002.0001. | 
+-------------+--------------------+---------+--------+-----------------+ 
+0

Извините, произошла ошибка с жестко закодированным «1» в родительской части rCTE – Shnugo

+0

отличным ответом, я думаю, вам нужно добавить Order by Level –

+1

@LiyaTansky, да, если OP нуждается в этом но вам нужно было бы вернуть уровень в сортируемой форме с заполненными числами (например, '001.002.001'), иначе' 1.12.3' был отсортирован * до * как '1.2.3' ... – Shnugo