【问题标题】:How to replicate a SAS merge如何复制 SAS 合并
【发布时间】:2015-08-27 17:43:03
【问题描述】:

我有两张桌子,t1 和 t2:

t1
  person | visit | code1 | type1
       1       1      50      50 
       1       1      50      50 
       1       2      75      50 

t2
  person | visit | code2 | type2
       1       1      50      50 
       1       1      50      50 
       1       1      50      50 

当 SAS 运行以下代码时:

   DATA t3;
     MERGE t1 t2;
     BY person visit;

   RUN;

它生成以下数据集:

       person | visit | code1 | type1 | code2 | type2
            1       1      50      50      50      50
            1       1      50      50      50      50
            1       1      50      50      50      50
            1       2      75      50

我想在 SQL 中复制这个过程,我的想法是使用全外连接。除非有重复的行,否则此方法有效。当我们在上面的例子中有重复的行时,一个完整的外连接会生成下表:

       person | visit | code1 | type1 | code2 | type2
            1       1      50      50      50      50
            1       1      50      50      50      50
            1       1      50      50      50      50
            1       1      50      50      50      50
            1       1      50      50      50      50
            1       1      50      50      50      50
            1       2      75      50

我想知道如何让 SQl 表与 SAS 表匹配。

【问题讨论】:

  • 是否有需要保留重复行的原因?
  • 这个问题和this question you posted earlier有什么不同?
  • @vkp 是的,稍后在代码中它对每个人求和,访问组
  • @Joe 我并没有真正得到另一个问题的答案。我尝试了你的建议,但一定是误解了它,因为我没有工作。我想我会在这里问一个更一般形式的问题。这通常是不受欢迎的吗?如果是这样我道歉。下面提供的答案很有帮助。
  • 是的,你不应该问这个问题两次(这非常接近以至于没有任何不同)。下次请编辑另一个问题,使其更笼统,如果这是您的意图,并且您没有收到任何有用的答案。

标签: sql merge sas outer-join


【解决方案1】:

戈登的回答很接近;但它错过了一点。这是它的输出:

person  visit   code1   type1   seqnum  person  visit   code2   type2   seqnum
1       1       1       1       1       1       1       1       1       1
1       1       2       2       2       1       1       2       2       2
NULL    NULL    NULL    NULL    NULL    1       1       3       3       3
1       2       1       3       1       NULL    NULL    NULL    NULL    NULL

第三行的空值不正确,而第四行的空值是正确的。

据我所知,在 SQL 中,除了将事情拆分为几个查询之外,没有真正好的方法可以做到这一点。我认为有五种可能:

  • 匹配人/访问,匹配seqnums
  • 匹配人员/访问,Left 有更多 seqnums
  • 匹配人员/访问,Right 有更多 seqnums
  • 左边有不匹配的人/访问
  • 权利有无与伦比的人/访问

我认为最后两个可能适用于一个查询,但我认为第二个和第三个必须是单独的查询。当然,您可以将所有内容结合在一起。

这里有一个例子,使用一些更适合查看正在发生的事情的临时表。请注意,第三行现在填充了code1type1,即使它们是“额外的”。我只添加了五个标准中的三个 - 您在最初示例中的三个 - 但其他两个并不太难。

请注意,这是在 SAS 中更快的示例 - 因为 SAS 具有逐行概念,即它能够一次执行一行。对于大型表,SQL 往往需要更长的时间,除非可以非常整齐地划分事物并拥有非常好的索引 - 即便如此,我也从未见过 SQL DBA 在其中一些方面做得与 SAS 一样好类型的问题。这当然是你必须接受的——SQL 有它自己的优势,其中之一可能就是价格......

这是我的示例代码。我确信它不是非常优雅,希望 SQL 人员之一可以改进它。这是为在 SQL Server 中工作而编写的(使用表变量),同样的事情应该在其他变体中进行一些更改(使用临时表),假设它们实现了窗口化。 (SAS 当然不能做这个特别的事情——因为即使是 FedSQL 也实现了 ANSI 1999,而不是 ANSI 2008。)这是基于 Gordon 的初始查询,然后在最后用附加位进行修改。任何想要改进这一点的人,请随时编辑和/或复制到新的/现有的答案。

declare @t1 table (person INT, visit INT, code1 INT, type1 INT);
declare @t2 table (person INT, visit INT, code2 INT, type2 INT);


insert into @t1 values (1,1,1,1)
insert into @t1 values (1,1,2,2)
insert into @t1 values (1,2,1,3)

insert into @t2 values (1,1,1,1)
insert into @t2 values (1,1,2,2)
insert into @t2 values (1,1,3,3)

select coalesce(t1.person, t2.person) as person, coalesce(t1.visit, t2.visit) as visit,
                t1.code1, t1.type1, t2.code2, t2.type2
from (select *,
             row_number() over (partition by person, visit order by type1) as seqnum
      from @t1
     ) t1 inner join
     (select *,
             row_number() over (partition by person, visit order by type2) as seqnum
      from @t2
     ) t2
     on t1.person = t2.person and t1.visit = t2.visit and
        t1.seqnum = t2.seqnum
 union all

select coalesce(t1.person, t2.person) as person, coalesce(t1.visit, t2.visit) as visit,
                t1.code1, t1.type1, t2.code2, t2.type2
from (
      (select person, visit, MAX(seqnum) as max_rownum from (
        select person, visit, 
             row_number() over (partition by person, visit order by type1) as seqnum
      from @t1) t1_f 
      group by person, visit
     ) t1_m inner join
     (select *, row_number() over (partition by person, visit order by type1) as seqnum
       from @t1
      ) t1 
        on t1.person=t1_m.person and t1.visit=t1_m.visit
        and t1.seqnum=t1_m.max_rownum
        inner join
     (select *,
             row_number() over (partition by person, visit order by type2) as seqnum
      from @t2
     ) t2
     on t1.person = t2.person and t1.visit = t2.visit and
        t1.seqnum < t2.seqnum 
     )
 union all
 select t1.person, t1.visit, t1.code1, t1.type1, t2.code2, t2.type2
     from @t1 t1 left join @t2 t2
    on t2.person=t1.person and t2.visit=t1.visit
    where t2.code2 is null

【讨论】:

    【解决方案2】:

    您可以通过向每个表添加 row_number() 来复制 SAS 合并:

    select t1.*, t2.*
    from (select t1.*,
                 row_number() over (partition by person, visit order by ??) as seqnum
          from t1
         ) t1 full outer join
         (select t2.*,
                 row_number() over (partition by person, visit order by ??) as seqnum
          from t2
         ) t2
         on t1.person = t2.person and t1.visit = t2.visit and
            t1.seqnum = t2.seqnum;
    

    注意事项:

    • ?? 表示放入用于排序的列。 SAS 数据集具有内在顺序。 SQL 表没有,因此需要指定顺序。
    • 您应该明确列出列(而不是在外部查询中使用t1.*, t2.*)。我认为 SAS 在结果数据集中只包含一次 personvisit

    编辑:

    注意:以上为键列生成单独的值。这很容易解决:

    select coalesce(t1.person, t2.person) as person,
           coalesce(t1.key, t2.key) as key,
           t1.code1, t1.type1, t2.code2, t2.type2
    from (select t1.*,
                 row_number() over (partition by person, visit order by ??) as seqnum
          from t1
         ) t1 full outer join
         (select t2.*,
                 row_number() over (partition by person, visit order by ??) as seqnum
          from t2
         ) t2
         on t1.person = t2.person and t1.visit = t2.visit and
            t1.seqnum = t2.seqnum;
    

    这解决了列问题。您可以使用first_value()/last_value() 或更复杂的join 条件来解决复制问题:

    select coalesce(t1.person, t2.person) as person,
           coalesce(t1.visit, t2.visit) as visit,
           t1.code1, t1.type1, t2.code2, t2.type2
    from (select t1.*,
                 count(*) over (partition by person, visit) as cnt,
                 row_number() over (partition by person, visit order by ??) as seqnum
          from t1
         ) t1 full outer join
         (select t2.*,
                 count(*) over (partition by person, visit) as cnt,
                 row_number() over (partition by person, visit order by ??) as seqnum
          from t2
         ) t2
         on t1.person = t2.person and t1.visit = t2.visit and
            (t1.seqnum = t2.seqnum or
            (t1.cnt > t2.cnt and t1.seqnum > t2.seqnum and t2.seqnum = t2.cnt) or
            (t2.cnt > t1.cnt and t2.seqnum > t1.seqnum and t1.seqnum = t1.cnt)
    

    这在单个连接中实现了“保留最后一行”逻辑。可能出于性能原因,您可能希望将其放入原始逻辑上的单独 left joins 中。

    【讨论】:

    • 这并没有完美地复制它,不是吗?如果 T1 有 2 行,T2 有 3 行用于特定 ID 分组,则 SAS 会将 T1.seqnum=2 复制到 T2.seqnum=3。我认为您必须为inner join 执行此操作,然后添加第二个(可能是第三个)表,即每个分组的T1.seqnum &gt; max(T2.seqnum),也许T2.seqnum &gt; max(T1.seqnum),加入它们各自的max.seqnum 行。
    • 你的意思是把上面代码中的全外连接换成内连接,然后用t1.seqnum > max(t2.seqnum) and t2.seqnum>的两张表做内连接最大值(t1.seqnum)? @乔
    • 在内部连接之上,然后联合到另一个或两个连接(每个联合)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多