SAS PROC SQL:如何快速搜索变量是否包含完整的子字符串?
SAS PROC SQL: How to quickly search if a variable contain a full substring?
我在工作中遇到这样的问题:
列Code
的值如1000、1200、A1000、B1200、AAA、BBB等。目前它被spaces分隔,由于数据不佳,有时会超过一个输入。我正在尝试检查记录是否包含我感兴趣的代码。
Interested_Code
: 1000 or A1000 or 444 or 555 or A555 etc.
我知道一个简单的解决方法from this answer:
A.CODE LIKE CAT('% ', T3.Interested_Code, ' %')
我在 A.CODE
后附加了前导和尾随 space 以确保返回 "full" 完全匹配。因为如果我只是做
A.CODE LIKE CAT('%', T3.Interested_Code, '%') or
A.CODE CONTAINS T3.Interested_Code
我将在包含代码 = A1000
的行中得到代码 = 1000
的误报,这与部分代码匹配,但不一定是正确的结果。
我的代码可以在上面运行,但是它进行了太多测试并且非常慢。 PROC SQL 有没有更快或更聪明的方法?主要的 table 大约有 100k 行,每行大约有 10-20 个代码。感兴趣的代码大约有 8k 个值。谢谢。
您可以使用 FINDW
或 INDEXW
,它们会找到 "words"(默认情况下,用 space 或类似的分隔)。这可能比您的解决方案更好,特别是因为您找不到
"1000 "
因为它不是以 space 开头的,所以您这样做的方式。
proc sql;
create table final_codes as
select codes.*
from codes where exists (
select 1 from interested_codes
where findw(codes.code,trim(interested_codes.code)) > 0)
;
quit;
但是,这实际上是一个笛卡尔连接,非常慢。它必须加入所有可能的组合——8000 乘以 100,000,或者实际上是 8 亿个临时行,然后再进行子集化。无论您做什么,都不会那么快。
在数据步骤中执行此操作会更有效率,特别是因为一旦找到匹配项就可以更轻松地停止。您可以将 interested_codes table 放入散列 table 或临时数组中,然后根据您的匹配频率搜索每个 code[=24 可能会更快=] 在 interested_codes table 中,或相反,但无论哪种方式在找到匹配项时停止(而不是进行所有可能的组合)。
尝试使用正则表达式:
data want;
set have;
where prxmatch('/^1000$/',strip(code));
run;
主要有两个问题
- 代码数据可能包含字母和数字之间的space
- 搜索space超过了几个目标代码
解决方案的特点是
- 规范化代码数据
- 搜索任何目标代码
2a.将记录标记为 匹配任何
2b.阐明匹配的目标(每个目标代码的二进制变量,或每个匹配目标的结果行)
您必须对您的安装进行基准测试,以比较各种方法之间的性能。
此示例代码是 2a 的模型。宏构建了一个 SQL 案例来标记 'any match' 条件。结果查询是昂贵的,因为它需要为每一行规范化正则表达式,并且所有情况条件都必须失败才能从结果集中排除行。
data have;
code = 'A 1000 1111 C333 555 A111 Z 999 B 222'; output;
code = 'ZZZZZ 1121'; output;
code = 'A 1000'; output;
code = 'AB1000'; output;
run;
%macro withAnyOf (data=have, out=want, targets=);
%local i qTarget N;
%let N = %sysfunc(countw(&targets,%str( )));
%put NOTE: &=N;
%do i = 1 %to &N;
%local clause&i;
%let qTarget = %sysfunc(quote(%qscan(&targets,&i,%str( ))));
%let clause&i = when indexw (calculated codeCleaned, &qTarget) then 1;
%put NOTE: &&clause&i;
%end;
proc sql;
create table &out(drop=codeCleaned) as
select
*
, ' ' || prxchange('s/([A-Z]) +//',-1,code) || ' ' as codeCleaned
from &data
where
case
%do i = 1 %to &N;
&&clause&i
%end;
else 0
end
;
quit;
%mend;
options mprint;
%withAnyOf (targets=1000 A1000 444 555 A555)
我在工作中遇到这样的问题:
列Code
的值如1000、1200、A1000、B1200、AAA、BBB等。目前它被spaces分隔,由于数据不佳,有时会超过一个输入。我正在尝试检查记录是否包含我感兴趣的代码。
Interested_Code
: 1000 or A1000 or 444 or 555 or A555 etc.
我知道一个简单的解决方法from this answer:
A.CODE LIKE CAT('% ', T3.Interested_Code, ' %')
我在 A.CODE
后附加了前导和尾随 space 以确保返回 "full" 完全匹配。因为如果我只是做
A.CODE LIKE CAT('%', T3.Interested_Code, '%') or
A.CODE CONTAINS T3.Interested_Code
我将在包含代码 = A1000
的行中得到代码 = 1000
的误报,这与部分代码匹配,但不一定是正确的结果。
我的代码可以在上面运行,但是它进行了太多测试并且非常慢。 PROC SQL 有没有更快或更聪明的方法?主要的 table 大约有 100k 行,每行大约有 10-20 个代码。感兴趣的代码大约有 8k 个值。谢谢。
您可以使用 FINDW
或 INDEXW
,它们会找到 "words"(默认情况下,用 space 或类似的分隔)。这可能比您的解决方案更好,特别是因为您找不到
"1000 "
因为它不是以 space 开头的,所以您这样做的方式。
proc sql;
create table final_codes as
select codes.*
from codes where exists (
select 1 from interested_codes
where findw(codes.code,trim(interested_codes.code)) > 0)
;
quit;
但是,这实际上是一个笛卡尔连接,非常慢。它必须加入所有可能的组合——8000 乘以 100,000,或者实际上是 8 亿个临时行,然后再进行子集化。无论您做什么,都不会那么快。
在数据步骤中执行此操作会更有效率,特别是因为一旦找到匹配项就可以更轻松地停止。您可以将 interested_codes table 放入散列 table 或临时数组中,然后根据您的匹配频率搜索每个 code[=24 可能会更快=] 在 interested_codes table 中,或相反,但无论哪种方式在找到匹配项时停止(而不是进行所有可能的组合)。
尝试使用正则表达式:
data want;
set have;
where prxmatch('/^1000$/',strip(code));
run;
主要有两个问题
- 代码数据可能包含字母和数字之间的space
- 搜索space超过了几个目标代码
解决方案的特点是
- 规范化代码数据
- 搜索任何目标代码
2a.将记录标记为 匹配任何
2b.阐明匹配的目标(每个目标代码的二进制变量,或每个匹配目标的结果行)
您必须对您的安装进行基准测试,以比较各种方法之间的性能。
此示例代码是 2a 的模型。宏构建了一个 SQL 案例来标记 'any match' 条件。结果查询是昂贵的,因为它需要为每一行规范化正则表达式,并且所有情况条件都必须失败才能从结果集中排除行。
data have;
code = 'A 1000 1111 C333 555 A111 Z 999 B 222'; output;
code = 'ZZZZZ 1121'; output;
code = 'A 1000'; output;
code = 'AB1000'; output;
run;
%macro withAnyOf (data=have, out=want, targets=);
%local i qTarget N;
%let N = %sysfunc(countw(&targets,%str( )));
%put NOTE: &=N;
%do i = 1 %to &N;
%local clause&i;
%let qTarget = %sysfunc(quote(%qscan(&targets,&i,%str( ))));
%let clause&i = when indexw (calculated codeCleaned, &qTarget) then 1;
%put NOTE: &&clause&i;
%end;
proc sql;
create table &out(drop=codeCleaned) as
select
*
, ' ' || prxchange('s/([A-Z]) +//',-1,code) || ' ' as codeCleaned
from &data
where
case
%do i = 1 %to &N;
&&clause&i
%end;
else 0
end
;
quit;
%mend;
options mprint;
%withAnyOf (targets=1000 A1000 444 555 A555)