2016-07-10 27 views
1

sagen, dass ich einen Start Tabelle haben (abgeleitet), die wie das ist ...mysql rekursive selbst verbinden sich schneidende Spalten

------------------------------------------------------- 
| 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       | 
------------------------------------------------------- 

und ich suche eine End-Ergebnismenge zu erhalten, die

wie folgt aussieht
------------------------------------------------------- 
| 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       | 
------------------------------------------------------- 

, die im Grunde eine rekursive Suche auf der übergeordneten ID und dann schneiden (innere Verbindung) die Werte in der PermissionID Spalte. Ein Kind kann also nie mehr Berechtigungen als ein Elternteil haben.

Ich habe Sachen auf benutzerdefinierten Funktionen nachgeschlagen (ich denke, ich könnte ein PDF um eine Spalte wickeln und es rekursiv schneiden basierend auf einer Eltern-ID), aber das brachte mich nicht sehr weit. Das einzige, woran ich wirklich denken kann, ist, es nicht db-Seite zu tun, sondern mit serverseitigem Code.

Solarflare - Hier ist, was ich gerade mit Ihrem Skript versucht habe ... das hat funktioniert!

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

Sind Sie öffnen Ihre Daten zu normalisieren und haben nicht ein Array von Berechtigungen in der Spalte BerechtigungenID und verwenden Sie stattdessen eine andere Tabelle, die diese Berechtigungen auflistet? Die meisten Probleme werden mit unnormalisierten Daten komplizierter, das ist ein gutes Beispiel. Und möglicherweise möchten Sie verschachtelte Mengen verwenden, obwohl Sie wahrscheinlich auch die Struktur verwenden können (wenn Sie Ihre Funktion für die Rekursion wiederholen) – Solarflare

+0

Ich bin offen für die Änderung des Datenformats und meines zweiten Tabellenbeispiels (3. Tabelle im ursprünglichen Beitrag) ist eine, die die Berechtigungen als Zeilen und nicht als eine verkettete Spalte anzeigt. Diese "Tabelle" ist auch eine abgeleitete Tabelle. – Josh

+0

Ah ok, ja, das ist besser, ich glaube, ich habe den Punkt des "Starttisches" verpasst. Sie haben also Ihre Tabelle: Möchten Sie "aufräumen" (löschen oder markieren Sie die "falschen" Einträge) oder möchten Sie die "falschen" Einträge behalten und eine Abfrage haben, die diese sauberere Liste zurückgibt? Ich denke, beides kann nicht in einem einzigen Schritt gemacht werden, aber der erste wäre eine Abfrage, die du n-mal ausführen müsstest (n = Tiefe deines Baumes - 1 oder bis sich nichts ändert), der zweite würde, denke ich, eine temporäre Tabelle benötigen (Sie können dies in einer gespeicherten Prozedur/udf tun). – Solarflare

Antwort

1

Es ist nicht möglich, dass in einer einzigen Abfrage zu tun (es sei denn, vielleicht unter bestimmten besonderen Bedingungen), aber man kann nur eine Bereinigungs Abfrage wiederholt eine Rekursion, so zu tun.

Wenn Ihre Bereinigung ist eine einmalige Sache, kann man einfach nicht mehr die folgende Abfrage mehrmals, bis sich nichts ändert laufen (man braucht höchstens depth of tree - 1 Läufe):

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; 

Sie, dass die Wiederholung in eine automatisieren Verfahren, z

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; 

Als Randbemerkung:

Sie möchten Ihre Daten normalisieren, z.B. eine separate Tabelle für Ihre Berechtigungen verfügen:

table permissions: usergroupID | permissionID 

table tree: usergroupID | parentID 

In Ihrer aktuellen Tabelle, haben Sie die gleichen Informationen (die Informationen, die parentID die Eltern ein usergroupID sind) mehrmals in der Tabelle, auch bekannt als denormalized. Eine praktische Konsequenz davon wäre, dass Sie zwei verschiedene parentID für denselben usergroupID haben könnten, die normalerweise in einem Baum undefiniert wären.

+0

Danke Solarflare. Ich werde es versuchen und so schnell wie möglich zurückkommen! – Josh

+0

Hallo Solarflare. Ich habe das mal geschossen und gesehen, dass es beim ersten Durchgang funktioniert, aber danach sieht es nicht so aus, als ob es etwas anderes löscht. Der Versuch, jetzt zu debuggen .... sieht aus wie die parentId in der WHERE-Anweisung, die jedes Mal geändert werden muss. – Josh

+0

@Josh Kannst du ein Beispiel hinzufügen, das nicht aufgeräumt wird? Der Code wird gestoppt, sobald er aufgeräumt ist, er braucht nicht unbedingt n-1 Läufe (es ist nur der max), also ist er vielleicht schon nach dem ersten Lauf fertig. Aber vielleicht habe ich etwas verpasst. – Solarflare

0

Ok, also ist das nicht unbedingt die Antwort ... aber im folgenden Setup finde ich, dass ich die richtigen Tabelleninformationen zurückbekomme. Dieses Beispiel funktioniert unter der Annahme, dass ich weiß (und kann eine maximale Tiefe und Gruppierung Beziehungen (die ich glaube, ich kann, obwohl die Gruppierung Beziehungen ein Problem sein kann, wenn ich dies tatsächlich mit "echten" Daten). das ist, was ich

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; 

ich bin immer noch nicht super heiß auf diesem als Lösung wegen all der bekannten Sachen habe ich auf eine gespeicherte Prozedur in übergeben müssen würde, dies zu automatisieren. ich wirklich in der Nähe von wenn ich nur links mache, komme ich in die Baumtiefe, aber in diesem Fall bekomme ich eine Tabelle mit einer Reihe von Spaltensätzen, die ich irgendwie pivotieren muss, zB

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; 

Dieses Ende gibt mir alle Informationen, aber es ist nur in einem Format, das ich nicht verwenden kann. Es gibt mir einen Tisch wie diesen ...

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 

und am Ende dafür suche ich (das ist, was meine erste Abfrage mir nicht geben) ...

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

Ich bin mir nicht sicher, ob Ihr Ziel hier ist, nur eine Abfrage zu haben, die nur die richtigen Einträge auswählt. In diesem Fall: Ja, das erste Problem ist, dass Sie die Tiefe kennen müssen. Die zweite schwerere Sache ist, dass dies nicht rekursiv funktioniert: Wenn Sie Einträge haben (1,0,1), (2,1,2), (3,2,2), wird es Ihnen geben (1,0,1), (3,2,2), denn für (3,2,2) wird es einen Schritt höher nach oben schauen und (2,1,2) finden, aber nicht "wissen", dass dieser Eintritt tatsächlich nicht sein wird im Endergebnissatz, weil (1,0,2) nicht existiert. Wie ich in meiner Antwort gesagt habe, bezweifle ich, dass es möglich ist, diese rekursive Suche in nur einer Abfrage durchzuführen. – Solarflare

+0

ah ja, du bist völlig richtig auf der nicht rekursiv funktioniert. Ich denke, mein Ziel war es, zu einer Form von Enddaten zu gelangen, die ich so schnell wie möglich zu einer anderen Tabelle der ID- und Speicherspalten hinzufügen kann (Ausführung zeitweise). Am Ende musste die Ausgabe nicht wirklich in der Form sein, die ich ursprünglich in meinem Beispiel angegeben hatte, aber ich musste irgendwie dorthin gelangen, wo ich diese Eltern-Kind-Beziehungen in irgendeiner Weise darstellen konnte, die ich herausholen konnte eine Verbindung zu dem anderen Tisch. – Josh