优化 SAS Proc SQL 查询
Optimize SAS Proc SQL query
我有 2 个大的 table 试图加入,以便根据第二个 table 的字段对第一个记录进行分组。左边 table 有大约 5000 万条事件记录,右边 table 有大约 3500 万条月间隔记录。每月间隔在 subjID 级别,因此我无法通过仅保留开始日期和结束日期来减少右侧 table 的大小。目前执行连接大约需要 40 - 60 分钟。
我尝试在 subjID、eventDate、startDate 和 endDate 上创建简单的索引,但它似乎并没有提高性能(创建索引在大约 5 分钟内完成,连接在 38 分钟内完成)。
我可以使用任何其他选项来改进处理吗?
剩余 Table 个 subjID 级别的事件:
data eventsTable;
input @1 subjID 8.
@10 eventDate date9.;
format eventDate mmddyy10.;
datalines;
101 01AUG2011
101 28AUG2011
101 30AUG2011
101 01SEP2011
101 12SEP2011
101 28SEP2011
102 01JAN2015
102 15JAN2015
102 01FEB2015
102 16FEB2015
;
run;
右侧 Table subjID 级别的每月间隔。如果事件发生在开始日期和结束日期之间,我将尝试将 endDate 带入事件:
data monthlyTable;
input @1 subjID 8.
@10 startDate date9.
@22 endDate date9.;
format startDate endDate mmddyy10.;
datalines;
101 28JUL2011 30AUG2011
101 30AUG2011 28SEP2011
101 28SEP2011 28OCT2011
102 01DEC2014 02JAN2015
102 02JAN2015 02FEB2015
102 02FEB2015 02MAR2015
;
run;
输出:
proc sql;
create table wantTable as
select a.*,
endDate as monthlyDate
from eventsTable a left join monthlyTable b on
a.subjID = b.subjID
where a.eventDate > b.startDate and a.eventDate <= b.endDate
order by subjID, eventDate;
quit;
您查询的最佳索引是 monthlyTable(subjId, startDate, endDate)
上的复合索引。但是,我不确定它是否会在 SAS 的性能方面有很大的改进。
如果您有足够的内存并且只需要 monthlyTable
中的 enddate
,您可能会发现格式合并是一种更有效的方法。但是,如果两个数据集都很大,那么您只能希望进行如此多的优化,因为您始终必须至少完整读取每个数据集。
data t_format(keep = fmtname--hlo) /view = t_format;
set monthlytable(keep = subjID startdate enddate) end = eof;
retain fmtname 'myinfmt' type 'i';
length start end ; /*Increase for IDs longer than 8 digits*/
start = cats(put(subjID,z8.),put(startdate + 1,yymmdd10.));
end = cats(put(subjID,z8.),put(enddate,yymmdd10.));
label = enddate;
output;
if eof then do;
hlo = 'O';
label = .N;
output;
end;
run;
proc format cntlin = t_format;
run;
data want;
set eventstable;
enddate = input(cats(put(subjID,z8.),put(eventdate,yymmdd10.)),myinfmt18.);
format enddate yymmdd10.;
run;
注意 yymmdd10.
和 z8.
格式的使用 - 这些确保键的长度始终相同,避免歧义,并且查找值的范围在以下情况下按升序正确指定创建数字信息 myinfmt
。我想,严格来说,这是一个 informat 合并而不是 format 合并,但它是同一种想法。
如果您想通过这种方法 return 多个查找变量,您需要在定义格式时将它们连接在一起,然后在应用格式后拆分它们。
我估计这种方法需要大约 1.5GB 的内存用于您指定的数据集 - 即(每个日期范围 18 字节 x 2 + 格式化值 8 字节)x 35m 行。根据您 ID 的长度,这可能会有所不同。
如果您需要多个查找值,那么您可以使用散列合并来做类似的事情,但我怀疑在这种情况下格式合并更有效。
一种可能的散列合并方法如下所示:
data t_lookup /view= t_lookup;
set monthlytable;
by subjID;
if first.subjID then id_range_count = 0;
id_range_count + 1;
run;
data want;
set eventstable;
if _n_ = 1 then do;
if 0 then set monthlytable(keep = subjID startdate enddate); /*Add extra lookup vars here as needed*/
declare hash h(dataset:"t_lookup");
rc = h.definekey("subjID","id_range_count");
rc = h.definedata("startdate","enddate"); /*Add extra lookup vars here as needed*/
rc = h.definedone();
end;
match = 0;
rc = 0;
do id_range_count = 1 by 1 while(rc = 0 and match = 0);
rc = h.find();
match = startdate < eventdate <= enddate;
end;
if match = 0 then call missing(startdate,enddate);
drop rc match id_range_count;
run;
与创建索引相比,我在 pre-sorting 数据集上的运气更好。但是,pre-sorting 可能需要很长时间,具体取决于数据集的大小以及您对它们进行排序的内容。它可能需要比原始 SQL 查询更长的时间,因此测试变得很重要。
尝试运行
PROC SORT DATA=eventsTable ;
BY subjID eventDate ;
RUN ;
PROC SORT DATA=monthlyTable ;
BY subjID startDate endDate ;
RUN ;
在您的 PROC SQL 之前。我唯一的解释是 SAS 识别 SORT BY header 信息并且不需要扫描整个表来寻找连接,因为给定的 subjID 可能只在几个连续的页面上。在几个连续的页面上也会减少 I/O。
我有 2 个大的 table 试图加入,以便根据第二个 table 的字段对第一个记录进行分组。左边 table 有大约 5000 万条事件记录,右边 table 有大约 3500 万条月间隔记录。每月间隔在 subjID 级别,因此我无法通过仅保留开始日期和结束日期来减少右侧 table 的大小。目前执行连接大约需要 40 - 60 分钟。
我尝试在 subjID、eventDate、startDate 和 endDate 上创建简单的索引,但它似乎并没有提高性能(创建索引在大约 5 分钟内完成,连接在 38 分钟内完成)。
我可以使用任何其他选项来改进处理吗?
剩余 Table 个 subjID 级别的事件:
data eventsTable;
input @1 subjID 8.
@10 eventDate date9.;
format eventDate mmddyy10.;
datalines;
101 01AUG2011
101 28AUG2011
101 30AUG2011
101 01SEP2011
101 12SEP2011
101 28SEP2011
102 01JAN2015
102 15JAN2015
102 01FEB2015
102 16FEB2015
;
run;
右侧 Table subjID 级别的每月间隔。如果事件发生在开始日期和结束日期之间,我将尝试将 endDate 带入事件:
data monthlyTable;
input @1 subjID 8.
@10 startDate date9.
@22 endDate date9.;
format startDate endDate mmddyy10.;
datalines;
101 28JUL2011 30AUG2011
101 30AUG2011 28SEP2011
101 28SEP2011 28OCT2011
102 01DEC2014 02JAN2015
102 02JAN2015 02FEB2015
102 02FEB2015 02MAR2015
;
run;
输出:
proc sql;
create table wantTable as
select a.*,
endDate as monthlyDate
from eventsTable a left join monthlyTable b on
a.subjID = b.subjID
where a.eventDate > b.startDate and a.eventDate <= b.endDate
order by subjID, eventDate;
quit;
您查询的最佳索引是 monthlyTable(subjId, startDate, endDate)
上的复合索引。但是,我不确定它是否会在 SAS 的性能方面有很大的改进。
如果您有足够的内存并且只需要 monthlyTable
中的 enddate
,您可能会发现格式合并是一种更有效的方法。但是,如果两个数据集都很大,那么您只能希望进行如此多的优化,因为您始终必须至少完整读取每个数据集。
data t_format(keep = fmtname--hlo) /view = t_format;
set monthlytable(keep = subjID startdate enddate) end = eof;
retain fmtname 'myinfmt' type 'i';
length start end ; /*Increase for IDs longer than 8 digits*/
start = cats(put(subjID,z8.),put(startdate + 1,yymmdd10.));
end = cats(put(subjID,z8.),put(enddate,yymmdd10.));
label = enddate;
output;
if eof then do;
hlo = 'O';
label = .N;
output;
end;
run;
proc format cntlin = t_format;
run;
data want;
set eventstable;
enddate = input(cats(put(subjID,z8.),put(eventdate,yymmdd10.)),myinfmt18.);
format enddate yymmdd10.;
run;
注意 yymmdd10.
和 z8.
格式的使用 - 这些确保键的长度始终相同,避免歧义,并且查找值的范围在以下情况下按升序正确指定创建数字信息 myinfmt
。我想,严格来说,这是一个 informat 合并而不是 format 合并,但它是同一种想法。
如果您想通过这种方法 return 多个查找变量,您需要在定义格式时将它们连接在一起,然后在应用格式后拆分它们。
我估计这种方法需要大约 1.5GB 的内存用于您指定的数据集 - 即(每个日期范围 18 字节 x 2 + 格式化值 8 字节)x 35m 行。根据您 ID 的长度,这可能会有所不同。
如果您需要多个查找值,那么您可以使用散列合并来做类似的事情,但我怀疑在这种情况下格式合并更有效。
一种可能的散列合并方法如下所示:
data t_lookup /view= t_lookup;
set monthlytable;
by subjID;
if first.subjID then id_range_count = 0;
id_range_count + 1;
run;
data want;
set eventstable;
if _n_ = 1 then do;
if 0 then set monthlytable(keep = subjID startdate enddate); /*Add extra lookup vars here as needed*/
declare hash h(dataset:"t_lookup");
rc = h.definekey("subjID","id_range_count");
rc = h.definedata("startdate","enddate"); /*Add extra lookup vars here as needed*/
rc = h.definedone();
end;
match = 0;
rc = 0;
do id_range_count = 1 by 1 while(rc = 0 and match = 0);
rc = h.find();
match = startdate < eventdate <= enddate;
end;
if match = 0 then call missing(startdate,enddate);
drop rc match id_range_count;
run;
与创建索引相比,我在 pre-sorting 数据集上的运气更好。但是,pre-sorting 可能需要很长时间,具体取决于数据集的大小以及您对它们进行排序的内容。它可能需要比原始 SQL 查询更长的时间,因此测试变得很重要。
尝试运行
PROC SORT DATA=eventsTable ;
BY subjID eventDate ;
RUN ;
PROC SORT DATA=monthlyTable ;
BY subjID startDate endDate ;
RUN ;
在您的 PROC SQL 之前。我唯一的解释是 SAS 识别 SORT BY header 信息并且不需要扫描整个表来寻找连接,因为给定的 subjID 可能只在几个连续的页面上。在几个连续的页面上也会减少 I/O。