Для ясности: вы моделируете иерархические данные.Существует несколько способов хранения иерархических данных в СУБД.Два из них:
- Списки смежности (например, таблицы со ссылками)
- Материализованные пути (например,
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 |
+----------+-------------+-----------+-----------+-----------+-----------+-----------+