Внутреннее соединение переменное количество раз для расширения дочерних/родительских отношений различной длины

У меня есть таблица, которая объединяет команды, отделы и подразделения.

Идентификатор команды Родительский_идентификатор Название команды Уровень 1 4 Великобритания 2 2 4 Франция 2 3 5 Япония 2 4 6 Европа 1 5 6 Азия 1 6 НУЛЕВОЙ Глобальный 0

Я хотел бы иметь возможность «растянуть» эту таблицу в более широкое представление, как показано ниже;

Идентификатор команды L0 Название команды L0 Идентификатор команды L1 Название команды L1 Идентификатор команды L2 Название команды L2 6 Глобальный 4 Европа 1 Великобритания 6 Глобальный 4 Европа 2 Франция 6 Глобальный 5 Азия 3 Япония

Хотя это просто делается с использованием повторяющихся внутренних соединений, когда я знаю, сколько существует уровней, я хотел бы придумать что-то более общее, чтобы запрос не нуждался в переписывании в случае, если в структуру были добавлены дополнительные уровни.

Если добавлены дополнительные уровни (давайте ограничимся 10 уровнями), то в таблице результатов будут все столбцы от идентификатора команды L0, идентификатора команды L2 до идентификатора команды L10.

например если бы мы добавили слой Лондона ниже Великобритании (уровень 3)

Идентификатор команды Родительский_идентификатор Название команды Уровень 1 4 Великобритания 2 2 4 Франция 2 3 5 Япония 2 4 6 Европа 1 5 6 Азия 1 6 НУЛЕВОЙ Глобальный 0 7 1 Лондон 3

Результатом станет;

Идентификатор команды L0 Название команды L0 Идентификатор команды L1 Название команды L1 Идентификатор команды L2 Название команды L2 Идентификатор команды L3 Название команды L3 1 Великобритания 4 Европа 6 Глобальный 7 Лондон 2 Франция 4 Европа 6 Глобальный НУЛЕВОЙ НУЛЕВОЙ 3 Япония 5 Азия 6 Глобальный НУЛЕВОЙ НУЛЕВОЙ

На сегодняшний день я хожу по кругу, пытаясь использовать функцию поворота, которая кажется правильной, но я просто не могу сделать это правильно. Точно так же я изучил рекурсивные CTE, но, похоже, это дает мне только узкие таблицы вместо широкой, к которой я стремлюсь.

А что будет, если будет уровень 3, или 4, или 17?

Thom A 07.06.2024 16:37

Посмотрите рекурсивный CTE.

Brad 07.06.2024 16:56

Я отредактировал вопрос, чтобы включить ожидаемый результат, если будет добавлен дополнительный уровень @ThomA.. Для каждого нового уровня в выходную таблицу будут добавлены еще два столбца. Дайте мне знать, если это прояснит вопрос или потребуется дополнительная информация.

Data_Mauler_2024 07.06.2024 16:57

Тогда вам понадобится динамический SQL @Data_Mauler_2024. Честно говоря, я бы предположил, что это проблема вашего уровня представления. В противном случае вы можете адаптировать решение здесь, но это будет не "весело": Группировать по столбцам и несколько строк в одну строку, несколько столбцов

Thom A 07.06.2024 16:58

SQL Server является декларативным по своей конструкции. Без известного или максимального количества уровней вам понадобится Dynamic SQL.

John Cappelletti 07.06.2024 16:58

Не придется ли вам менять другие части кода, которые, например, ожидают 3 столбца, а вы выдаете 17?

Salman Arshad 07.06.2024 19:55
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
0
6
52
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Как отмечали другие в комментариях, вам нужно будет использовать динамический SQL. Это справедливо в любое время, когда количество выходных столбцов зависит от входных данных и не известно заранее.

Общая техника заключается в следующем:

  1. Определите повторяющиеся части запроса. В вашем случае вам нужен динамический SQL для вашего списка выбора и для ваших объединений.
  2. Создавайте эти повторяющиеся фрагменты SQL с помощью запросов, которые адаптируют шаблонный фрагмент SQL со значениями для каждого вхождения и объединяют эти повторяющиеся фрагменты в одну строку.
  3. Объедините эти фрагменты SQL с фиксированным SQL.
  4. Выполните объединенный динамический SQL, используя либо EXEC (@Sql), либо EXEC sp_executesql @Sql, '<parameter definitions>', <parameter-list>. (Последнее необходимо, если вам нужно передать один или несколько параметров в динамический SQL или из него.)

Предполагая, что команда высшего уровня всегда имеет нулевой уровень и что в номерах уровней нет пробелов, следующее должно дать вам желаемые результаты.

DECLARE @SelectItems NVARCHAR(MAX) = (
    SELECT STRING_AGG(CA.Item, ',') WITHIN GROUP(ORDER BY Level)
    FROM (SELECT DISTINCT T.Level FROM Teams T) L
    CROSS APPLY (
        SELECT CONCAT(
            CHAR(13), SPACE(8),
            'L', L.Level, '.[Team Id] AS [Team Id L', L.Level, '], ',
            'L', L.Level, '.[Team Name] AS [Team Name L', L.Level, ']')
            AS Item 
    ) CA
)

DECLARE @JoinItems NVARCHAR(MAX) = (
    SELECT STRING_AGG(CA.Item, '') WITHIN GROUP(ORDER BY Level)
    FROM (SELECT DISTINCT T.Level FROM Teams T WHERE T.Level > 0) L
    CROSS APPLY (
        SELECT CONCAT(
            CHAR(13), SPACE(4),
            'LEFT JOIN Teams L', L.Level,
            ' ON L', L.Level, '.Parent_Id = L', L.Level - 1, '.[Team Id]')
            AS Item 
    ) CA
)

DECLARE @Sql NVARCHAR(MAX) = '
    SELECT' + @SelectItems + '
    FROM Teams L0' + @JoinItems + '
    WHERE L0.Level = 0
'

--PRINT @SelectItems
--PRINT @JoinItems
PRINT @Sql

EXEC (@Sql)

(Новые строки CHAR(13) и отступы SPACE(8) присутствуют в печатном SQL только для эстетических целей.)

В приведенном выше примере используется функция STRING_AGG(), доступная в SQL Server 2017 и более поздних версиях. Для более ранних версий вы можете использовать метод, использующий комбинацию FOR XML PATH(''), TYPE, .value(...) и STUFF() для получения эквивалентных результатов.

ВАЖНО: При использовании динамического SQL вы всегда должны быть осторожны с возможностью SQL-инъекции, при которой ненадежный текст включается в динамический SQL небезопасным образом. Всякий раз, когда строковые данные из источника используются в качестве имени таблицы или имени столбца или его части, вы должны использовать QUOTENAME(name) для безопасного ввода этих имен. При внедрении строковых литералов используйте вариант QUOTENAME(value, '''').

В приведенном выше примере использования мы вводим только целочисленные значения, поэтому кавычки не требуются.

Сгенерированный SQL:

    SELECT
        L0.[Team Id] AS [Team Id L0], L0.[Team Name] AS [Team Name L0],
        L1.[Team Id] AS [Team Id L1], L1.[Team Name] AS [Team Name L1],
        L2.[Team Id] AS [Team Id L2], L2.[Team Name] AS [Team Name L2],
        L3.[Team Id] AS [Team Id L3], L3.[Team Name] AS [Team Name L3]
    FROM Teams L0
    LEFT JOIN Teams L1 ON L1.Parent_Id = L0.[Team Id]
    LEFT JOIN Teams L2 ON L2.Parent_Id = L1.[Team Id]
    LEFT JOIN Teams L3 ON L3.Parent_Id = L2.[Team Id]
    WHERE L0.Level = 0

Полученные результаты:

Идентификатор команды L0 Название команды L0 Идентификатор команды L1 Название команды L1 Идентификатор команды L2 Название команды L2 Идентификатор команды L3 Название команды L3 6 Глобальный 4 Европа 1 Великобритания 7 Лондон 6 Глобальный 4 Европа 2 Франция нулевой нулевой 6 Глобальный 5 Азия 3 Япония нулевой нулевой

См. эту db<>fiddle для демонстрации, которая включает в себя как STRING_AGG(), так и FOR XML версии кода.

Большое спасибо. Результат соответствует тому, что мне нужно, и объяснение было подробным и ясным. Теперь я гораздо лучше понимаю, где я ошибался!

Data_Mauler_2024 07.06.2024 22:10

Другие вопросы по теме