SAS PROC SQL:为什么 IN() 中的硬编码值不同?

SAS PROC SQL: why there is a different when hard coding values within IN()?

我对 SAS PROC SQL 中的以下 2 个代码有疑问。

代码 1:(标准图书版本)

CREATE TABLE WORK.OUTPUT AS 
SELECT 
    "CLAIM" AS SOURCE,
    a.CLAIMID, 
    a.DXCODE
FROM 
    DW.CLAIMS_BAV AS a
WHERE 
    a.SITEID = '0001'
    AND a.CLAIMID IN (SELECT CLAIMID FROM WORK.INPUT)

代码 2:(实践中速度更快的方法)

CREATE TABLE WORK.OUTPUT AS 
SELECT 
    "CLAIM" AS SOURCE,
    a.CLAIMID, 
    a.DXCODE
FROM 
    DW.CLAIMS_BAV AS a
WHERE 
    a.SITEID = '0001'
    AND a.CLAIMID IN ('10001', '10002', '10003', ... '15000')

当我尝试在#1 中使用子查询更优雅地完成它时,运行 时间增加了 50 分钟以上。但是使用代码 2 在 3 分钟内输入相同的 returns。这是为什么?请注意,使用 INNER JOIN 也一样慢 (after reading this)。输入是 5000+ CLAIMID,我每天手动粘贴到 IN('...') 块中。

PS:CLAIMID是编造的,现实生活中是随机的。

CLAIMID 在 DW.CLAIMS 中编入​​索引。我正在使用 SAS PROC SQL 访问 Oracle 数据库。这是怎么回事,有没有更好的办法?谢谢!

我认为您正在处理本地数据和服务器上的数据。当 SAS 处理来自不同来源(数据库)的数据时,它会将所有数据带入 SAS 进行处理,这可能非常非常慢。

相反,您可以创建一个宏变量并在您的查询中使用它。如果它是 5000,它应该适合一个宏变量,假设每个长度小于 13 个字符。宏变量大小限制为 64K 个字符,因此它取决于变量的长度。如果没有,您可以改为创建一个宏。

proc sql noprint;
   select quote(claimID, "'") into : claim_list separated by ", " from input;
quit;



proc sql;
CREATE TABLE WORK.OUTPUT AS 
SELECT 
"CLAIM" AS SOURCE,
a.CLAIMID, 
a.DXCODE
FROM  DW.CLAIMS_BAV AS a
WHERE 
a.SITEID = '0001'
AND a.CLAIMID IN (&claim_list.);
quit;

我不知道我可以告诉你为什么 SAS 一开始这么慢 select;在那种情况下显然没有优化某些东西。

如果非要我猜的话,我猜 SAS 在第一种情况下决定它不能使用直通 SQL,所以它正在下载整个 table 和然后 运行 连接这个 SAS 端,而在第二种情况下,它将查询传递到 SQL 数据库,并且只将结果行传回。

但是无论如何,有几种方法可以解决这个问题。这是一个:使用宏变量来精确地执行您正在执行的粘贴操作!

proc sql;
select quote(strip(claimid)) into :claimlist separated by ',' 
from work.input
;

CREATE TABLE WORK.OUTPUT AS 
SELECT 
    "CLAIM" AS SOURCE,
    a.CLAIMID, 
    a.DXCODE
FROM 
    DW.CLAIMS_BAV AS a
WHERE 
    a.SITEID = '0001'
    AND a.CLAIMID IN (&claimlist.)
;
quit;

Tada,你不用再碰这个了,它和你碰过的copy/paste一样。

一些额外的注释给出了一些评论:

如果 CLAIMID 小于 15,您可能有 space 填充,所以我添加了 strip 来删除它们。字符串比较并不重要——除非你可能 运行 不使用宏语言,而且我担心某些 DBMS 可能实际上关心填充。如果 15 是恒定长度,则可以省略 strip

宏变量 运行 在 space 中最大 64K。如果你有 15 个字符的变量加上“”二加逗号一,你就有 18 个字符;您有超过 3500 个值的空间。不幸的是,这还不到 5000。

在这种情况下,您可以将字段拆分为两个宏变量(希望足够简单,使用 obsfirstobs)或者您可以做一些其他的解决方案。

  1. work.input 数据集转移到 DW 库名中,然后在 SQL 中进行连接。
  2. 将 claimID 的内容放入文件而不是放入宏变量,然后 %include 该文件。
  3. 使用call execute执行整个过程SQL.

这是 CALL EXECUTE 的一个例子。

data _null_;
  set work.input end=eof;
  if _n_=1 then do;
    call execute('CREATE TABLE WORK.OUTPUT AS 
SELECT 
    "CLAIM" AS SOURCE,
    a.CLAIMID, 
    a.DXCODE
FROM 
    DW.CLAIMS_BAV AS a
WHERE 
    a.SITEID = "0001"
    AND a.CLAIMID IN (');      *the part of the SQL query before the list of IDs;
  end;
  call execute(quote(claimID) || ' ');
  if EOF then do;
    call execute('); QUIT;');  *the part of the SQL query after the list of IDs;
  end;
run;

这实际上与 %INCLUDE 解决方案几乎相同,只是您 put 将这些内容写入文本文件而不是 CALL EXECUTE ,然后您 %INCLUDE那个文本文件。

使用不带子查询的 In() 肯定更快,但要记住的其他性能考虑因素是 运行 时的网络和计算服务器 load/traffic;假设您 运行 在客户端/服务器配置上。

如果打算使用SQL select 宏变量解决方案;请记住不同值的计数和您在宏中保存的字符串的长度,因为存在大小限制。

请务必使用

option sastrace=',,,ds' sastraceloc=saslog nostsuffix;

接收有关 SAS/Aceess 引擎如何将您的代码转换为数据库语句的信息。

为了给 SAS 提示从你的 IN (SELECT .. 查询动态构建 IN (1,2,3, ..) 子句

  1. MULTI_DATASRC_OPT=IN_CLAUSE 添加到您的 libname DW ... 声明和
  2. dbmaster 数据集选项添加到 "master" table

喜欢以下查询之一:

CREATE TABLE WORK.OUTPUT AS 
SELECT 
    "CLAIM" AS SOURCE,
    a.CLAIMID, 
    a.DXCODE
FROM 
    DW.CLAIMS_BAV (dbmaster=yes) AS a
WHERE 
    a.SITEID = '0001'
    AND a.CLAIMID IN (SELECT CLAIMID FROM WORK.INPUT)

CREATE TABLE WORK.OUTPUT AS 
SELECT 
    "CLAIM" AS SOURCE,
    a.CLAIMID, 
    a.DXCODE
FROM 
    DW.CLAIMS_BAV (dbmaster=yes) AS a
    inner join WORK.INPUT AS b
    on a.CLAIMID = b.CLAIMID
WHERE 
    a.SITEID = '0001'

您还可以将 In() 值保存在 table 中,然后只进行连接。

PROC SQL;
/*CLAIM ID Table*/
CREATE TABLE WORK.OUTPUT1 AS 
SELECT 
    "CLAIM" AS SOURCE,
    a.CLAIMID, 
    a.DXCODE
FROM 
    DW.CLAIMS_BAV AS  a
WHERE 
    a.SITEID = '0001';
/*ID Lookup Table*/
CREATE TABLE WORK.OUTPUT2 AS 
SELECT
DISTINCT b.CLAIMID  FROM WORK.INPUT AS b
;
/*Inner Join Table / AKA lookup join*/
CREATE TABLE WORK.Final AS 
SELECT 
a.SOURCE,    a.CLAIMID,     a.DXCODE
FROM WORK.OUTPUT1 AS a INNER JOIN WORK.OUTPUT2 AS b
ON a.CLAIMID = b.CLAIMID
;
QUIT;