распространение родительской иерархии в SQL / DAX - PullRequest
0 голосов
/ 23 февраля 2019

Предположим, у меня есть таблица, которая описывает первичные и вторичные строки отчетности для каждого сотрудника.Давайте представим организационную структуру, в которой генеральный директор, сотрудник 0, имеет в своем подчинении 2 менеджера (1 и 2).

Менеджер 2 имеет 2 сотрудника в своей команде (34), однако сотрудник 4 фактически работает в часовом поясе диспетчера 1, поэтому, хотя он имеет 2 в качестве основного отчета, он также сообщает диспетчеру 1 в качестве дополнительного отчета, так что 1 может выполнять обычные фидуциарные управленческие обязательства (оказывать поддержку и т. Д.).

Помимо принятия второстепенной управленческой роли для сотрудника 4, у менеджера 2 также есть подчиненный член команды (5).

Редактировать: Чтобы проиллюстрировать проблему с несколькими родителями, давайте предоставим члену команды 4 стажера, сотрудника 6. Член команды 6 теперь является подчиненным обоих менеджеров 1 и 2 - последний наследуется через вспомогательную строку отчетности.

Организационная структура будет выглядеть следующим образом:

+--+-------+---------+
|ID|Primary|Secondary|
|0 |NULL   |NULL     |
|1 |0      |NULL     |
|2 |0      |NULL     |
|3 |1      |NULL     |
|4 |1      |2        |
|5 |2      |NULL     |
|6 |4      |NULL     |
+--+-------+---------+

Теперь я хочу расширить это представление SQL, которое дает мне список людей ниже любого данного сотрудника, охватывающий как первичные, так и вторичные отчеты.Поэтому для сотрудника 2 (менеджера с первичным и вторичным отчетом) я бы ожидал увидеть членов команды 4 и 5, а для генерального директора (0) я бы ожидал увидеть персоналчлен, кроме генерального директора.Наш новый стажер, 6, является подчиненным генерального директора, менеджеров 1 и 2, а также его непосредственным менеджером, 4.

Это будет выглядеть так:

+--+-----------+
|ID|Subordinate|
|0 |1          |
|0 |2          |
|0 |3          |
|0 |4          |
|0 |5          |
|0 |6          |
|1 |3          |
|1 |4          |
|1 |6          |
|2 |4          |
|2 |5          |
|2 |6          |
|4 |6          |
+--+-----------+

Как бы я достиг этого в SQL?Я думаю о какой-то операции OUTER APPLY над удостоверением личности, но я изо всех сил пытаюсь разобраться с повторным входом, который потребуется (я думаю), чтобы решить эту проблему.Мой опыт работы в процедурном программировании, что, я думаю, является одной из причин, по которым я здесь борюсь.

NB : очевидный вопрос, который я хотел бы предвидеть, это «Конечно, это проблема XY - зачем вам это делать?»

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

Сказав это, я открытк лучшим способам решения этой проблемы.

Ответы [ 4 ]

0 голосов
/ 01 марта 2019

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

+--+-------+---------+-------+--------+
|ID|project|over     |under  |level   |
|0 |14     |0        |9      |1       |
|1 |53     |4        |1      |2       |
|2 |4      |4        |4      |2       |
|3 |1      |4        |2      |3       |
|4 |1      |0        |7      |1       |
|5 |2      |4        |6      |1       |
|6 |4      |4        |8      |5       |
+--+-------+---------+-------+--------+

Пример расширенного использования проекта - добавление текущего проекта «Заявление о миссии» для отдела / компании / объекта / офиса / комнаты/ vendor / position или любая другая «группировка», о которой вы можете подумать, где желательно разрешение иерархии.Зачем делать жизнь сложнее?Худшее, что вам может когда-нибудь понадобиться сделать, - это разгрузить записи для завершенных проектов в какой-то архив, если необходима историческая информация.

0 голосов
/ 23 февраля 2019

Это довольно легко решить с помощью функций Parent-Child Hierarchy в DAX.Я не думаю, что вам нужно создавать какие-либо дополнительные таблицы, просто придерживайтесь следующих условий в ваших правилах RLS:

Для Сотрудника N, вам просто нужно проверить, если

PATHCONTAINS(PATH('Hierarchy'[ID], 'Hierarchy'[Primary]), N)

или

PATHCONTAINS(PATH('Hierarchy'[ID], 'Hierarchy'[Secondary]), N)

Обратите внимание, что это позволяет Сотруднику N видеть себя и своих подчиненных, но вы можете добавить дополнительное условие, если вы этого не хотите.


Редактировать: Когда ваша структура не является деревом, проблема становится более сложной.Вот подход, который должен работать.

Для каждого ID найдите подчиненных, чтобы получить Level1, найдите Level1 для следующего уровня подчиненных и т. Д., Пока не существует подчиненных.(Если в вашей структуре есть цикл, который возвращает вас на более высокий уровень, то вы застрянете в рекурсии.)

В этом случае под вершиной есть три уровня, поэтому нам нужно три шага.

| ID | Primary | Secondary | Level1 | Level2 | Level3 |
|----|---------|-----------|--------|--------|--------|
| 0  |         |           | 1      | 4      | 6      |
| 0  |         |           | 2      | 4      | 6      |
| 0  |         |           | 2      | 5      |        |
| 0  |         |           | 3      |        |        |
| 1  | 0       |           | 4      | 6      |        |
| 2  | 0       |           | 4      | 6      |        |
| 2  | 0       |           | 5      |        |        |
| 3  | 0       |           |        |        |        |
| 4  | 1       | 2         | 6      |        |        |
| 5  | 2       |           |        |        |        |
| 6  | 4       |           |        |        |        |

Вот код M, чтобы сделать это в редакторе Power Query:

let
    Source = Table.FromRows({{0,null,null},{1,0,null},{2,0,null},{3,0,null},{4,1,2},{5,2,null},{6,4,null}},{"ID", "Primary", "Secondary"}),
    #"Changed Type" = Table.TransformColumnTypes(Source,{{"ID", Int64.Type}, {"Primary", Int64.Type}, {"Secondary", Int64.Type}}),
    SearchNextLevel = ExpandNext(ExpandNext(ExpandNext(#"Changed Type", "Level1", "ID"), "Level2", "Level1"), "Level3", "Level2"),
    #"Appended Query" =
        Table.Combine(
            {Table.RenameColumns(Table.SelectColumns(SearchNextLevel, {"ID", "Level1"}), {"Level1","Subordinate"}),
             Table.RenameColumns(Table.SelectColumns(SearchNextLevel, {"ID", "Level2"}), {"Level2","Subordinate"}),
             Table.RenameColumns(Table.SelectColumns(SearchNextLevel, {"ID", "Level3"}), {"Level3","Subordinate"})}
        ),
    #"Filtered Rows" = Table.SelectRows(#"Appended Query", each ([Subordinate] <> null)),
    #"Removed Duplicates" = Table.Distinct(#"Filtered Rows"),
    #"Sorted Rows" = Table.Sort(#"Removed Duplicates",{{"ID", Order.Ascending}, {"Subordinate", Order.Ascending}})
in
    #"Sorted Rows"

Вот пользовательская функция, которая используется несколько раз для перехода на следующий уровень:

let
    ExpandToNextLevel = (T as table, NextLevel as text, ThisLevel as text) as table =>
    let
        SearchNextLevel =
        Table.AddColumn(T,
            NextLevel,
            (C) =>
                Table.SelectRows(
                    T, each Record.Field(C, ThisLevel) <> null and
                       ([Primary] = Record.Field(C, ThisLevel) or
                        [Secondary] = Record.Field(C, ThisLevel))
                    )[ID]
        ),
        ExpandColumn = Table.ExpandListColumn(SearchNextLevel, NextLevel)
    in
        ExpandColumn
in
    ExpandToNextLevel

Чтобы сделать это общее, мне, очевидно, нужно поместить расширение и добавление в рекурсивный цикл.Я вернусь к этому, как только позволит время.


Редактировать: Вот рекурсивная версия запроса, в которой вместо добавления используется разворот.

let
    Source = Table.FromRows({{0,null,null},{1,0,null},{2,0,null},{3,0,null},{4,1,2},{5,2,null},{6,4,null}},{"ID", "Primary", "Secondary"}),
    #"Changed Types" = Table.TransformColumnTypes(Source,{{"ID", Int64.Type}, {"Primary", Int64.Type}, {"Secondary", Int64.Type}}),
    IDCount = List.Count(List.Distinct(#"Changed Types"[ID])),
    RecursiveExpand = List.Generate(
        () => [i=0, InputTable = #"Changed Types"],
        each [i] < IDCount and
             List.NonNullCount(List.Last(Table.ToColumns([InputTable]))) > 0,
        each [
             CurrentLevel = if [i] = 0 then "ID" else "Level" & Text.From([i]),
             NextLevel = if [i] = 0 then "Level1" else "Level" & Text.From([i]+1),
             InputTable = ExpandNext([InputTable], NextLevel, CurrentLevel),
             i = [i] + 1
        ]
    ),
    FinalTable = List.Last(RecursiveExpand)[InputTable],
    #"Unpivoted Other Columns" = Table.UnpivotOtherColumns(FinalTable, {"Secondary", "Primary", "ID"}, "Level", "Subordinate"),
    #"Removed Other Columns" = Table.SelectColumns(#"Unpivoted Other Columns",{"ID", "Subordinate"}),
    #"Removed Duplicates" = Table.Distinct(#"Removed Other Columns"),
    #"Sorted Rows" = Table.Sort(#"Removed Duplicates",{{"ID", Order.Ascending}, {"Subordinate", Order.Ascending}})
in
    #"Sorted Rows"

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

0 голосов
/ 26 февраля 2019

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

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

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

WITH all_reports AS (
    SELECT [Primary] [ManagerID], ID [Subordinate]
    FROM tbl
    WHERE [Primary] IS NOT NULL
    UNION
    SELECT [Secondary], ID
    FROM tbl
    WHERE [Secondary] IS NOT NULL
)
, recursive_cte AS (
    SELECT ManagerID, Subordinate
    FROM all_reports
    UNION ALL
    SELECT ancestor.ManagerID, descendant.Subordinate
    FROM recursive_cte ancestor
    INNER JOIN all_reports descendant ON descendant.ManagerID = ancestor.Subordinate
)
SELECT DISTINCT ManagerID, Subordinate
FROM recursive_cte

Если вы хотите расстояние между менеджером и подчиненным, то перепишите рекурсивный CTE следующим образом:

SELECT ManagerID, Subordinate, 1 [Distance]
FROM all_reports
UNION ALL
SELECT ancestor.ManagerID, descendant.Subordinate, ancestor.Distance + 1
FROM recursive_cte ancestor
INNER JOIN all_reports descendant ON descendant.ManagerID = ancestor.Subordinate
0 голосов
/ 23 февраля 2019

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

См. Шаблоны DAX: иерархии родитель-потомок как это сделать полностью в DAX.Или вы можете использовать запрос SQL Server с использованием рекурсивного выражения общей таблицы, чтобы сгладить две иерархии.

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

...