SAS或SQL:根据变量值之间的关系重新排列表

时间:2016-08-19 08:07:52

标签: sql sorting sas

在SAS中,我有一个表,其中包含两个名为parent和child的列(变量)。我想把这个表改为两种不同的安排。

该表的一个例子是:

Obs number | parent | child
----------------------------
1          | D      | B
2          | J      | Q
3          | D      | S
4          | K      | J
5          | T      | U
6          | B      | T

构建此表的规则是:

  1. 在每一行(观察)中,父列中的值是子列中值的父级。 例如,在第1行和第2行中,我们发现D是B的父母,B是D的孩子; J是Q'父母和Q是J的孩子。

    Obs number | parent | child
    ----------------------------
    1          | D      | B
    2          | J      | Q
    
  2. 一位家长可以有几个孩子。例如,在第1行和第3行中,我们发现B和S都是D的孩子。

    Obs number | parent | child
    ----------------------------
    1          | D      | B
    ...        
    3          | D      | S
    
  3. 一个孩子只能有一个父母。例如,B的父母是D,B不应该是D以外的任何父母的子女。不允许以下形式:

    Obs number | parent | child
    ----------------------------
    1          | D      | B
    ...
    n          | C      | B
    
  4. 孩子可以拥有自己的孩子,父母可以拥有自己的孩子。例如,在第1行和第6行中,我们发现B是D的孩子并且它有自己的孩子T:

    Obs number | parent | child
    ----------------------------
    1          | D      | B
    ...
    6          | B      | T
    

    在第2行和第4行中,我们发现J是Q的父级,它有自己的父级K:

    Obs number | parent | child
    ----------------------------
    2          | J      | Q
    ...
    4          | K      | J
    
  5. 孩子不应该是自己父母的父母,父母不应该是自己孩子的孩子。例如,B是来自D的孩子,它不能再是D的父母。不允许使用以下表格:

    Obs number | parent | child
    ----------------------------
    1          | D      | B
    ...
    n          | B      | D
    
  6. 以上是构建原始表所需的所有规则。然后我想把表变成两种不同的安排。

    首先,我想对这个表进行排序,使得父和子的相关行聚在一起,形成从最老的父级到最小的子级的链接。通过这个,我的意思是从左边的原始表到右边的变换表的转换,如下所示:

    Original table:                          Transformed table:
    
    Obs number | parent | child              Obs number | parent | child
    ----------------------------             ----------------------------
    1          | D      | B                  1          | D      | S
    2          | J      | Q                  2          | D      | B 
    3          | D      | S         TO:      3          | B      | T 
    4          | K      | J                  4          | T      | U
    5          | T      | U                  5          | K      | J
    6          | B      | T                  6          | J      | Q
    

    其次,我想将原始表转换为新表,以使新表的每一行都包含从最老的父级到最小的子级的列。我的意思是下面的转变:

    Original table:                          Transformed table:
    
    Obs number | parent | child              Obs number | var1 | var2 | var3 | var4
    ----------------------------             --------------------------------------
    1          | D      | B                  1          | D    | B    | T    | U
    2          | J      | Q                  2          | K    | J    | Q    | .
    3          | D      | S         TO:      3          | D    | S    | .    | .
    4          | K      | J 
    5          | T      | U
    6          | B      | T
    

    以下是设置原始测试表的SAS代码:

    data original_table;
        input parent $ child $;
        datalines;
    D      B
    J      Q
    D      S
    K      J
    T      U
    B      T
    ;
    

    有人问过我尝试过的最小尝试。所以这就是。在我发布这里几个小时之后,我确实找到了进行第二次转换的方法。以下是代码:

    %macro TransformTable(lib=, dt=, output=);
    /* rename the variable in the input table */
    proc sql noprint;
        select cats(name,"=",cats(upcase("var"),substr("12", varnum, 1))) into :newvars separated by ' '
        from sashelp.vcolumn
        where libname = upcase("&lib.") and memname=upcase("&dt.") and 
        1 <= varnum <= 2;
    quit;
    
    data work._original_table;
        set &lib..&dt.;
        rename &newvars.;
    run;
    
    /* select values that are not child of other values (pure parent table) */
    proc sql noprint;
        create table _combine1
        as select * from
        _original_table
        where VAR1 not in (select VAR2 from _original_table);
    quit;
    
    /* join the pure parent table and its update table to the original table multiple times */
    /* move all child and child's child into one line */
    %let nonmissing = 1;
    %let i = 1;
    %do %while(&nonmissing. > 0);
        %let newfile_num = %eval(&i. + 1);
        %let lastcol_num = %eval(&i. + 2);
        proc sql noprint;
            create table _combine&newfile_num.
            as select a.*, b.VAR2 as VAR&lastcol_num.
            from _combine&i. a
            left join
            _original_table b
            on a.VAR&newfile_num. = b.VAR1;
        quit;
    
        proc sql noprint;
            select count(*) into :nonmissing
            from _combine&newfile_num.(where=(^missing(VAR&lastcol_num.)));
        quit;
        %put &nonmissing.;
    
        %let i = %eval(&i. + 1);
    %end;
    
    /* remove the last empty column */
    data &output. (drop = VAR&lastcol_num.); 
        set _combine&newfile_num.;
    run;
    %mend TransformTable;
    
    %TransformTable(lib=work, dt=original_table, output=Result_for_2ndTrans)
    

    但它还远远不够好。

    首先我使用循环而不是递归,因为我不知道如何在SAS或SQL中进行递归。

    其次,要停止循环,我需要每次检查输出文件。它消耗额外的资源,而不像递归中的基本情况那样漂亮。

    最终输出还将包含一个提取空列,需要在此宏的末尾进行额外处理。如果您可以使用它并提出更好的解决方案,请告诉我。

    对于第一次转型,到目前为止我还没有想到解决这个问题。

2 个答案:

答案 0 :(得分:2)

您可以使用hash tablehash iterator解决此问题。 我希望这能产生正确的结果。你需要检查一下。

proc sort data=original_table;
    by parent child;
run;

data _null_;
    length path $32767 longest 8;

    if _N_ = 1 then do;
        declare hash h();
        h.defineKey('path');
        h.defineData('path','longest');
        h.defineDone();
        call missing(path, longest);

        declare hiter hi('h');
    end;

    set original_table end=last;
    put "PARENT: " parent " CHILD: " child;

    longest = 1;
    path = parent;
    h.ref();
    put "ADD NEW SOURCE: " path=;

    rc = hi.first();
    do while(rc = 0);
        if substr(path, lengthn(path), 1) = parent then do;
            longest = 0;
            h.replace();
            longest = 1;
            path = cats(path, child);
            h.ref();
            put "ADD CHILD: " path=;
        end;
        rc = hi.next();
    end;

    rc = hi.first();
    do while(rc = 0);
        if substr(path, 1, 1) = child then do;
            longest = 0;
            h.replace();
            longest = 1;
            path = cats(parent, path);
            h.ref();
            put "ADD PARENT: " path=;
        end;
        rc = hi.next();
    end;

    if last;
    h.output(dataset: 'result(where=(longest = 1))');
run;

data result;
    set result;
    len = lengthn(path);
run;

proc sort data=result;
    by descending len path;
run;

答案 1 :(得分:1)

您想要做的是递归处理数据集。在宏处理器之外,SAS并不是真的可以做到这一点。您必须使用goto之类的东西来构建递归函数。结果代码非常混乱。哈希表将有助于跟踪已使用的父/子对。

此解决方案有效,但可以进一步优化:

data transformed_table;
length var1 - var10 $1;

if _n_ = 1 then do;
    declare hash h(dataset:"original_table ");
    declare hiter hi("h");
    h.definekey("parent", "child");
    h.definedone();
    call missing(ref);

end;

set original_table end = eof;

array var [*] var1 - var10;
var1 = parent; ref = 2; 
var[ref] = child;
start:
check = 0;
do i = 1 to (n);
    set original_table (rename=(parent=p1 child=c1) obs=1 ) point=i nobs=n;
    rc = hi.first();
    do while (rc = 0);
        if h.check(key:p1, key:c1) eq 0 and var[ref] = p1 then do;
            ref + 1;
            var[ref] = c1;
            check = 1;
        end;
        rc = hi.next();
        if check = 1 then do;
            h.remove(key:var[ref - 1], key:var[ref]);
            goto start;
        end;
    end;    
end;

keep var1 - var10 ;
if eof then h.output(dataset: 'id');
run;

proc sort data = transformed_table; by var1 var2; run;
proc sort data = id; by parent child; run;
data transformed_table;
    merge transformed_table
          id (in = a rename=(parent=var1 child=var2))
          ;
    by var1 var2;
    if a;

run;