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?

War es hilfreich?

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.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top