2016-03-19 12 views
0

Ich habe zwei Tabellen ps_product und ps_category. Die Produkttabelle hat ungefähr 146690 Zeilen in der lokalen Datenbank und 196000 in der entfernten Datenbank, mit btree Index auf id_product und id_default_category. Die Kategorietabelle hat einige 851 Zeilen und btree Index auf id_category und id_parent Spalten.Optimieren mysql rekursive Join-Abfrage

Die Kategorien sind hierarchisch bis 6 Ebenen und ich brauche alle Produkte mit allen ihren Kategorien. Also, wenn Produkt 53 von Kategorie 67 ist, die wiederum eine Unterkategorie von 50 ist, was eine Unterkategorie von 43 ist ... und so weiter bis zu 1, was die Stammkategorie ist, werde ich -> 67 erhalten -> 50-> 43-> 20-> 1-> null.

Ich habe eine MySQL-Abfrage herausgefunden, die eine Selbst ps_category 6mal verbinden macht die Daten zu holen und es dauert etwa 0,8 Sekunden auf lokalen db und - 5 sec über das Netzwerk auszuführen. Kann ich es irgendwie optimieren? Die Abfrage:

SELECT 
    p.id_product, c.id_category, c1.id_category, c2.id_category, c3.id_category, c4.id_category, c5.id_category, c6.id_category 
FROM `ps_category` c 
    left join ps_product p on p.id_category_default = c.id_category 
    left join ps_category c1 on c1.id_category = c.id_parent 
    left join ps_category c2 on c2.id_category = c1.id_parent 
    left join ps_category c3 on c3.id_category = c2.id_parent 
    left join ps_category c4 on c4.id_category = c3.id_parent 
    left join ps_category c5 on c5.id_category = c4.id_parent 
    left join ps_category c6 on c6.id_category = c5.id_parent 
+0

0,8 Mikrosekunde ist sehr schnell. Warum suchen Sie nach einer optimierten Lösung? – Shadow

+0

@Shadow, änderte die Statistik –

+0

Ihre Abfrage ist in Ordnung für was Sie wollen und kann kaum besser optimiert werden. Abhängig von Ihrer Anwendung (wie oft neue Produkte zur Datenbank hinzugefügt werden) können Sie eine MATERIALIZED VIEW einrichten. Das Konzept existiert nicht in MySQL, aber Sie können es "nachahmen": Überprüfen Sie diese http://www.fromdual.com/mysql-materialized-views –

Antwort

0

Eine Möglichkeit zu versuchen, ist die Verwendung einer Verschlusstabelle.

CREATE TABLE category_closure (
    `a_catagory_id` SMALLINT UNSIGNED NOT NULL, 
    `d_category_id` SMALLINT UNSIGNED NOT NULL, 
    PRIMARY KEY (`a_category_id`,`d_category_id`) 
) ENGINE=InnoDB 

Diese Tabelle enthält die Beziehungen zwischen den verschiedenen Ebenen der Kategorien in der Hierarchie. Die a_category_id bezieht sich auf den Vorfahren in einer Beziehung, während sich die d_category_id auf den Nachkommen bezieht.

Damit die Tabelle ordnungsgemäß funktioniert, muss jede Kategorie als eigener Vor- und Nachfahre in die Tabelle eingegeben werden.

INSERT INTO category_closure 
(a_category_id, d_category_id) 
SELECT 
id_category, 
id_category 
FROM ps_category 

Dann können Sie Ihre bekannten Beziehungen von der id_parent Spalte auf jeder Kategorie ein.

INSERT INTO category_closure 
(a_category_id, d_category_id) 
SELECT 
id_parent, 
id_category 
FROM ps_category 
WHERE id_parent IS NOT NULL 

Schließlich müssen Sie die Punkte verbinden. Führen Sie das folgende SELECT in einer do..while-Schleife aus, und so lange wie Zeilen von ihm zurückgegeben werden, fügen Sie diese Zeilen in der Verschlusstabelle ein, und fahren Sie mit der Schleife fort.

SELECT 
cc1.a_category_id, 
cc2.d_category_id 
FROM category_closure cc1 
INNER JOIN category_closure cc2 
ON cc2.a_category_id = cc1.d_category_id 
LEFT OUTER JOIN category_closure missing_cc 
ON missing_cc.a_category_id = cc1.a_category_id 
AND missing_cc.d_category_id = cc2.d_category_id 
WHERE missing_cc.a_category_id IS NULL 

Mit dieser Abfrage werden alle vorhandenen Beziehungen abgefragt und gefunden, was auch vorhanden sein soll. Zum Beispiel haben Sie die folgende Kette:

53> 67> 50

Das bedeutet, dass Sie die folgenden Datensätze haben, aus den ersten beiden INSERTs: (50,50) (67,67) (53,53) (50,67) (67,53) (und andere).

Nun, was wir brauchen, ist (50,53), da 53 ein Nachkomme von 50 ist. In der obigen SELECT-Abfrage entspricht cc1 dem (50,67) Datensatz. cc2 entspricht dem Datensatz (67,53). Das bedeutet, missing_cc versucht, mit 50 aus cc1 (a_category_id) und 53 aus cc2 (d_category_id) zusammen zu finden.

Da anfänglich kein solcher Datensatz existiert, gibt die SELECT-Anweisung diese zwei Zeilen zurück, Sie können sie einfügen und wiederholen. Diesmal weiter in der Kette.Sie brauchen nicht (oder Ihr Programm) zu wissen, wie viele Ebenen es gibt, fahren Sie einfach fort, bis das SELECT keine Ergebnisse mehr findet.

Schließlich, sobald Ihre Schließung Tisch aufgebaut ist, können Sie die entsprechenden Informationen SELECT:

SELECT 
p.id_product, 
c.id_category, 
GROUP_CONCAT(cc.a_category_id) AS parent_category_ids 
FROM ps_category c 
LEFT OUTER JOIN ps_product p 
ON p.id_category_default = c.id_category 
LEFT OUTER JOIN category_closure cc 
ON cc.d_category_id = c.id_category 
AND cc.a_category_id != c.id_category 
GROUP BY c.id_category, p.id_product 

Dadurch werden alle Kategorien auswählen, werden alle Produkte in jeder Kategorie, und dann eine durch Kommata getrennte Liste der Vorfahren Kategorien liefern für jede dieser Kombinationen.

Nun, das ist tatsächlich ziemlich viel wiederholte Informationen, da Sie die Kategorien und ihre Vorfahren getrennt von den Produkten ausführen können, aber es läuft wirklich darauf hinaus, wie Sie diese Daten verwenden möchten, ob Sie trennen können die Abfragen oder nicht.

Hinweis: Wenn Sie Kategorien hinzufügen und entfernen, müssen Sie diesen Vorgang jedes Mal wiederholen, nachdem Sie alle aktuellen Abschlussdaten gelöscht haben. Es gibt bessere Möglichkeiten, dies zu tun, als hier gezeigt, und auf diese Weise können Sie sogar die Verwendung der Spalte id_parent löschen (insbesondere wenn die Hierarchie nicht zyklisch ist), aber solche Dinge liegen außerhalb des Bereichs dieser Frage.

Diese Antwort sollte zumindest in der Lage sein, Sie etwas ausprobieren zu lassen, ohne Ihre Anwendung oder vorhandene Daten ändern zu müssen.