2009-07-16 14 views
5

Mam problem z wykonaniem kwerendy "drzewa" (jak to nazywamy?) W SQL.Zapytanie SQL "jak w drzewie" - większość grupy nadrzędnej

Spójrz na moim schemacie poniżej (nazwy tabel i kolumn są w języku duńskim - przepraszam):

DB diagram http://img197.imageshack.us/img197/8721/44060572.jpg Korzystanie MSSQL Server 2005, celem jest znalezienie najbardziej dominującą grupę (Gruppe) , dla każdego klienta (Kunde).

Każda grupa może mieć wiele grup nadrzędnych i wiele grup podrzędnych.

I Chciałbym również wiedzieć, jak wyświetlić drzewo takiego:

 
Customer 1 
    - Parent group 1 
     - Child group 1 
     - ChildChild group n 
     - Child group n 
    - Parent group n 
     - ... 
     - ... 
Customer n 
    - ... 

Kolejne pytanie:

Jak wygląda kwerendy, aby uzyskać wszystkie grupy dla wszystkich klienci? Grupy rodziców i dzieci.

+1

wierzę wspólną nazwę dla tego typu danych jest „hierarchiczne” i chcesz „top przodka”. Możesz użyć zapytań rekursywnych (zobacz http://www.codeproject.com/KB/architecture/RoleBasedSecurity.aspx), aby to zrobić. – Blixt

Odpowiedz

4

Po prostu nie mogę tego powiedzieć lepiej niż Joe Celko. Problem polega zwykle na tym, że zbudowane modele nie nadają się dobrze do budowania hierarchii i że modele te muszą uwzględniać charakterystykę twojej hierarchii. Czy to jest zbyt głębokie? Czy jest zbyt szeroki? Czy jest wąski i płytki?

Kluczem do sukcesu na szerokich i płytkich drzewach jest posiadanie pełnej ścieżki w hierarchii w kolumnie, jak wspomina Celko w pierwszym linku.

+0

Już przeglądałem te artykuły. Czego nie dostaję, to jak mogę zastosować to do mojego schematu. –

+0

Możesz nie być w stanie powiedzieć tego lepiej niż Celko, ale na pewno powiedziałeś to ładniej :) –

+0

Mówię, że bez modyfikacji twojego schematu, będziesz miał trudności z uzyskaniem dobrej wydajności. –

1

W T-SQL, można napisać pętli while. Niesprawdzone:

@group = <starting group> 
WHILE (EXISTS(SELECT * FROM Gruppe_Gruppe WHERE [email protected])) 
BEGIN 
    SELECT @group=ParentGruppeId FROM Gruppe_Gruppe WHERE [email protected] 
END 
+0

Czy to działałoby? ? Co jeśli grupa @ jest rodzicem? –

+0

Masz na myśli, jeśli @group nie ma już grupy nadrzędnej? Pętla nie jest wprowadzana, a wynikiem jest @group, co rozumiem, czego chcesz. –

+0

Ah tak. Mój błąd. Jak znaleźć najbardziej odpowiednią grupę dla każdego klienta? Może być ich więcej. –

1

Używamy SQL Server 2000 i jest przykładem rozszerzania hierarchie wykorzystaniem stosu w SQL Books Online, napisałem kilka wariantów dla naszego systemu ERP

http://support.microsoft.com/kb/248915

wnoszę, że nie jest to metoda naturalnej przy użyciu CTE ciągu SQL 2005, ale nie zostały wykorzystane sobie

5

CTE można używać do skonstruowania „pełna ścieżka” kolumny w locie

--DROP TABLE Gruppe, Kunde, Gruppe_Gruppe, Kunde_Gruppe 
CREATE TABLE Gruppe (
    Id     INT PRIMARY KEY 
    , Name    VARCHAR(100) 
    ) 
CREATE TABLE Kunde (
    Id     INT PRIMARY KEY 
    , Name    VARCHAR(100) 
    ) 
CREATE TABLE Gruppe_Gruppe (
    ParentGruppeId  INT 
    , ChildGruppeId  INT 
    ) 
CREATE TABLE Kunde_Gruppe (
    KundeId    INT 
    , GruppeId   INT 
    ) 

INSERT  Gruppe 
VALUES  (1, 'Group 1'), (2, 'Group 2'), (3, 'Group 3') 
      , (4, 'Sub-group A'), (5, 'Sub-group B'), (6, 'Sub-group C'), (7, 'Sub-group D') 

INSERT  Kunde 
VALUES  (1, 'Kunde 1'), (2, 'Kunde 2'), (3, 'Kunde 3') 

INSERT  Gruppe_Gruppe 
VALUES  (1, 4), (1, 5), (1, 7) 
      , (2, 6), (2, 7) 
      , (6, 1) 

INSERT  Kunde_Gruppe 
VALUES  (1, 1), (1, 2) 
      , (2, 3), (2, 4) 

;WITH  CTE 
AS   (
      SELECT  CONVERT(VARCHAR(1000), REPLACE(CONVERT(CHAR(5), k.Id), ' ', 'K')) AS TheKey 
         , k.Name  AS Name 
      FROM  Kunde k 

      UNION ALL 

      SELECT  CONVERT(VARCHAR(1000), REPLACE(CONVERT(CHAR(5), x.KundeId), ' ', 'K') 
          + REPLACE(CONVERT(CHAR(5), g.Id), ' ', 'G')) AS TheKey 
         , g.Name 
      FROM  Gruppe g 
      JOIN  Kunde_Gruppe x 
      ON   g.Id = x.GruppeId 

      UNION ALL 

      SELECT  CONVERT(VARCHAR(1000), p.TheKey + REPLACE(CONVERT(CHAR(5), g.Id), ' ', 'G')) AS TheKey 
         , g.Name 
      FROM  Gruppe g 
      JOIN  Gruppe_Gruppe x 
      ON   g.Id = x.ChildGruppeId 
      JOIN  CTE p 
      ON   REPLACE(CONVERT(CHAR(5), x.ParentGruppeId), ' ', 'G') = RIGHT(p.TheKey, 5) 
      WHERE  LEN(p.TheKey) < 32 * 5 
      ) 
SELECT  * 
      , LEN(TheKey)/5 AS Level 
FROM  CTE c 
ORDER BY c.TheKey 

Wydajność może być nieoptymalna, jeśli masz dużo odczytów a rzadkie modyfikacje.

3

Wymyśliłem rozwiązanie, które rozwiązuje problem z wyświetlaniem WSZYSTKICH grup dla każdego klienta. Grupy rodziców i dzieci.

Co myślisz?

 
WITH GroupTree 
AS 
(
    SELECT kg.KundeId, g.Id GruppeId 
    FROM ActiveDirectory.Gruppe g 
    INNER JOIN ActiveDirectory.Kunde_Gruppe kg ON g.Id = kg.GruppeId 
    AND (EXISTS (SELECT * FROM ActiveDirectory.Gruppe_Gruppe WHERE ParentGruppeId = g.Id) 
    OR NOT EXISTS (SELECT * FROM ActiveDirectory.Gruppe_Gruppe WHERE ParentGruppeId = g.Id)) 

    UNION ALL 

    SELECT GroupTree.KundeId, gg.ChildGruppeId 
    FROM ActiveDirectory.Gruppe_Gruppe gg 
    INNER JOIN GroupTree ON gg.ParentGruppeId = GroupTree.GruppeId 
) 
SELECT KundeId, GruppeId 
FROM GroupTree 

OPTION (MAXRECURSION 32767) 
2

Jak o czymś takim:

DECLARE @Customer TABLE(
     CustomerID INT IDENTITY(1,1), 
     CustomerName VARCHAR(MAX) 
) 

INSERT INTO @Customer SELECT 'Customer1' 
INSERT INTO @Customer SELECT 'Customer2' 
INSERT INTO @Customer SELECT 'Customer3' 

DECLARE @CustomerTreeStructure TABLE(
     CustomerID INT, 
     TreeItemID INT 
) 

INSERT INTO @CustomerTreeStructure (CustomerID,TreeItemID) SELECT 1, 1 
INSERT INTO @CustomerTreeStructure (CustomerID,TreeItemID) SELECT 2, 12 
INSERT INTO @CustomerTreeStructure (CustomerID,TreeItemID) SELECT 3, 1 
INSERT INTO @CustomerTreeStructure (CustomerID,TreeItemID) SELECT 3, 12 

DECLARE @TreeStructure TABLE(
     TreeItemID INT IDENTITY(1,1), 
     TreeItemName VARCHAR(MAX), 
     TreeParentID INT 
) 

INSERT INTO @TreeStructure (TreeItemName,TreeParentID) SELECT '001', NULL 
INSERT INTO @TreeStructure (TreeItemName,TreeParentID) SELECT '001.001', 1 
INSERT INTO @TreeStructure (TreeItemName,TreeParentID) SELECT '001.001.001', 2 
INSERT INTO @TreeStructure (TreeItemName,TreeParentID) SELECT '001.001.002', 2 
INSERT INTO @TreeStructure (TreeItemName,TreeParentID) SELECT '001.001.003', 2 
INSERT INTO @TreeStructure (TreeItemName,TreeParentID) SELECT '001.002', 1 
INSERT INTO @TreeStructure (TreeItemName,TreeParentID) SELECT '001.003', 1 
INSERT INTO @TreeStructure (TreeItemName,TreeParentID) SELECT '001.003.001', 7 
INSERT INTO @TreeStructure (TreeItemName,TreeParentID) SELECT '001.001.002.001', 4 
INSERT INTO @TreeStructure (TreeItemName,TreeParentID) SELECT '001.001.002.002', 4 
INSERT INTO @TreeStructure (TreeItemName,TreeParentID) SELECT '001.001.002.003', 4 

INSERT INTO @TreeStructure (TreeItemName,TreeParentID) SELECT '002', NULL 
INSERT INTO @TreeStructure (TreeItemName,TreeParentID) SELECT '002.001', 12 
INSERT INTO @TreeStructure (TreeItemName,TreeParentID) SELECT '002.001.001', 13 
INSERT INTO @TreeStructure (TreeItemName,TreeParentID) SELECT '002.001.002', 13 

;WITH Structure AS (
    SELECT TreeItemID, 
      TreeItemName, 
      TreeParentID, 
      REPLICATE('0',5 - LEN(CAST(TreeItemID AS VARCHAR(MAX)))) + CAST(TreeItemID AS VARCHAR(MAX)) + '\\' TreePath 
    FROM @TreeStructure ts 
    WHERE ts.TreeParentID IS NULL 
    UNION ALL 
    SELECT ts.*, 
      s.TreePath + REPLICATE('0',5 - LEN(CAST(ts.TreeItemID AS VARCHAR(5)))) + CAST(ts.TreeItemID AS VARCHAR(5)) + '\\' TreePath 
    FROM @TreeStructure ts INNER JOIN 
      Structure s ON ts.TreeParentID = s.TreeItemID 
) 

SELECT c.CustomerName, 
     Children.TreeItemName, 
     Children.TreePath 
FROM @Customer c INNER JOIN 
     @CustomerTreeStructure cts ON c.CustomerID = cts.CustomerID INNER JOIN 
     Structure s ON cts.TreeItemID = s.TreeItemID INNER JOIN 
     (
      SELECT * 
      FROM Structure 
     ) Children ON Children.TreePath LIKE s.TreePath +'%' 
ORDER BY 1,3 
OPTION (MAXRECURSION 0)