Рекурсия SQL Server с несколькими таблицами

Я пытаюсь привлечь пользователей, входящих в группы, но группы также могут быть членами групп. Таким образом, вопрос в том, кто имеет «эффективный» доступ к определенным группам. Я видел много примеров использования CTE на SQL Server, и это похоже на один и тот же случай, но все примеры довольно просты и касаются одной таблицы, такой как организационная диаграмма. В моем случае мне нужно объединить несколько таблиц, и я просто не могу понять всю суть рекурсии.

Таблицы:

usergroup: у каких пользователей какие группы

uid int
gid int

groupgroup: какие группы дают какие группы

parentid int
childid int

group: многие группы в одной sourceid.

gid int
sourceid int
name varchar

Например:

usergroup

uid   gid
------------
1     101
1     102
2     102
2     120

groupgroup

parentid  childid
-------------------
103       101
110       103
120       110

group

gid   sourceid    name
-----------------------
101   5           group101
102   5           group102
103   5           group103
110   8           group110
120   8           group120

Разница между родительским и дочерним идентификатором заключается в том, что родительский идентификатор предоставляет дочерний идентификатор. Итак, если вы являетесь членом родительского идентификатора, у вас есть эффективный доступ к childid. Как и ожидалось, родительский и дочерний идентификаторы БУДУТ СУЩЕСТВОВАТЬ в групповой таблице.

Моя цель — просто получить список всех пользователей, которые имеют доступ к тем или иным группам в данном источнике (идентификатор источника). Без вложенности для sourceid 5 было бы просто:

select *
from usergroup
inner join group on group.gid = usergroup.gid
where group.sourceid = 5

И этот запрос даст мне, что у пользователя 1 есть группы 101, 102, а у пользователя 2 — 102.

Но проблема в том, что у пользователя может быть родительский gid, который дает дочерний идентификатор на любом уровне вложенности, и эти дочерние элементы могут быть gid с sourceid, равным 5.

Вместо этого я бы надеялся, что у пользователя 1 все еще есть 101 и 102 напрямую, а у пользователя 2 есть 102 напрямую И 101 и 103 из-за вложенности.

На базовом уровне мне просто нужно знать, что пользователи XYS имеют группы ABC в источнике либо напрямую, либо через вложение и флаг или другой маркер, если это вложение или прямой. Если бы мы могли получить что-то вроде уровня (0 = прямой, 1 = на один уровень выше и т. д.), это было бы еще лучше.

Я могу сделать это в коде Java, создав большую карту/список и зациклившись, но есть ли элегантный способ получить это напрямую из SQL Server и, возможно, быстрее, чем в коде?

Спасибо!

Пожалуйста, предоставьте минимально воспроизводимый пример с выборочными данными (охватывающими все крайние случаи) и желаемыми результатами.

Dale K 15.08.2024 00:57

Я не вижу, чем это отличается от любых других вопросов о рекурсивном cte, сегодня было задано несколько вопросов, посвященных этой проблеме stackoverflow.com/questions/78866869/… просто следуйте родительской группеID и т. д.

siggemannen 15.08.2024 01:02

Подсказка: используйте примеры CTE, которые вы видите повсюду в таблице groupgroup, чтобы создать проекцию, которая возвращает строку для каждого потомка (как childid) каждого предка (как parentid). Затем замените usergroup в своем запросе на этот CTE.

StriplingWarrior 15.08.2024 01:06
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
0
3
50
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Рекурсивный CTE состоит из привязки запроса, за которым следует UNION ALL к одному или нескольким рекурсивным запросам. (Обычно существует только одна рекурсивная часть, но в некоторых сценариях, например в двоичных деревьях, их может быть более одной.)

В вашем случае якорем будет выбрано все прямое членство в группах из таблицы UserGroup. Эта часть представляет собой обычный запрос, который заполняет рекурсивный CTE начальным набором строк.

Рекурсивная часть запроса расширяет существующие строки с помощью дополнительного запроса (или запросов), который объединяет существующие строки со всем, что необходимо для рекурсивного расширения результатов. В вашем случае он соединит CTE с таблицей GroupGroup, чтобы пройти по цепочке и включить все родительские группы.

Для каждой строки исходного набора рекурсивный запрос может возвращать ноль, одну или несколько дополнительных строк. Эти результаты добавляются в объединенный набор результатов и возвращаются в рекурсию, что, возможно, дает еще больше результатов. Хотя должен быть предел. ПО умолчанию SQL Server допускает 100 уровней или рекурсию, но это можно переопределить с помощью параметра MAXRECURSION.

with ExpandedUserGroups as (
    -- Anchor part
    select ug.uid, ug.gid
    from UserGroup ug

    union all

    -- Recursive part
    select xug.uid, gg.parentid
    from ExpandedUserGroups xug
    join GroupGroup gg
        on gg.childid = xug.gid
)
select xug.uid, xug.gid, g.sourceid, g.name
from ExpandedUserGroups xug
join [Group] g
    on g.gid = xug.gid
order by xug.uid, xug.gid;

Результаты

жидкость гид идентификатор источника имя 1 101 5 группа101 1 102 5 группа102 1 103 5 группа103 1 110 8 группа110 1 120 8 группа120 2 102 5 группа102 2 120 8 группа120

См. эту db<>fiddle для демонстрации.

Вот и все, спасибо. Я изменил скрипт, чтобы переключить родительский и дочерний идентификатор, добавить уровень и предложениеwhere. Но это было ключом, спасибо!

user26830288 15.08.2024 01:44

Небольшая модификация приведенного выше переключения родительского и дочернего элементов, добавление уровня вложенности и предложенияwhere, но именно это объединение было ключевым.

with ExpandedUserGroups as (
    -- Anchor part
    select ug.uid, ug.gid, 0 as level
    from UserGroup ug
    union all
    -- Recursive part
    select xug.uid, gg.childid, level+1 as level
    from ExpandedUserGroups xug
    join GroupGroup gg
        on gg.parentid = xug.gid
)
select xug.uid, xug.gid, xug.level, g.sourceid
from ExpandedUserGroups xug
join [Group] g
    on g.gid = xug.gid
where sourceid = 5
order by xug.uid, xug.gid;
жидкость гид идентификатор источника имя уровень 1 101 5 группа101 0 1 102 5 группа102 0 2 101 5 группа101 3 2 102 5 группа102 0 2 103 5 группа103 2

Другие вопросы по теме