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

问题描述 投票:5回答:4

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

经理2在她的团队中有2名工作人员(34),但是工作人员4实际上在1经理的时区工作,所以虽然他有2作为他的主要报告,他也向1经理报告作为中学报告,以便1可以满足正常的信托管理义务(提供支持等)。

除了为员工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是首席执行官,经理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操作,但我正在努力解决这个需要(我认为)解决这个问题的重入问题。我的背景是程序编程,我认为这是我在这里挣扎的部分原因。

注意:我想在这里预料到的一个显而易见的问题是“当然这是一个XY问题 - 为什么你想要这样做呢?”

我想在PowerBI中使用row-level security,让每个员工在组织结构中访问有关他们下面的个人的某些信息。不幸的是,RLS不允许每个人执行存储过程,所以我坚持做这个组合扩展,然后根据登录简单地过滤上面的表。

话虽如此,我愿意接受更好的方法来解决这个问题。

sql-server tsql powerbi dax ssas-tabular
4个回答
2
投票

要在SQL中获得所需的结果,最简单的方法是使用递归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
投票

使用DAX中的Parent-Child Hierarchy函数可以很容易地解决这个问题。我认为您不需要构建任何额外的表,只需在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 Editor中执行此操作的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

为了实现这一点,我显然需要将扩展​​和追加放入递归循环中。我会在时间允许的情况下回到这里。


编辑:这是查询的递归版本,它使用unpivoting而不是追加。

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"

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


2
投票

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

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

在任何一种情况下,它们都会成为模型中的两个独立表,以及两个独立的关系,您可以在RLS过滤器中引用它们。


-1
投票

简单的存储方式,imho。所有int。只有一个连接点,但将满足我所能看到的所有需求,以便在各个方向都具有很大的灵活性。项目可以是一个小项目,也可以是一组项目甚至是部门/公司层级。似乎动态和适应性是优先考虑或排序。

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

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

© www.soinside.com 2019 - 2024. All rights reserved.