Многотабличный иерархический запрос - PullRequest
1 голос
/ 24 апреля 2019

Я пытаюсь создать иерархический запрос в 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, у которого есть родительский идентификатор в таблице org4, у которого есть родительский идентификатор в таблице org3.

Родитель может не иметь никакихдети.

Скажем, у меня есть следующие значения в таблицах:

Org3 table

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

Org4 table

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

2 голосов
/ 24 апреля 2019

Это возможное решение, но я не так, как оно будет работать.По сути, это добавление пустой строки ко всем уровням.

        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 голос
/ 24 апреля 2019

Для ясности: вы моделируете иерархические данные.Существует несколько способов хранения иерархических данных в СУБД.Два из них:

  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 (с использованием CASE и COALESCE):

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