PROC SQL 大型数据集的更新效率

PROC SQL Update Efficiency for Large Datasets

我有一个包含 1000 万行和 1800 列的 SAS 主数据集。我需要使用包含 1500 万条记录的事务数据集更新 10 列,仅针对具有匹配键的记录。 我使用以下代码尝试 运行 a 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 列将再次成为一项乏味的任务。

有faster/more有效的技巧吗?

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

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

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

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

生成虚拟数据集:master(1000 万条记录)和transaction(1500 万条记录)数据集: (原始 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 ;

总结

  1. 汇总 lib1.changes_1 到临时 table(例如,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 甚至可能是 table 的 lib1.master1 ]).此外,我们需要应用 lib1.changes_1 中的所有更改。这意味着无论如何我们都必须读取 lib1.changes_1 中的所有记录。如果是这样,最有效的方法是 lib1.changes_1 table 全面扫描 只执行一次 。此完整扫描会将所有更改从 lib1.changes_1 汇总为(可能是临时的)table 的定义:

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

此汇总更改 table 可能包含不超过 1000 万条记录,并且根据 colX 大小的不同,在存储 space 要求方面可能相对较小。更重要的是,每个 key 值只有一个记录,这可能是一个很大的好处。

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

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

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

P.S。 为了简化描述,我省略了 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
      )
  ..............

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

要替换始终来自同一行的十列,我建议使用散列 table。通常,速度与单一格式大致相同。 (格式实际上在 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;