【问题标题】:PROC SQL Update Efficiency for Large Datasets大型数据集的 PROC SQL 更新效率
【发布时间】:2015-08-22 20:54:20
【问题描述】:

我有一个包含 1000 万行和 1800 列的 SAS 主数据集。我需要使用具有 1500 万条记录的事务数据集更新 10 列,仅用于具有匹配键的记录。 我尝试使用以下代码运行 proc sql update 语句。

proc sql;
UPDATE lib1.master1 a

SET col1 = (SELECT col1 FROM lib1.changes_1 b WHERE a.key=b.key),
    col2 = (SELECT col2 FROM lib1.changes_1 b WHERE a.key=b.key),
    col3 = (SELECT col3 FROM lib1.changes_1 b WHERE a.key=b.key),
    col4 = (SELECT col4 FROM lib1.changes_1 b WHERE a.key=b.key),
    col5 = (SELECT col5 FROM lib1.changes_1 b WHERE a.key=b.key),
    col6 = (SELECT col6 FROM lib1.changes_1 b WHERE a.key=b.key),
    col7 = (SELECT col7 FROM lib1.changes_1 b WHERE a.key=b.key),
    col8 = (SELECT col8 FROM lib1.changes_1 b WHERE a.key=b.key),
    col9 = (SELECT col9 FROM lib1.changes_1 b WHERE a.key=b.key)

WHERE EXISTS ( SELECT 1 FROM lib1.changes_1 b WHERE A.key = B.key);
quit;

为了测试目的,我只尝试了 col1,它已经运行了 4 个多小时。

我可以通过删除 10 列然后左连接来考虑数据合并,但这会改变列的顺序。重新排序 1800 列将再次成为一项乏味的任务。

有更快/更有效的技术吗?

【问题讨论】:

  • 仅供参考 - 不幸的是,SAS 支持的 SQL 更新几乎总是很糟糕。 SQL 在 SAS 中有很多用途,但更新几乎不应该是一个,除非它是一个不是来自表的单值更新。无论出于何种原因,这 总是 似乎非常慢,即使对于不是那么大的数据集,甚至对于 SQL Server 中的更新都非常快。

标签: sql performance sas large-data


【解决方案1】:

你有一个挑战。首先,我建议在lib1.changes_1(key) 上创建一个索引,如果你没有的话。这可能会大大提升性能。

proc sql 不支持更新中的join——这才是你真正需要的。但是,许多底层数据引擎可以。因此,如果您正在与数据库(例如 MySQL、Postgres 或 SQL Server)中的数据进行通信,那么您可以编写一个原生模式查询来使用 join 进行更新。

全 SAS 解决方案是使用数据步骤。

【讨论】:

    【解决方案2】:

    如何为您的交易数据集B 中的 10 列中的每一列生成格式?

    生成虚拟数据集:master(10M 记录)和 transaction(15M 记录)数据集: (原始的transaction 数据集在下面进行了规范化,以准备创建每个事务变量的格式)

    data master ;
      do key=1 to 10000000 ;
        output ;
      end ;
    run ;
    
    data transaction(keep=start label fmtname) ;
      array t(10) col1-col10 ;
          do i=1 to 10 ;
            do start=1 to 15000000 ;
            fmtname=cat('COL',i,'F') ;
            label=cat('Column ',i,' has value ',start) ;
            output ;
        end ;
      end ;
    run ;
    

    读入格式:

    proc format cntlin=transaction ;
    run ;
    

    然后将格式应用于master 数据集:

    data want ;
      set master ;
        col1=put(key,COL1F.) ;
        col2=put(key,COL2F.) ;
        col3=put(key,COL3F.) ;
        col4=put(key,COL4F.) ;
        col5=put(key,COL5F.) ;
        col6=put(key,COL6F.) ;
        col7=put(key,COL7F.) ;
        col8=put(key,COL8F.) ;
        col9=put(key,COL9F.) ;
        col10=put(key,COL10F.) ;
        output ;
    run ;
    

    【讨论】:

    • 这当然看起来是一个合理的方法,虽然我认为你上面的实际实现需要清理一些。
    【解决方案3】:

    总结

    1. lib1.changes_1 汇总到执行全扫描的临时表(例如lib1.changes_1_rolledup)中。 (lib1.changes_1_rolledup的物理属性要谨慎设置。)
    2. 使用lib1.changes_1_rolledup 的数据更新lib1.master1,对两者之一执行完整扫描,对另一个执行索引扫描。 (哪一项全面扫描取决于实际数据。)

    解释

    首先,为了获得更好的性能,您很可能必须深入到底层 DBMS 级别并利用其功能。

    那么,优化技术实际上取决于数据的性质。

    我想[几乎]所有的lib1.changes_1(key) 值都匹配lib1.master1(key) 值之一(lib1.changes_1 甚至可能是lib1.master1 的详细信息表)。此外,我们需要应用来自lib1.changes_1 的所有更改。这意味着无论如何我们都必须读取来自lib1.changes_1 的所有记录。如果是这样,最有效的方法是lib1.changes_1完全扫描,但只执行一次。此完整扫描会将 lib1.changes_1 中的所有更改汇总到此类定义的(可能是临时的)表中:

    -- pseudo code:
    create [temporary] table lib1.changes_1_rolledup 
      <set physical attributes depending on your data nature - see below>
      as select key, col1, col2, col3, col4, col5, col6, col7, col8, col9
      from lib1.changes_1
      where 1 = 2
    

    这个汇总的更改表可能包含不超过 10M 的记录,并且根据 colX 大小的不同,存储空间要求可能相对较小。更重要的是,每个key 值只有一条记录,这可能是一个很大的好处。

    我们需要评估lib1.master1 中有多少记录在lib1.changes_1_rolledup 中表示,在主从关系的情况下,这只是记录数的比较。

    如果lib1.changes_1_rolledup 只有两棵树的时间(速率实际上取决于 DBMS)或小于lib1.master1(就记录数而言),最有效的方法是对lib1.master1 使用来自lib1.changes_1_rolledup 的相应值更新来自lib1.master1 的每条记录(一次所有9 个colX 值)。 (当然,应尽可能在单个更新查询中实现全扫描更新。)在这种情况下,lib1.changes_1_rolledup 表的物理属性必须针对键查找进行调整。如果可用,我建议使用类似于 Oracle 的索引组织表的技术。

    如果lib1.changes_1_rolleduplib1.master1 短几倍,那么lib1.changes_1_rolleduplib1.master1 更新相应记录的效率更高。在这种情况下,lib1.changes_1_rolledup 的物理存储应该针对全扫描进行调整,并且可能按照lib1.master1(key) 出现的顺序进行调整(例如,自动递增代理键可能就是这种情况)。

    附: 为了简化描述,我省略了 lib1.changes_1 仅包含某个键的部分列更新的情况。这可以通过在lib1.changes_1_rolledup 中添加标志字段并按如下方式调整更新来解决:

    -- pseudo code:
    update lib1.master1 m
      set m.col1 = (
            select case c.col1 then select c.col1 else m.col1 end
              from lib1.changes_1_rolledup c
              where c.key = m.key
          )
      ..............
    

    【讨论】:

      【解决方案4】:

      要替换一列,最简单的格式(大致类似于 Bendy 的方法)。

      要替换总是来自同一行的十列,我推荐使用哈希表。通常与单一格式的速度大致相同。 (格式实际上在 10MM 行标记处可能会有点慢,所以这甚至可能比一个更快。)

      这在我的笔记本电脑上花费了大约 30 秒(CPU 时间和实时时间;我有一个 SSD,所以它们是相似的。在 HDD 上这可能是 30 秒 CPU 时间和几分钟实时时间。)

      *make some dummy data;
      data maindata;
        array col(10);
        do _i = 1 to dim(col);
          col[_i] = _i;
        end;
        do key = 1 to 1e7;
          output;
        end;
      run;
      data updatedata;
        array col(10);
        do _i = 1 to dim(col);
          col[_i] = 100+_i;
        end;
        do key = 1 to 3e7 by 2;
          output;
        end;
      run;
      
      *using MODIFY here which is identical in function to SQL UPDATE - does not make a new dataset;
      data maindata;  
        if _n_=1 then do;
          declare hash ud(dataset:'updatedata');
          ud.defineKey('key');
          ud.defineData('col1', 'col2', 'col3', 'col4', 'col5', 'col6', 'col7', 'col8', 'col9', 'col10');
          ud.defineDone();
        end;
        modify maindata;
        rcUpdate = ud.find();
        if rcUpdate=0 then replace;
      run;
      

      【讨论】:

      • 我喜欢这种方法,因为它会更新数据集并使用哈希表来查找数据。当您有这么多需要更新的行时,很难在速度上超越它。
      • 像魔术一样工作!!它在 1 分钟内运行在 3 MM 的子集上。非常感谢乔,从不知道哈希表如此高效。 :)
      • 哈希表和格式的效率大致相同——两者都在 O(1) 时间内搜索。当然,两者都有内存限制——对于一个 10GB 的表,如果没有 10GB 的 RAM,你就无法构建一个哈希表,虽然我认为格式仍然有效,但它们是 sloooowww..
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-04-01
      • 1970-01-01
      • 2019-11-21
      • 1970-01-01
      • 2016-10-28
      • 2023-03-08
      • 1970-01-01
      相关资源
      最近更新 更多