多表层次结构查询

时间:2019-04-24 18:41:49

标签: sql sql-server hierarchical-data

我正在尝试在SQL Server中创建分层查询。

我有4张桌子:

  • org3(org3_id,org3_name)
  • org4(org4_id,org4_name,org4_org3id)
  • org5(org5_id,org5_name,org5_org4id)
  • org6(org6_id,org6_name,org6_org5id)

Org3是最高级别,而org 6是最低级别。如您所见,org6在org5表中具有一个父ID,在org4表中具有一个父ID,在org3表中具有一个父ID。

父母可能没有孩子。

所以说我在表中有这些值:

Org3

| org3_id | org3_name |
+---------+-----------+
| 1       | MS        |
| 2       | NS        |

Org4

| org4_id | org4_name | org4_org3id |
+---------+-----------+-------------+
| 1       | TS        | 1           |
| 2       | QS        | 1           |
| 3       | BS        | 1           |

Org5

| org5_id | org5_name | org5_org4id |
+---------+-----------+-------------+
| 1       | LS        | 1           |
| 2       | PS        | 1           |
| 3       | VS        | 2           |

Org6

| org6_id | org6_name | org6_org5id |
+---------+-----------+-------------+
| 1       | AS        | 1           |
| 2       | RS        | 1           |
| 3       | ZS        | 2           |

我想要得到的结果是:

| org3_id | org3_name | org4_id | org4_name | org5_id | org5_name | org6_id | org6_name | path        |
|---------|-----------|---------|-----------|---------|-----------|---------|-----------|-------------|
| 1       | MS        | NULL    | NULL      | NULL    | NULL      | NULL    | NULL      | MS          |
| 1       | MS        | 1       | TS        | NULL    | NULL      | NULL    | NULL      | MS\TS       |
| 1       | MS        | 1       | TS        | 1       | LS        | NULL    | NULL      | MS\TS\LS    |
| 1       | MS        | 1       | TS        | 1       | LS        | 1       | AS        | MS\TS\LS\AS |
| 1       | MS        | 1       | TS        | 1       | LS        | 2       | RS        | MS\TS\LS\RS |
| 1       | MS        | 1       | TS        | 2       | PS        | NULL    | NULL      | MS\TS\PS    |
| 1       | MS        | 1       | TS        | 2       | PS        | 3       | ZS        | MS\TS\PS\ZS |
| 1       | MS        | 2       | QS        | NULL    | NULL      | NULL    | NULL      | MS\QS       |
| 1       | MS        | 2       | QS        | 3       | VS        | NULL    | NULL      | MS\QS\VS    |
| 1       | MS        | 3       | BS        | NULL    | NULL      | NULL    | NULL      | MS\BS       |
| 2       | NS        | NULL    | NULL      | NULL    | NULL      | NULL    | NULL      | NS          |

这是我尝试过的。

SELECT 
    org3.org3_id,
    org3.org3_name,
    org3.org3_open_ind,
    org4.org4_id,
    org4.org4_name,
    org4.org4_open_ind,
    org5.org5_id,
    org5.org5_name,
    org5.org5_open_ind,
    org6.org6_id,
    org6.org6_name,
    org6.org6_open_ind,
    CONCAT(org3.org3_abbrv, '\', org4.org4_abbrv, 
           CASE
              WHEN org5.org5_abbrv IS NULL THEN ''
              ELSE CONCAT('\', org5.org5_abbrv)
           END, 
           CASE
              WHEN org6.org6_abbrv IS NULL THEN ''
              ELSE CONCAT('\', org6.org6_abbrv)
           END) AS [ORG PATH]                       
FROM 
    (SELECT
         *
     FROM
         TSTAFFORG3 
     WHERE
         org3_open_ind = 1) org3
LEFT OUTER JOIN 
    (SELECT
         *
     FROM
         TSTAFFORG4 
     WHERE
         org4_open_ind = 1) org4 ON org4.org4_org3id = org3.org3_id
LEFT OUTER JOIN 
    (SELECT
         *
     FROM
         TSTAFFORG5
     WHERE
         org5_open_ind = 1) org5 ON org5.org5_org4id = org4.org4_id
LEFT OUTER JOIN 
    (SELECT
         *
     FROM 
         TSTAFFORG6
     WHERE
         org6_open_ind = 1) org6 ON org6.org6_org5id = org5.org5_id
ORDER BY
    org3.org3_name, org4.org4_name, org5.org5_name, org6.org6_name

我认为也许需要CTE查询,但是在这种情况下我不确定如何对其进行构架。如果所有表都在一个表中,我想我可以弄清楚,但是由于它是多个表,所以我很难弄清SQL。我尝试的查询不只显示父级。它只会显示org3有子级的结果。

2 个答案:

答案 0 :(得分:2)

这是一个可能的解决方案,但我不是它会如何执行。基本上是在所有级别上添加一个空行。

        SELECT 
                org3.org3_id,
                org3.org3_name,
                org3.org3_open_ind,
                org4.org4_id,
                org4.org4_name,
                org4.org4_open_ind,
                org5.org5_id,
                org5.org5_name,
                org5.org5_open_ind,
                org6.org6_id,
                org6.org6_name,
                org6.org6_open_ind,
                CONCAT
                    (
                        org3.org3_name, 
                        '\' + org4.org4_name, 
                        '\' + org5.org5_name, 
                        '\' + org6.org6_name
                    )   AS [ORG PATH]                       
        FROM TSTAFFORG3 org3
        CROSS APPLY 
                (
                    SELECT  org4_id, org4_name, org4_open_ind, org4_org3id
                    FROM    TSTAFFORG4 
                    WHERE   org4_open_ind = 1
                    AND     org4_org3id = org3.org3_id
                    UNION ALL
                    SELECT NULL, NULL, NULL, org3.org3_id
                ) org4 
                CROSS APPLY
                (
                    SELECT  org5_id, org5_name, org5_open_ind, org5_org4id
                    FROM    TSTAFFORG5
                    WHERE   org5_open_ind = 1
                    AND     org5_org4id = org4.org4_id
                    UNION ALL
                    SELECT NULL, NULL, NULL, org4.org4_id
                ) org5 
                OUTER APPLY
                (
                    SELECT  org6_id, org6_name, org6_open_ind, org6_org5id
                    FROM    TSTAFFORG6
                    WHERE   org6_open_ind = 1
                    AND     org6_org5id = org5.org5_id
                    UNION ALL
                    SELECT NULL, NULL, NULL, org4.org4_id
                ) org6 
                    WHERE
                            org3_open_ind = 1
        ORDER BY
                org3.org3_name, org4.org4_name, org5.org5_name, org6.org6_name;

如果其他人有不同的想法,我将样本数据保留为可消耗格式。

CREATE TABLE TSTAFFORG3( 
 org3_id    int,
 org3_name  varchar(10),
 org3_open_ind  bit);

 INSERT INTO TSTAFFORG3 
 VALUES
    ( 1, 'MS', 1),
    ( 2, 'NS', 1);

CREATE TABLE TSTAFFORG4( 
 org4_id    int,
 org4_name  varchar(10),
 org4_org3id    int,
 org4_open_ind  bit);

 INSERT INTO TSTAFFORG4
 VALUES
    ( 1, 'TS', 1, 1),
    ( 2, 'QS', 1, 1),
    ( 3, 'BS', 1, 1);

CREATE TABLE TSTAFFORG5( 
 org5_id    int,
 org5_name  varchar(10),
 org5_org4id    int,
 org5_open_ind  bit);

 INSERT INTO TSTAFFORG5
 VALUES
    ( 1, 'LS', 1, 1),
    ( 2, 'PS', 1, 1),
    ( 3, 'VS', 2, 1);

CREATE TABLE TSTAFFORG6( 
 org6_id    int,
 org6_name  varchar(10),
 org6_org5id    int,
 org6_open_ind  bit);

 INSERT INTO TSTAFFORG6
 VALUES
    ( 1, 'AS', 1, 1),
    ( 2, 'RS', 1, 1),
    ( 3, 'ZS', 2, 1);

答案 1 :(得分:1)

要清楚:您正在对分层数据建模。在RDBMS中有多种存储分层数据的方法。两个是:

  1. 邻接表(例如,自引用表)
  2. 材料化路径(例如TS/MS/RS)。

您的数据模型似乎有问题:如果要添加另一个级别,是否要添加新表?

如果可以的话,应将所有内容移到一张表中:

orgs(org_id, org_name, parent_org)

这使用邻接列表方法。

然后,您可以创建一个简单的递归CTE(或多个自联接)来获得具体的路径。

避开组织ID(如果将所有内容放在一张表中则必须重新生成),以下查询将为您提供以下结果:

WITH 
    -- sample data (I'm only using org names, not org IDs).
    Orgs(org_name, parent_org) AS
    (
        SELECT * FROM
        (
            VALUES
            ('MS', NULL),
            ('NS', NULL),
            ('TS', 'MS'),
            ('QS', 'MS'),
            ('BS', 'MS'),
            ('LS', 'TS'),
            ('PS', 'TS'),
            ('VS', 'QS'),
            ('AS', 'LS'),
            ('RS', 'LS'),
            ('ZS', 'PS')
        ) v(c1, c2)
    ),
    -- hierarchical/recursive CTE
    OrgsWithPath(org_name, parent_org, org_path) AS
    (
        SELECT org_name, parent_org, CAST(org_name AS VARCHAR(MAX))
        FROM Orgs
        WHERE parent_org IS NULL

        UNION ALL

        SELECT Orgs.org_name, Orgs.parent_org, OrgsWithPath.org_path + '\' + Orgs.org_name
        FROM OrgsWithPath
            INNER JOIN Orgs ON
                Orgs.parent_org = OrgsWithPath.org_name
    )

SELECT * FROM OrgsWithPath ORDER BY org_path
+----------+------------+-------------+
| org_name | parent_org |  org_path   |
+----------+------------+-------------+
| MS       | NULL       | MS          |
| BS       | MS         | MS\BS       |
| QS       | MS         | MS\QS       |
| VS       | QS         | MS\QS\VS    |
| TS       | MS         | MS\TS       |
| LS       | TS         | MS\TS\LS    |
| AS       | LS         | MS\TS\LS\AS |
| RS       | LS         | MS\TS\LS\RS |
| PS       | TS         | MS\TS\PS    |
| ZS       | PS         | MS\TS\PS\ZS |
| NS       | NULL       | NS          |
+----------+------------+-------------+

请注意最后一个ORDER BY中的SELECT:这确定您的查询是深度优先(遍历完整路径)还是广度优先(从所有顶级节点开始,然后进行)。通过这种方法,包含一个“级别”也很容易,因此您知道它是顶级节点还是其他级别。

获取其他列比较麻烦,但也可以由递归CTE处理(使用CASECOALESCE):

WITH 
    OrgsWithPath(org_name, org_path, org_level, org3_name, org4_name, org5_name, org6_name) AS
    (
        SELECT 
            Orgs.org_name, 
            CAST(org_name AS VARCHAR(MAX)), 
            1,
            Orgs.org_name, 
            CAST(NULL AS VARCHAR(255)), 
            CAST(NULL AS VARCHAR(255)), 
            CAST(NULL AS VARCHAR(255))
        FROM Orgs
        WHERE parent_org IS NULL

        UNION ALL

        SELECT 
            Orgs.org_name,
            OrgsWithPath.org_path + '\' + Orgs.org_name,
            OrgsWithPath.org_level + 1,
            OrgsWithPath.org3_name,
            CASE WHEN OrgsWithPath.org_level+1 >= 2 THEN COALESCE(OrgsWithPath.org4_name, Orgs.org_name) END,
            CASE WHEN OrgsWithPath.org_level+1 >= 3 THEN COALESCE(OrgsWithPath.org5_name, Orgs.org_name) END,
            CASE WHEN OrgsWithPath.org_level+1 >= 4 THEN COALESCE(OrgsWithPath.org6_name, Orgs.org_name) END
        FROM OrgsWithPath
            INNER JOIN Orgs ON
                Orgs.parent_org = OrgsWithPath.org_name
    )

SELECT *
FROM OrgsWithPath 
ORDER BY org_path
+----------+-------------+-----------+-----------+-----------+-----------+-----------+
| org_name |  org_path   | org_level | org3_name | org4_name | org5_name | org6_name |
+----------+-------------+-----------+-----------+-----------+-----------+-----------+
| MS       | MS          |         1 | MS        | NULL      | NULL      | NULL      |
| BS       | MS\BS       |         2 | MS        | BS        | NULL      | NULL      |
| QS       | MS\QS       |         2 | MS        | QS        | NULL      | NULL      |
| VS       | MS\QS\VS    |         3 | MS        | QS        | VS        | NULL      |
| TS       | MS\TS       |         2 | MS        | TS        | NULL      | NULL      |
| LS       | MS\TS\LS    |         3 | MS        | TS        | LS        | NULL      |
| AS       | MS\TS\LS\AS |         4 | MS        | TS        | LS        | AS        |
| RS       | MS\TS\LS\RS |         4 | MS        | TS        | LS        | RS        |
| PS       | MS\TS\PS    |         3 | MS        | TS        | PS        | NULL      |
| ZS       | MS\TS\PS\ZS |         4 | MS        | TS        | PS        | ZS        |
| NS       | NS          |         1 | NS        | NULL      | NULL      | NULL      |
+----------+-------------+-----------+-----------+-----------+-----------+-----------+