【问题标题】:SQL "tree-like" query - most parent groupSQL“树状”查询 - 大多数父组
【发布时间】:2009-07-16 07:51:35
【问题描述】:

我在 SQL 中执行“树状”查询(我们称之为什么?)时遇到了一些麻烦。

看看我下面的图表(表和列名是丹麦语 - 对此感到抱歉):

DB diagram http://img197.imageshack.us/img197/8721/44060572.jpg 使用 MSSQL Server 2005,目标是为每个客户 (Kunde) 找到最父组 (Gruppe)。

每个组可以有许多父组和许多子组。

而且,我还想知道如何像这样显示树:

客户 1 - 家长组 1 - 儿童组 1 - ChildChild 组 n - 儿童组 n - 家长组 n - ... - ... 客户 n - ...

另一个问题:

查询如何获取所有客户的所有组?父子组。

【问题讨论】:

标签: sql tree


【解决方案1】:

您可以使用 CTE 即时构建“完整路径”列

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

如果您有大量读取而不是少量修改,则性能可能不是最佳。

【讨论】:

    【解决方案2】:

    我不能说比乔·塞尔科更好。问题通常是构建的模型不适合构建层次结构,并且这些模型必须考虑层次结构的特征。是不是太深了?是不是太宽了?是不是又窄又浅?

    在宽树和浅树上成功的一个关键是将层次结构中的完整路径放在一列中,就像 Celko 在第一个链接中提到的那样。

    【讨论】:

    • 我已经看过那些文章了。我不明白如何将它应用到我的架构中。
    • 你可能不能说得比 Celko 更好,但你肯定说得更好:)
    • 我的意思是,如果不修改您的架构,您将很难获得良好的性能。
    • 你会在架构中改变什么?
    • 这取决于实际使用和扩展,但我会将完整路径列添加到 kunde,这样您就可以免费获得顶级组,但代价是必须更新/生成完整路径更新/插入
    【解决方案3】:

    我想出了一个解决方案,可以解决为每个客户列出所有组的问题。父子组。

    你怎么看?

    与组树 作为 ( 选择 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) 或不存在(SELECT * FROM ActiveDirectory.Gruppe_Gruppe WHERE ParentGruppeId = g.Id)) 联合所有 选择 GroupTree.KundeId,gg.ChildGruppeId FROM ActiveDirectory.Gruppe_Gruppe gg 内部加入 GroupTree ON gg.ParentGruppeId = GroupTree.GruppeId ) 选择 KundeId,GruppeId 从组树 选项(MAXRECURSION 32767)

    【讨论】:

      【解决方案4】:

      这样的事情怎么样:

      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)
      

      【讨论】:

        【解决方案5】:

        在 T-SQL 中,您可以编写一个 while 循环。未经测试:

        @group = <starting group>
        WHILE (EXISTS(SELECT * FROM Gruppe_Gruppe WHERE ChildGruppeId=@group))
        BEGIN
          SELECT @group=ParentGruppeId FROM Gruppe_Gruppe WHERE ChildGruppeId=@group
        END
        

        【讨论】:

        • 您的意思是,如果@group 不再有父组?没有进入循环,结果是@group,我明白是你想要的。
        • 啊,是的。我的错。那么,您将如何为每个客户找到最多的父组?可以有多个。
        【解决方案6】:

        我们使用 SQL Server 2000,在 SQL 联机丛书中有一个使用堆栈扩展层次结构的示例,我为我们的 ERP 系统编写了许多变体

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

        我知道在 SQL 2005 中有一个使用 CTE 的 Native 方法,但我自己没有使用它

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-12-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多