SQL / DAX中的多父级层次结构传播

时间:2019-02-23 02:26:13

标签: sql-server tsql powerbi dax ssas-tabular

假设我有一张表格,该表格描述了每个工作人员的主要和次要报告项目。让我们想象一个组织结构,其中首席执行官,雇员0有2位经理(12)向他汇报。

经理2的团队中有2名员工(34),但是工作人员4实际上在经理1的时区工作,因此,在他将2作为主要报告的同时,他还向经理1报告了辅助报告,以便1能够履行正常的信托管理义务(提供支持等)。< / p>

除了为员工4担任二级管理角色外,经理2还拥有一个向其报告的团队成员(5)。

编辑:为了说明多父辈问题,我们给团队成员4实习生,工作人员6团队成员6现在是经理12的下属-后者是通过辅助报告行继承的。

组织结构如下:

+--+-------+---------+
|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(具有主要和辅助报告的经理),我希望看到团队成员45,以及首席执行官(0)我希望看到除CEO以外的任何工作人员。我们的新实习生6是CEO,经理12的下属,以及他的直接经理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中实现此目标?我正在考虑对ID进行某种OUTER APPLY操作,但是我正在努力解决解决此问题所需的可重入性。我的背景是过程编程,我认为这是我在这里苦苦挣扎的部分原因。

NB :我想在这里想到一个明显的问题是“当然这是一个XY问题-为什么您到底想这样做?”

我想在PowerBI中使用row-level security,以使每个工作人员都可以访问有关组织结构中其下属个人的某些信息。不幸的是,RLS不允许每个人执行存储过程,因此我坚持进行这种组合扩展,然后仅根据登录名过滤上表。

话虽如此,我对解决这个问题的更好方法持开放态度。

4 个答案:

答案 0 :(得分:3)

使用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       |           |        |        |        |

这是在Power Query编辑器中执行此操作的M代码:

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"

它将一直扩展级别,直到扩展到下一个级别会产生所有空值或达到最大级别数,以防止无限循环。

答案 1 :(得分:2)

您需要弄平报告层次结构和辅助报告层次结构,然后将它们加载到表格模型的单独表中。

有关如何在DAX中完全执行此操作,请参见DAX Patterns: Parent-Child Hierarchies。或者,您可以使用带有递归公用表表达式的SQL Server查询来展平这两个层次结构。

无论哪种情况,它们都将成为模型中的两个单独的表以及两个单独的关系,然后可以在RLS过滤器中引用它们。

答案 2 :(得分:2)

要获得所需的SQL结果,最简单的方法是使用递归CTE。

在下面的示例中,我将工作分为两个CTE。第一种将集合转换为成对的管理者和下属。第二个CTE从第一个CTE获取所有结果,然后使用UNION ALL加入自身,其中第一个CTE的管理者是递归CTE的从属。这将一直重复,直到无法进行匹配为止。

由于下属可能有多个管理者,因此可以为每个祖先返回重复的行。因此,从递归CTE返回结果时使用DISTINCT。

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

答案 3 :(得分:-1)

简单的存储方式,恕我直言。全部为整数。只有一个连接点,但可以满足我看到的所有需求,并在各个方向上都具有很大的灵活性。项目可以是小型项目,也可以是项目组合,甚至可以是部门/公司层次结构。好像动态和适应性强是优先事项或排序。

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

以扩展方式使用项目的示例是为部门/公司/设施/办公室/房间/供应商/职位或您可以想到的其他任何“分组”添加正在进行的“任务声明”项目需要分辨率。为什么使生活更加复杂?有一天您可能需要做的最糟糕的事情是,如果需要历史信息,则将已完成项目的条目卸载到某种存档中。