SQL рекурсия - PullRequest
       17

SQL рекурсия

7 голосов
/ 12 сентября 2008

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

groups
---------
id  
parent_id
name

group_member
---------
id
group_id
user_id

ID  PARENT_ID  NAME
---------------------------
1   NULL       Cerebra
2   1          CATS 
3   2          CATS 2.0 
4   1          Cerepedia 
5   4          Cerepedia 2.0
6   1          CMS 

ID GROUP_ID USER_ID
---------------------------
1  1        3
2  1        4
3  1        5
4  2        7
5  2        6
6  4        6
7  5        12
8  4        9
9  1        10

Я хочу получить видимые группы для данного пользователя. То есть группы, в которые входит пользователь, и потомки этих групп. Например, с указанными выше данными:

USER  VISIBLE_GROUPS
9     4, 5 
3     1,2,4,5,6
12    5 

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

Ответы [ 8 ]

6 голосов
/ 12 сентября 2008

На ум приходят две вещи:

1 - Вы можете многократно присоединять таблицу к себе, чтобы рекурсивно пройтись по вашему дереву, как в:

SELECT *
FROM
  MY_GROUPS MG1
 ,MY_GROUPS MG2
 ,MY_GROUPS MG3
 ,MY_GROUPS MG4
 ,MY_GROUPS MG5
 ,MY_GROUP_MEMBERS MGM
WHERE MG1.PARENT_ID = MG2.UNIQID (+)
  AND MG1.UNIQID = MGM.GROUP_ID (+)
  AND MG2.PARENT_ID = MG3.UNIQID (+)
  AND MG3.PARENT_ID = MG4.UNIQID (+)
  AND MG4.PARENT_ID = MG5.UNIQID (+)
  AND MGM.USER_ID = 9

Это даст вам такие результаты:

UNIQID PARENT_ID NAME      UNIQID_1 PARENT_ID_1 NAME_1 UNIQID_2 PARENT_ID_2 NAME_2  UNIQID_3 PARENT_ID_3 NAME_3 UNIQID_4 PARENT_ID_4 NAME_4 UNIQID_5 GROUP_ID USER_ID
4      2         Cerepedia 2        1           CATS   1        null        Cerebra null     null        null   null      null       null   8        4        9

Пределом здесь является то, что вы должны добавить новое объединение для каждого "уровня", который вы хотите пройти по дереву. Если ваше дерево имеет, скажем, менее 20 уровней, то вам, вероятно, это сойдет с рук, создав представление, отображающее 20 уровней от каждого пользователя.

2 - Единственный известный мне другой подход - создать рекурсивную функцию базы данных и вызывать ее из кода. Таким образом, у вас все еще будут некоторые накладные расходы на поиск (т. Е. Количество запросов по-прежнему будет равно числу уровней, которые вы проходите по дереву), но в целом это должно быть быстрее, поскольку все это происходит в базе данных.

Я не уверен насчет MySql, но в Oracle такая функция будет похожа на эту (вам придется изменить имена таблиц и полей; я просто копирую то, что делала в прошлом):

CREATE OR REPLACE FUNCTION GoUpLevel(WO_ID INTEGER, UPLEVEL INTEGER) RETURN INTEGER
IS
BEGIN
  DECLARE
    iResult INTEGER;
    iParent INTEGER;
BEGIN
  IF UPLEVEL <= 0 THEN
    iResult := WO_ID;
  ELSE
    SELECT PARENT_ID
    INTO iParent
    FROM WOTREE
    WHERE ID = WO_ID;    
    iResult := GoUpLevel(iParent,UPLEVEL-1);  --recursive
  END;
  RETURN iResult;
  EXCEPTION WHEN NO_DATA_FOUND THEN
    RETURN NULL;
  END;
END GoUpLevel;
/
3 голосов
/ 13 сентября 2008

В книгах Джо Клеко «SQL для умных» и «Деревья и иерархии в SQL для умных» описываются методы, которые полностью избегают рекурсии, используя вложенные множества. Это усложняет обновление, но делает другие запросы (которые обычно требуют рекурсии) сравнительно простыми. В этой статье приведены некоторые примеры , написанные Джо в 1996 году.

0 голосов
/ 26 июня 2009

Я не помню, на какой вопрос SO я нашел ссылку, но эта статья на sitepoint.com (вторая страница) показывает другой способ хранения иерархических деревьев в таблице, который облегчает поиск все дочерние узлы, или путь к вершине, и тому подобное. Хорошее объяснение с примером кода.


PS. Новичок в StackOverflow, все в порядке как ответ, или это действительно был комментарий к вопросу, так как это просто указатель на другое решение (не совсем отвечающий на сам вопрос)?

0 голосов
/ 14 сентября 2008

Был уже похожий вопрос поднят.

Вот мой ответ (немного отредактированный):

Я не уверен, что правильно понимаю ваш вопрос, но это может сработать Мой взгляд на деревья в SQL .

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

С помощью этого метода вы можете легко обновить все узлы, зависящие от модифицированного узла K, с помощью примерно N простых запросов SELECT, где N - это расстояние от K до корневого узла.

Удачи!

0 голосов
/ 12 сентября 2008

Нет способа сделать это в стандарте SQL, но обычно вы можете найти специфичные для поставщика расширения, например CONNECT BY в Oracle.

ОБНОВЛЕНИЕ: Как отмечается в комментариях, это было добавлено в SQL 99.

0 голосов
/ 12 сентября 2008

Не знал, была ли у вас таблица Users, поэтому я получаю список через идентификаторы User_ID, хранящиеся в таблице Group_Member ...

SELECT GroupUsers.User_ID,
        (
        SELECT 
            STUFF((SELECT ',' + 
            Cast(Group_ID As Varchar(10))
            FROM Group_Member Member (nolock) 
            WHERE Member.User_ID=GroupUsers.User_ID
        FOR XML PATH('')),1,1,'') 
        ) As Groups
FROM (SELECT User_ID FROM Group_Member GROUP BY User_ID) GroupUsers

Возвращает:

User_ID    Groups
3          1
4          1
5          1
6          2,4
7          2
9          4
10         1
12         5

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

РЕДАКТИРОВАТЬ: Dang. Просто заметил, что вы используете MySQL. Мое решение было для SQL Server. К сожалению.

- Кевин Фэйрчайлд

0 голосов
/ 12 сентября 2008

Я не думаю, что это можно сделать без использования рекурсии. Вы можете сделать это с помощью одной хранимой процедуры, использующей mySQL, но рекурсия не разрешена в хранимых процедурах по умолчанию. Эта статья содержит информацию о том, как включить рекурсию. Я не уверен в том, насколько это повлияет на производительность при использовании подхода с несколькими запросами. mySQL может оптимизировать хранимые процедуры, но в противном случае я ожидаю, что производительность будет схожей.

0 голосов
/ 12 сентября 2008

Я думаю, вам понадобятся CURSOR для этого, эта ссылка может помочь

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...