2016-07-10 5 views
1

Say У меня есть стартовый стол (производный), который, как это ...MySQL рекурсивного автообъединение пересекающихся столбцы

------------------------------------------------------- 
| UsergroupID | ParentID | PermissionIDs    | 
------------------------------------------------------- 
| 1   | 0  | 1       | 
| 1   | 0  | 2       | 
| 1   | 0  | 3       | 
| 1   | 0  | 4       | 
| 1   | 0  | 5       | 
| 1   | 0  | 6       | 
| 2   | 1  | 1       | 
| 2   | 1  | 8       | 
| 2   | 1  | 9       | 
| 3   | 1  | 3       | 
| 3   | 1  | 8       | 
| 3   | 1  | 2       | 
------------------------------------------------------- 

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

------------------------------------------------------- 
| UsergroupID | ParentID | PermissionID    | 
------------------------------------------------------- 
| 1   | 0  | 1       | 
| 1   | 0  | 2       | 
| 1   | 0  | 3       | 
| 1   | 0  | 4       | 
| 1   | 0  | 5       | 
| 1   | 0  | 6       | 
| 2   | 1  | 1       | 
| 3   | 1  | 3       | 
| 3   | 1  | 2       | 
------------------------------------------------------- 

который в основном выполняет рекурсивный поиск родительского идентификатора и затем пересекает (внутреннее соединение) значения в столбце PermissionID. Таким образом, у ребенка никогда не будет больше разрешений, чем у родителя.

Я искал информацию о пользовательских функциях (думаю, я мог бы обернуть udf вокруг столбца и пересечь рекурсивно на основе родительского идентификатора), но это не привело меня далеко. Единственное, о чем я могу думать, это не делать это db, а делать это с кодом на стороне сервера.

Solarflare - вот что я только что пробовал использовать ваш скрипт ... это сработало!

delimiter $$ 
CREATE PROCEDURE prcPermCleanup5() 
BEGIN 
DROP TABLE IF EXISTS table1; 
CREATE TABLE table1 (usergroupID INT, parentID INT, StoreID INT) ENGINE=MEMORY; 
INSERT INTO table1 VALUES 
(1,0,1), 
(1,0,2), 
(1,0,3), 
(1,0,4), 
(2,1,1), 
(2,1,2), 
(2,0,5), 
(3,2,2), 
(3,2,7), 
(4,1,1), 
(4,1,2), 
(5,4,1), 
(5,4,8), 
(6,2,1), 
(6,2,6); 
    REPEAT 
    DELETE entry.* 
    FROM table1 entry 
    LEFT JOIN table1 parent 
    ON entry.parentID = parent.usergroupID 
    AND entry.`StoreID` = parent.StoreID 
    WHERE parent.usergroupID IS NULL 
    AND NOT entry.parentID = 0; 
    UNTIL row_count() = 0 END REPEAT; 
    SELECT * FROM table1; 
END $$ 
delimiter ; 
+0

Вы открыты для нормализации данных и не имеют массив разрешений в столбце PermissionsID и вместо этого использовать другую таблицу, в которой перечислены эти разрешения? В большинстве случаев проблемы с ненормализованными данными усложняются, это хороший пример. И вы можете использовать вложенные наборы, хотя вы, вероятно, тоже можете использовать дерево (если вы повторяете свою функцию для рекурсии) – Solarflare

+0

Я открыт для изменения формата данных и моего второго примера таблицы (3-я таблица в исходном сообщении) это тот, который отображает разрешения как строки, а не как конкатенированный столбец. Эта «таблица» также является производной таблицей. – Josh

+0

О, да, это лучше, я думаю, что я пропустил точку «стартового стола». Итак, у вас есть таблица: вы хотите «очистить ее» (удалить или пометить «неправильные» записи) или вы хотите сохранить «неправильные» записи и получить запрос, который возвращает этот более чистый список? Я думаю, что оба они не могут быть выполнены за один шаг, но один из них будет запросом, который вы должны выполнить n раз (n = глубина вашего дерева - 1 или пока ничего не изменится), второй, наверное, требуется временная таблица (вы можете сделать это в хранимой процедуре/udf). – Solarflare

ответ

1

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

Если очистка является одноразовая вещь, вы не можете просто запустить следующий запрос несколько раз, пока ничего не меняется больше (вы должны в большинстве depth of tree - 1 трасс):

delete entry.* 
from table1 entry 
left join table1 parent 
on entry.parentID = parent.usergroupID 
and entry.permissionIDs = parent.permissionIDs 
where parent.usergroupID is null 
and not entry.parentID = 0; 

Вы можете автоматизировать, что повторение в процедуры, например

delimiter $$ 
create procedure prcPermCleanup() 
begin 
    repeat 
    delete entry.* 
    from table1 entry 
    left join table1 parent 
    on entry.parentID = parent.usergroupID 
    and entry.permissionIDs = parent.permissionIDs 
    where parent.usergroupID is null 
    and not entry.parentID = 0; 
    until row_count() = 0 end repeat; 
end $$ 
delimiter ; 

call prcPermCleanup; 

В качестве побочного примечание:

Вы можете нормализовать ваши данные, например, есть отдельная таблица для разрешений:

table permissions: usergroupID | permissionID 

table tree: usergroupID | parentID 

В текущей таблице, имеют ту же самую информацию (информация, которая parentID является родителем usergroupID) несколько раз в вашей таблице, иначе Денормализованную. Практическим следствием этого было бы то, что у вас могло бы быть два разных parentID для того же usergroupID, который обычно не определен в дереве.

+0

Спасибо Solarflare. Я сделаю это и вернусь как можно скорее! – Josh

+0

Hi Solarflare. Я сделал это и увидел, что он работает на начальном проходе (первый запуск), но после этого не похоже, что он удаляет что-либо еще. Попытка отладки теперь ... выглядит как parentId в инструкции where, которая может потребоваться изменить каждый раз. – Josh

+0

@Josh Можете ли вы добавить пример, который не очищается? Код будет остановлен, как только он будет очищен, он не обязательно требует n-1 прогонов (это просто макс), поэтому, возможно, он уже завершился после первого запуска. Но, возможно, я что-то пропустил. – Solarflare

0

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

DROP TABLE IF EXISTS joshTestTable; 
CREATE TABLE joshTestTable (id INT, parent INT, store INT) ENGINE=MEMORY; 
INSERT INTO joshTestTable VALUES 
(1,0,1), 
(1,0,2), 
(1,0,3), 
(1,0,4), 
(2,1,1), 
(2,1,2), 
(2,0,5), 
(3,2,2), 
(3,2,7), 
(4,1,1), 
(4,1,2), 
(5,4,1), 
(5,4,8), 
(6,2,1), 
(6,2,6); 


SELECT * FROM (
    SELECT * FROM joshTestTable p 
    WHERE p.parent = 0 
    UNION ALL 
    SELECT c.* FROM joshTestTable p 
    LEFT JOIN joshTestTable c 
    ON p.id = c.parent 
    AND p.store = c.store 
    AND c.parent=1 
    UNION ALL 
    SELECT c.* FROM joshTestTable p 
    LEFT JOIN joshTestTable c 
    ON p.id = c.parent 
    AND p.store = c.store 
    AND c.parent IN (2,4)) outter WHERE id IS NOT NULL; 

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

SELECT * FROM joshTestTable p 
LEFT JOIN joshTestTable c 
ON p.id = c.parent 
AND p.store = c.store 
LEFT JOIN joshTestTable gc 
ON c.id = gc.parent 
AND c.store = gc.store 
WHERE p.parent = 0; 

В конце концов, давая мне всю информацию, но это только в формате, который я не думаю, что могу использовать. Это дает мне такую ​​таблицу ...

id parent store id parent store id parent store 
1  0 1  2  1  1  6  2 1 
1  0 1  4  1  1  5  4 1 
1  0 2  2  1  2  3  2 2 
1  0 2  4  1  2  NULL NULL NULL 
1  0 3  NULL NULL NULL NULL NULL NULL 
1  0 4  NULL NULL NULL NULL NULL NULL 
2  0 5  NULL NULL NULL NULL NULL NULL 

и в конце концов я ищу это (то, что мой первый запрос действительно дает мне) ...

id parent store 
1 0  1 
1 0  2 
1 0  3 
1 0  4 
2 0  5 
2 1  1 
2 1  2 
3 2  2 
4 1  1 
4 1  2 
5 4  1 
6 2  1 
+0

Я не совсем уверен, что ваша цель здесь состоит в том, чтобы иметь только один запрос, который просто выбирает правильные записи. В этом случае: да, первая проблема заключается в том, что вы должны знать глубину. Вторая более серьезная вещь - это не будет работать рекурсивно: если у вас есть записи (1,0,1), (2,1,2), (3,2,2), это даст вам (1,0,1), (3,2,2), потому что для (3,2,2) он будет выглядеть на один шаг выше и находит (2,1,2), но не «знает», что эта запись на самом деле не будет в конечном наборе результатов, поскольку (1,0,2) не существует. Как я уже сказал в своем ответе, я сомневаюсь, что можно сделать этот рекурсивный поиск только в одном запросе. – Solarflare

+0

ах да, вы совершенно правы, не работая рекурсивно. Я предполагаю, что моя цель состояла в том, чтобы получить некоторую форму конечных данных, которые я могу как можно быстрее присоединиться к другой таблице в идентификаторе и сохранить столбцы (время выполнения). В конце концов, набор выходных данных не обязательно должен был быть в той форме, которую я изначально дал в моем примере, но мне действительно нужно было как-то добраться до места, где я мог бы каким-то образом представить родительские дочерние отношения, присоединиться к другой таблице. – Josh