Wählen Sie Produkte, bei denen die Kategorie in der Hierarchie zu jeder Kategorie gehört
-
10-07-2019 - |
Frage
Ich habe eine Produkttabelle, die eine FK für eine Kategorie enthält, wird die Tabelle Kategorien in einer Art und Weise erstellt, die jede Kategorie eine übergeordnete Kategorie haben kann, zB:
Computers
Processors
Intel
Pentium
Core 2 Duo
AMD
Athlon
Ich brauche eine Auswahlabfrage zu machen, dass, wenn die gewählte Kategorie Prozessoren sind, wird es Produkte zurück, die in Intel, Pentium, Core 2 Duo, Amd, etc ...
Ich dachte an eine Art „Cache“ zu schaffen, die alle Kategorien in der Hierarchie für jede Kategorie in der DB gespeichert werden und beinhalten die „IN“ in der where-Klausel. Ist dies die beste Lösung?
Lösung
Die beste Lösung hierfür ist in der Datenbank Designphase. Ihre Kategorien Tisch braucht ein Nested Set sein. Der Artikel Verwalten hierarchischer Daten in MySQL ist nicht, dass MySQL spezifische ( trotz des Titels) und gibt einen guten Überblick über die verschiedenen Methoden zur Herstellung eine Hierarchie in einer Datenbanktabelle gespeichert werden.
Zusammenfassung:
Verschachtelte Sätze
- Wählt einfach für jede Tiefe
- Einfügungen und Löschungen sind hart
Standard parent_id basierend Hierarchie
- Wählt basieren auf inneren Joins (so behaart schnell bekommen)
- Einfügungen und Löschungen sind einfach
So basiert auf dem Beispiel, wenn Ihr Hierarchietabelle wäre so etwas wie dies eine verschachtelte Gruppe Ihre Anfrage wurde aussehen:
SELECT * FROM products
INNER JOIN categories ON categories.id = products.category_id
WHERE categories.lft > 2 and categories.rgt < 11
die 2 und 11 sind die linken und rechten bzw. des Processors
Rekord.
Andere Tipps
Sieht aus wie ein Job für ein Common Table Expression .. etwas entlang der Linien von:
with catCTE (catid, parentid)
as
(
select cat.catid, cat.catparentid from cat where cat.name = 'Processors'
UNION ALL
select cat.catid, cat.catparentid from cat inner join catCTE on cat.catparentid=catcte.catid
)
select distinct * from catCTE
Das sollte die Kategorie, deren Namen wählen ist ‚Prozessoren‘ und irgendetwas davon ist Abkömmlinge soll, dass die Produkte in einer IN-Klausel in der Lage zu verwenden zurück zu ziehen.
Ich habe ähnliche Dinge in der Vergangenheit getan, zuerst für das Kategorie-IDs abzufragen, dann für die Produkte „IN“ diesen Kategorien abfragt. die Kategorien immer ist die harte Bit, und Sie haben ein paar Optionen:
- Ist die Höhe der Verschachtelung von Kategorien bekannt ist, oder Sie können eine obere Schranke finden: Erstellen Sie eine schreckliche aussehende SELECT mit vielen beitritt. Dies ist schnell, aber hässlich, und Sie müssen eine Begrenzung auf den Ebenen der Hierarchie setzen.
- Wenn Sie eine relativ kleine Anzahl von insgesamt Kategorien haben, fragen sie alle (nur ids, Eltern), sammeln Sie die IDs von denen, die Ihnen wichtig sind, und machen Sie einen SELECT .... IN für die Produkte. Dies war die entsprechende Option für mich.
- Abfrage nach oben / unten in der Hierarchie eine Reihe von SELECTs verwenden. Einfach, aber relativ langsam.
- Ich glaube, für rekursive Abfragen eine gewisse Unterstützung aktuellere Versionen von SQL Server haben, haben sie aber selbst nicht verwendet.
Gespeicherte Prozeduren helfen können, wenn Sie wollen diese App-Seite nicht tun.
Was Sie finden wollen, ist die transitive Schließung der Kategorie „Eltern“ Beziehung. Ich nehme an, es gibt keine Begrenzung der Kategorie Hierarchietiefe, so dass Sie nicht eine einzelne SQL-Abfrage formulieren, die alle Kategorien findet. Was ich tun würde (in Pseudo-Code) ist dies:
categoriesSet = empty set
while new.size > 0:
new = select * from categories where parent in categoriesSet
categoriesSet = categoriesSet+new
So halten nur für Kinder auf die Abfrage, bis nicht mehr zu finden sind. Dies verhält sich auch in Bezug auf die Geschwindigkeit, wenn Sie eine degenerierte Hierarchie haben (sagen wir, 1000 Kategorien, die jeweils ein Kind von einem anderen), oder eine große Anzahl von insgesamt Kategorien. Im zweiten Fall können Sie immer mit temporären Tabellen arbeiten die Datenübertragung zwischen Ihrer App zu halten und der Datenbank klein ist.
Vielleicht so etwas wie:
select *
from products
where products.category_id IN
(select c2.category_id
from categories c1 inner join categories c2 on c1.category_id = c2.parent_id
where c1.category = 'Processors'
group by c2.category_id)
[EDIT] Wenn die Kategorie Tiefe größer als eine dieser würde Ihre innerste Abfrage bilden. Ich vermute, dass Sie eine gespeicherte Prozedur entwerfen könnten, die in der Tabelle nach unten bohren würden, bis die Ide von der inneren Abfrage zurückgegeben haben keine Kinder haben - wahrscheinlich besser ein Attribut zu haben, die eine Kategorie als Endknoten in der Hierarchie markiert - dann führen die äußere Abfrage auf diese IDs.
CREATE TABLE #categories (id INT NOT NULL, parentId INT, [name] NVARCHAR(100))
INSERT INTO #categories
SELECT 1, NULL, 'Computers'
UNION
SELECT 2, 1, 'Processors'
UNION
SELECT 3, 2, 'Intel'
UNION
SELECT 4, 2, 'AMD'
UNION
SELECT 5, 3, 'Pentium'
UNION
SELECT 6, 3, 'Core 2 Duo'
UNION
SELECT 7, 4, 'Athlon'
SELECT *
FROM #categories
DECLARE @id INT
SET @id = 2
; WITH r(id, parentid, [name]) AS (
SELECT id, parentid, [name]
FROM #categories c
WHERE id = @id
UNION ALL
SELECT c.id, c.parentid, c.[name]
FROM #categories c JOIN r ON c.parentid=r.id
)
SELECT *
FROM products
WHERE p.productd IN
(SELECT id
FROM r)
DROP TABLE #categories
Der letzte Teil des Beispiels funktioniert nicht wirklich, wenn Sie es gerade wie das hier läuft. Entfernen Sie einfach die Auswahl aus den Produkten und Ersatz mit einem einfachen SELECT * FROM r
Dies sollte rekursiv alle ‚Kind‘ Kategorien aus einer bestimmten Kategorie zu starten.
DECLARE @startingCatagoryId int
DECLARE @current int
SET @startingCatagoryId = 13813 -- or whatever the CatagoryId is for 'Processors'
CREATE TABLE #CatagoriesToFindChildrenFor
(CatagoryId int)
CREATE TABLE #CatagoryTree
(CatagoryId int)
INSERT INTO #CatagoriesToFindChildrenFor VALUES (@startingCatagoryId)
WHILE (SELECT count(*) FROM #CatagoriesToFindChildrenFor) > 0
BEGIN
SET @current = (SELECT TOP 1 * FROM #CatagoriesToFindChildrenFor)
INSERT INTO #CatagoriesToFindChildrenFor
SELECT ID FROM Catagory WHERE ParentCatagoryId = @current AND Deleted = 0
INSERT INTO #CatagoryTree VALUES (@current)
DELETE #CatagoriesToFindChildrenFor WHERE CatagoryId = @current
END
SELECT * FROM #CatagoryTree ORDER BY CatagoryId
DROP TABLE #CatagoriesToFindChildrenFor
DROP TABLE #CatagoryTree
Ich mag für hierarchische Daten eine Stapel temporäre Tabelle verwenden. hier ist ein grobes Beispiel -
-- create a categories table and fill it with 10 rows (with random parentIds)
CREATE TABLE Categories ( Id uniqueidentifier, ParentId uniqueidentifier )
GO
INSERT
INTO Categories
SELECT NEWID(),
NULL
GO
INSERT
INTO Categories
SELECT TOP(1)NEWID(),
Id
FROM Categories
ORDER BY Id
GO 9
DECLARE @lvl INT, -- holds onto the level as we move throught the hierarchy
@Id Uniqueidentifier -- the id of the current item in the stack
SET @lvl = 1
CREATE TABLE #stack (item UNIQUEIDENTIFIER, [lvl] INT)
-- we fill fill this table with the ids we want
CREATE TABLE #tmpCategories (Id UNIQUEIDENTIFIER)
-- for this example we’ll just select all the ids
-- if we want all the children of a specific parent we would include it’s id in
-- this where clause
INSERT INTO #stack SELECT Id, @lvl FROM Categories WHERE ParentId IS NULL
WHILE @lvl > 0
BEGIN -- begin 1
IF EXISTS ( SELECT * FROM #stack WHERE lvl = @lvl )
BEGIN -- begin 2
SELECT @Id = [item]
FROM #stack
WHERE lvl = @lvl
INSERT INTO #tmpCategories
SELECT @Id
DELETE FROM #stack
WHERE lvl = @lvl
AND item = @Id
INSERT INTO #stack
SELECT Id, @lvl + 1
FROM Categories
WHERE ParentId = @Id
IF @@ROWCOUNT > 0
BEGIN -- begin 3
SELECT @lvl = @lvl + 1
END -- end 3
END -- end 2
ELSE
SELECT @lvl = @lvl - 1
END -- end 1
DROP TABLE #stack
SELECT * FROM #tmpCategories
DROP TABLE #tmpCategories
DROP TABLE Categories
gibt es eine gute Erklärung hier Linktext
Meine Antwort auf eine andere Frage von vor ein paar Tagen hier gilt ... Rekursion in SQL
Es gibt einige Methoden in dem Buch, das ich verknüpft haben, die Ihre Situation gut abdecken sollte.