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 ;
总结
- 汇总
lib1.changes_1
到临时 table(例如,lib1.changes_1_rolledup
)执行完整扫描。 (lib1.changes_1_rolledup
的物理属性要慎重设置。)
- 使用
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_rolledup
比 lib1.master1
短几倍,那么 lib1.changes_1_rolledup
从 lib1.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;
我有一个包含 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 ;
总结
- 汇总
lib1.changes_1
到临时 table(例如,lib1.changes_1_rolledup
)执行完整扫描。 (lib1.changes_1_rolledup
的物理属性要慎重设置。) - 使用
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_rolledup
比 lib1.master1
短几倍,那么 lib1.changes_1_rolledup
从 lib1.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;