【问题标题】:SQL - Recursive Tree Hierarchy with Record at Each LevelSQL - 每个级别都有记录的递归树层次结构
【发布时间】:2017-12-14 00:21:49
【问题描述】:

尝试使用 SAS(据我所知不支持 WITH RECURSIVE)在 SQL 中创建经典的层次结构树。

这是现有表中的简化数据结构:

|USER_ID|SUPERVISOR_ID|

因此,要构建层次结构,您只需将其递归 x 次即可获取您要查找的数据,其中SUPERVISOR_ID = USER_ID。在我公司,是 16 级。

当尝试为每个用户终止分支时会出现此问题。例如,假设第 1 级的用户 A 在第 2 级下有用户 B、C、D 和 E。因此,使用递归 LEFT JOIN,您将得到:

| -- Level 1 -- | -- Level 2 -- |
     User A          User B
     User A          User C
     User A          User D
     User A          User E

问题是,用户 A 没有自己的终止分支。需要的最终结果是:

| -- Level 1 -- | -- Level 2 -- |
     User A           NULL         
     User A          User B
     User A          User C
     User A          User D
     User A          User E

我第一个脸红的想法是我可以通过在每个级别创建一个临时表然后对结果完全执行 UNION ALL 来解决这个问题,但是考虑到大小(16 个级别),这似乎非常低效,我希望我错过了这是一个更清洁的解决方案。

【问题讨论】:

  • SAS 的 proc SQL 是一个玩具实现。它可能看起来干净,但它甚至不能识别反连接。忽略它。您可能注定要使用大量数据步骤(+ 大量临时表)
  • 如果您发布示例数据和预期输出,我们可以为您提供更好的帮助。我假设这会减慢更改维度类型表的速度?还有其他使用 SAS 功能而不是 SQL 的方法来实现这一点。
  • SAS 中的真正递归(链表或二叉树之类的东西)是痛苦的,不管有没有 SQL。你最好使用 Python 或 Mongo DB 之类的东西。 Map/Reduce 让这类事情变得更加容易。

标签: sql recursion sas hierarchy


【解决方案1】:

我不太确定我是否理解这个问题,但如果您要生成每个主管下所有员工的完整列表,那么这是一种方法,假设每个员工都有一个唯一的 ID,这可以出现在用户或主管列中:

data employees;
input SUPERVISOR_ID USER_ID;
cards;
1 2
1 3
1 4
2 5
2 6
2 7
7 8
;
run;

proc sql;
  create view distinct_employees as 
  select distinct SUPERVISOR_ID as USER_ID from employees
  union
  select distinct USER_ID from employees;
quit;

data hierarchy;
  if 0 then set employees;
  set distinct_employees;
  if _n_ = 1 then do;
    declare hash h(dataset:'employees');
    rc = h.definekey('USER_ID');
    rc = h.definedata('SUPERVISOR_ID');
    rc = h.definedone();
  end;
  T_USER_ID = USER_ID;
  do while(h.find() = 0);
    USER_ID = T_USER_ID;
    output;
    USER_ID = SUPERVISOR_ID;
  end;
  drop rc T_USER_ID;
run;

proc sort data = hierarchy;
  by SUPERVISOR_ID USER_ID;
run;

【讨论】:

    【解决方案2】:

    考虑一些简单的过程 P,它从一组 (super_id, user_id) 中创建可能路径的矩形。

    长度为 N 的路径有 N 层深,并链接 (N-1) 个关系。

    每个级别的值是否与该级别不同?

    • 没有?与实际路径相比,P 将找到循环、交叉路径和环绕路径。环绕是当实际路径级别 > 1 的节点被“发现”为级别 = 1 节点时。
    • 是吗? P 将找到路径、交叉路径和环绕路径。额外的数据限制或规则可以帮助消除

    考虑 4 个级别值不明确的简单路径:

    data path(keep=L1-L4) rels(keep=super_id user_id);
      array L(4);
      input L(*);
      output path;
      super_id = L(1);
      do i = 2 to dim(L);
        user_id = L(i);
        output rels;
        super_id = user_id;
      end;
    datalines;
    1 3 1 4
    1 5 1 4
    2 3 2 3
    1 2 3 4
    run;
    

    只有12条关系数据。这些对存在的路径和它们存在的级别都不是未知的:

     1 : 1 3
     2 : 3 1
     3 : 1 4
     4 : 1 5
     5 : 5 1
     6 : 1 4
     7 : 2 3
     8 : 3 2
     9 : 2 3
    10 : 1 2
    11 : 2 3
    12 : 3 4
    

    一个显式的 2 阶段查询,用于在关系中组装 4 级路径。如果代码有效,则可以将其抽象为宏编码。

    proc sql;
    
      * RELS cross RELS, extensive i/o;
      * get on the induction ladder;
    
      create table ITER_1 as
      select distinct
        S.super_id as L3 /* parent^2 */
      , S.user_id as L2 /* parent */ 
      , U.user_id as L1 /* leaf */
      from RELS U
      cross join RELS S 
      where S.user_id = U.super_id
      order by L3, L2, L1
      ;
    
      * ITER_1 cross RELS, little less extensive i/o;
      * if you see the inductive variation you can macroize it;
    
      create table ITER_2 as
      select distinct
        S.super_id as L4 /* parent^3 */
      , U.L3 /* parent^2 */
      , U.L2 /* parent */
      , U.L1 /* leaf */
      from ITER_1 U
      cross join RELS S
      where S.user_id = U.L3
      order by L4, L3, L2, L1
      ;
    quit;
    

    上述汇编器没有对身份知识,不能限制离散对的路径。所以会有循环、交叉和换行。

    找到的路径(一些解释)

     1 : 1 2 3 1   path 4 L3 xover to path 1 L2
     2 : 1 2 3 2   path 4 L3 xover to path 3 L2
     3 : 1 2 3 4   actual
     4 : 1 3 1 2   path 1 L3 xover to path 4 L1
     5 : 1 3 1 3
     6 : 1 3 1 4   actual
     7 : 1 3 1 5
     8 : 1 3 2 3
     9 : 1 5 1 2
    10 : 1 5 1 3
    11 : 1 5 1 4   actual
    12 : 1 5 1 5
    13 : 2 3 1 2
    14 : 2 3 1 3
    15 : 2 3 1 4
    16 : 2 3 1 5
    17 : 2 3 2 3   actual is actually a cycler too
    18 : 3 1 2 3
    19 : 3 1 3 1
    20 : 3 1 3 2
    21 : 3 1 3 4
    22 : 3 1 5 1
    23 : 3 2 3 1
    24 : 3 2 3 2
    25 : 3 2 3 4
    26 : 5 1 2 3
    27 : 5 1 3 1
    28 : 5 1 3 2
    29 : 5 1 3 4
    30 : 5 1 5 1   path 2 L3 cycled to path 2 L1
    

    如果每个关系级别的 id 在任何其他级别都找不到,则隐式消除循环。因为没有路径标识信息,所以不能消除交叉。环绕也一样。

    更复杂的 SQL 可以确保找到的“路径”中的每个关系只出现一次,并且路径的内容不同。根据实际数据,您可能仍有大量错误路径。

    高度规则的代码适合宏化,但实际的 SQL 运行时间高度依赖于实际数据和 REL 数据集索引。

    proc sql;

    create table ITER_1 as
    select 
      L3 /* parent^2 */
    , L2 /* parent */ 
    , L1 /* leaf */
    , R1
    , R2
    from 
    (
      select distinct
        S.super_id as L3 /* parent^2 */
      , S.user_id as L2 /* parent */ 
      , U.user_id as L1 /* leaf */
      , U.row_id as R1
      , S.row_id as R2
      , monotonic() as seq
      from RELS U
      cross join RELS S 
      where S.user_id = U.super_id
        and S.row_id < U.row_id  /* triangular constraint allowed due to symmetry */
    )
    group by L3, L2, L1
    having seq = min(seq)
    order by L3, L2, L1
    ;
    
    create table ITER_2 as
    select
      L4 /* parent^3 */ format=6.
    , L3 /* parent^2 */ format=6.
    , L2 /* parent */ format=6.
    , L1 /* leaf */ format=6.
    , R1 format=6.
    , R2 format=6.
    , R3 format=6.
    from
    (
      select distinct
        S.super_id as L4 /* parent^3 */ format=6.
      , U.L3 /* parent^2 */ format=6.
      , U.L2 /* parent */ format=6.
      , U.L1 /* leaf */ format=6.
      , U.R1 format=6.
      , U.R2 format=6.
      , S.row_id as R3 format=6.
      , monotonic() as seq
      from ITER_1 U
      cross join RELS S
      where S.user_id = U.L3
        and S.row_id ne R1
        and S.row_id ne R2
    )
    group by L4, L3, L2, L1
    having seq = min(seq)
    order by L4, L3, L2, L1
    ;
    

    退出;

    对 NULL 项的最后调整将需要更多 SQL。

    是否可以在不需要 NULL 的情况下处理发现的层次结构?带有 BY 处理的 DATA Step SET 可以使用 LAST 检测关卡的结束。

    【讨论】:

      猜你喜欢
      • 2013-11-07
      • 2018-02-21
      • 1970-01-01
      • 2017-02-04
      • 2019-07-02
      • 2012-03-01
      • 2023-03-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多