基于匹配字符串的一部分有效地加入/合并

时间:2013-09-09 23:40:03

标签: sql sas

我正在尝试根据第一个表中的字符串是否包含在第二个表中的长字符串的一部分中来连接两个表。我在SAS中使用PROC SQL,但也可以使用数据步骤而不是SQL查询。

此代码适用于较小的数据集,但由于必须进行大量比较,因此很快就会陷入困境。如果它是一个简单的相等检查就好了,但是必须使用index()函数会使它变得困难。

proc sql noprint;
  create table matched as
  select A.*, B.* 
  from  search_notes as B,
        names as A
  where index(B.notes,A.first) or 
        index(B.notes,A.last)
  order by names.name, notes.id;
quit;
run;

B.notes是一个2000字符(有时是完全填充的)文本块,我正在查找包含A中的名字或姓氏的任何结果。

我不认为通过两步完成它可以获得任何速度优势,因为它已经必须将A的每一行与B的每一行进行比较(因此检查名字和姓氏不是瓶颈)

当我运行它时,我的日志中会显示NOTE: The execution of this query involves performing one or more Cartesian product joins that can not be optimized.。运行A = 4000次观察和B = 100,000次观察需要30分钟才能产生~1000次匹配。

有没有办法优化这个?

3 个答案:

答案 0 :(得分:0)

笛卡尔积可能最适合您的数据,但这里有一些尝试。我正在做的是在数据步骤中使用CALL EXECUTE()来构建步骤匹配到数据步骤。这意味着您只需要遍历每个表一次。但是,您的书面数据步骤中将包含4000个IF / THEN子句。这样做会使我的示例数据的运行时间从55秒增加到40秒。如果比率保持不变,这将比你的30分钟减少大约24分钟。

我会打开这个问题。也许有人可以提出更好的方法。

%let n=50;
data B;
format notes $&n..;
choose = "ABCDEFGHIJLKMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
do j=1 to 9000000;
    notes = "";
    do i=1 to floor(5 + ranuni(123)*(&n-5));
        r = floor(ranuni(123)*62+1);
        notes = catt(notes,substr(choose,r,1));

    end;
    output;
    drop r choose i;
end;
run;

data a;
choose = "ABCDEFGHIJLKMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
format first last $2.;
do i=1 to 62 by 2;
    first = strip(substr(choose,i,1));
    first = catt(first,first);
    last =  strip(substr(choose,i+1,1));
    last = catt(last,last);
    output;
end;
drop choose ;
run;

proc sql noprint;
  create table matched as
  select A.*, B.* 
  from  B as B,
        A as A
  where index(B.notes,A.first) or 
        index(B.notes,A.last)
  order by B.notes, a.i;
quit;

options nosource;
data _null_;
set a end=l;
if _n_ = 1 then do;
    call execute("data matched2; set B;");
    call execute("format First Last $2. i best.;");
end;

format outStr $200.;
outStr = "if index(notes,'" || first || "') or index(notes,'" || last || "') then do;";
call execute(outStr);

outStr = "first = '" || first || "';";
call execute(outStr);
outStr = "last = '" || last || "';";
call execute(outStr);
outStr = "i = " || i || ";";
call execute(outStr);
call execute("output; end;");

if l then do;
    call execute("run;");
end;
run;

proc sort data=matched2;
by notes i;
run;

答案 1 :(得分:0)

这听起来不像是PROC SQL的好选择。如果我理解正确,您希望将search_notes中的每一行与names中的每一行进行比较(因此是笛卡尔积)。更传统的数据步骤程序可能更容易理解,也许更有效:

data matched;
   set search_notes;
   do _i_=1 to nobs;
      set names point=_i_ nobs=nobs;
      if index(notes,first) 
      or index(notes,last) then output;
      end;
   drop _i_;
run;
proc sort data=matched;
   by vendor_name, claimant_id;
run;

答案 2 :(得分:0)

这是一个部分答案,使其运行速度提高4-5倍,但它并不理想(在我的情况下有所帮助,但在优化笛卡尔积产品连接的一般情况下不一定有效)。

我最初有4个单独的index()语句,就像我的例子一样(我的简化样本有A.first和A.last的2个)。

我能够将所有4个index()语句(加上我要添加的第5个)重构为一个解决相同问题的正则表达式。它不会返回相同的结果集,但我认为它实际上会返回更好的结果而不是5个单独的索引,因为您可以指定单词边缘。

在我清理匹配名称的datastep中,我创建了以下模式:

pattern = cats('/\b(',substr(upcase(first_name),1,1),'|',upcase(first_name),').?\s?',upcase(last_name),'\b/');

这应该创建一个/\b(F|FIRST).?\s?LAST\b/的正则表达式,它将匹配F. Last,First Last,flast @email.com等等(有些组合它没有提取,但我我只关注我在数据中观察到的组合。使用'\ b'也不允许FLAST恰好与单词的开头/结尾相同(例如“Edward Lo”与“Eloquent”匹配),我发现很难用index()

然后我像这样做我的sql join:

proc sql noprint;
create table matched as
  select  B.*, 
          prxparse(B.pattern) as prxm, 
          A.* 
  from  search_text as A,
        search_names as B
  where prxmatch(calculated prxm,A.notes)
  order by A.id;
quit;
run;

能够在B中为每个名称编译一次正则表达式,然后在A中的每个文本上运行它似乎比几个索引语句快得多(不确定正则表达式与单个索引的情况) )。

使用A = 250,000 Obs和B = 4,000 Obs运行它,为index()方法花费了90分钟的CPU时间,而使用prxmatch()执行相同操作只需要20分钟的CPU时间。