[英]Writing Efficient Queries in SAS Using Proc sql with Teradata
编辑:这是一套更完整的代码,可以根据下面的答案准确显示正在进行的操作。
libname output '/data/files/jeff'
%let DateStart = '01Jan2013'd;
%let DateEnd = '01Jun2013'd;
proc sql;
CREATE TABLE output.id AS (
SELECT DISTINCT id
FROM mydb.sale_volume AS sv
WHERE sv.category IN ('a', 'b', 'c') AND
sv.trans_date BETWEEN &DateStart AND &DateEnd
)
CREATE TABLE output.sums AS (
SELECT id, SUM(sales)
FROM mydb.sale_volue AS sv
INNER JOIN output.id AS ids
ON ids.id = sv.id
WHERE sv.trans_date BETWEEN &DateStart AND &DateEnd
GROUP BY id
)
run;
目标是根据类别成员资格在表中查询某些id。 然后,我将这些成员的活动汇总到所有类别。
上述方法远比以下方法慢:
如果我理解正确,确保我的所有代码都完全通过而不是交叉加载可能更有效。
在昨天发布一个问题之后,一位成员建议我可以从提出更具体的情况的单独的问题中获益。
我正在使用SAS Enterprise Guide编写一些程序/数据查询。 我没有权限修改存储在“Teradata”中的基础数据。
我的基本问题是在这种环境中编写高效的SQL查询。 例如,我为一小部分ID查询一个大表(有数千万条记录)。 然后,我使用此子集再次查询更大的表:
proc sql;
CREATE TABLE subset AS (
SELECT
id
FROM
bigTable
WHERE
someValue = x AND
date BETWEEN a AND b
)
这可以在几秒钟内完成,并返回90k ID。 接下来,我想在大表中查询这组ID,然后出现问题。 我希望随着时间的推移对ID的值进行求和:
proc sql;
CREATE TABLE subset_data AS (
SELECT
bigTable.id,
SUM(bigTable.value) AS total
FROM
bigTable
INNER JOIN subset
ON subset.id = bigTable.id
WHERE
bigTable.date BETWEEN a AND b
GROUP BY
bigTable.id
)
无论出于何种原因,这需要很长时间。 区别在于第一个查询标记'someValue'。 第二个是查看所有活动,不管'someValue'中的内容是什么。 例如,我可以标记每个订购披萨的顾客。 然后我会查看订购披萨的所有客户的每次购买。
我对SAS并不太熟悉,所以我正在寻找有关如何更有效地做到这一点或加快速度的任何建议。 我对任何想法或建议持开放态度,如果我能提供更多细节,请告诉我。 我想我很惊讶第二个查询需要很长时间来处理。
使用SAS访问Teradata(或任何其他外部数据库)中的数据时,最重要的一点是SAS软件准备SQL并将其提交到数据库。 我们的想法是尝试让您(用户)从所有数据库特定的细节中解脱出来。 SAS使用称为“implict pass-through”的概念来实现这一点,这意味着SAS将SAS代码转换为DBMS代码。 发生的很多事情都是数据类型转换:SAS只有两种(只有两种)数据类型,数字和字符。
SAS处理为您翻译的事情,但这可能令人困惑。 例如,我见过用VARCHAR(400)列定义的“懒惰”数据库表,其值永远不会超过一些较小的长度(如人名的列)。 在数据库中,这不是什么大问题,但由于SAS没有VARCHAR数据类型,因此每行创建一个宽度为400个字符的变量。 即使使用数据集压缩,这也可能会使得到的SAS数据集不必要地变大。
另一种方法是使用“显式传递”,使用相关DBMS的实际语法编写本机查询。 这些查询完全在DBMS上执行,并将结果返回给SAS(它仍然为您进行数据类型转换。例如,这是一个“传递”查询,它执行两个表的连接并创建一个SAS数据集作为结果:
proc sql;
connect to teradata (user=userid password=password mode=teradata);
create table mydata as
select * from connection to teradata (
select a.customer_id
, a.customer_name
, b.last_payment_date
, b.last_payment_amt
from base.customers a
join base.invoices b
on a.customer_id=b.customer_id
where b.bill_month = date '2013-07-01'
and b.paid_flag = 'N'
);
quit;
请注意,括号内的所有内容都是本机Teradata SQL,并且连接操作本身在数据库中运行。
您在问题中显示的示例代码不是 SAS / Teradata程序的完整工作示例。 为了更好地提供帮助,您需要显示真实的程序,包括任何库引用。 例如,假设您的真实程序如下所示:
proc sql;
CREATE TABLE subset_data AS
SELECT bigTable.id,
SUM(bigTable.value) AS total
FROM TDATA.bigTable bigTable
JOIN TDATA.subset subset
ON subset.id = bigTable.id
WHERE bigTable.date BETWEEN a AND b
GROUP BY bigTable.id
;
这将指示先前分配的LIBNAME语句,SAS通过该语句连接到Teradata。 如果SAS甚至能够将完整查询传递给Teradata,那么该WHERE子句的语法将非常相关。 (您的示例未显示“a”和“b”所指的内容.SAS可以执行连接的唯一方法是将两个表拖回本地工作会话并在SAS服务器上执行连接。
我强烈建议的一件事是,您试图说服您的Teradata管理员允许您在某个实用程序数据库中创建“驱动程序”表。 我们的想法是,您将在Teradata中创建一个包含要提取的ID的相对较小的表,然后使用该表执行显式连接。 我相信你需要更正式的数据库培训才能做到这一点(比如如何定义一个合适的索引以及如何“收集统计数据”),但凭借这些知识和能力,你的工作就会飞翔。
我可以继续,但我会在这里停下来。 我每天都广泛使用SAS和Teradata,而我所说的是这个星球上最大的Teradata环境之一。 我喜欢两种编程。
您暗示假设您的第一个查询中的90k记录都是唯一id
。 那是明确的吗?
我问,因为你的第二个问题的含义是它们不是唯一的。
- 一个id
随着时间的推移可以有多个值,并且具有不同的somevalue
值
如果id
在第一个数据集中不唯一,则需要在第一个查询中使用GROUP BY id
或使用DISTINCT
。
想象一下,90k行由30k个唯一id
组成,因此每个id
平均有3行。
然后想象一下,那些30k的唯一id
在你的时间窗口中实际上有9条记录,包括somevalue <> x
行。
然后,您将获得每个id
3x9记录。
随着这两个数字的增长,第二个查询中的记录数量会逐渐增长。
替代查询
如果这不是问题,那么另一种查询(这不是理想的,但可能的)将是......
SELECT
bigTable.id,
SUM(bigTable.value) AS total
FROM
bigTable
WHERE
bigTable.date BETWEEN a AND b
GROUP BY
bigTable.id
HAVING
MAX(CASE WHEN bigTable.somevalue = x THEN 1 ELSE 0 END) = 1
如果ID是唯一且是单个值,那么您可以尝试构建格式。
创建一个如下所示的数据集:
fmtname, start, label
其中fmtname对于所有记录都是相同的,合法的格式名称(以字母开头和结尾,包含字母数字或_); start是ID值; 并且标签是1.然后为fmtname添加一行,空白开始,标签为0,另一个变量为hlo='o'
(对于'other')。 然后使用CNTLIN
选项导入proc格式,现在您的值转换为1/0。
这是使用SASHELP.CLASS的简短示例。 这里的ID是名称,但它可以是数字或字符 - 适合您的使用。
data for_fmt;
set sashelp.class;
retain fmtname '$IDF'; *Format name is up to you. Should have $ if ID is character, no $ if numeric;
start=name; *this would be your ID variable - the look up;
label='1';
output;
if _n_ = 1 then do;
hlo='o';
call missing(start);
label='0';
output;
end;
run;
proc format cntlin=for_fmt;
quit;
现在不是进行连接,而是可以“正常”进行查询,但是使用另外的where子句and put(id,$IDF.)='1'
。 这不会使用索引或任何内容进行优化,但可能比连接更快。 (它可能也不会更快 - 取决于SQL优化器的工作方式。)
如果id是唯一的,您可以向该表添加UNIQUE PRIMARY INDEX(id),否则它默认为非唯一PI。 了解uniquenes有助于优化者制定更好的计划。
如果没有像Explain那样的更多信息(只是将EXPLAIN放在SELECT前面),很难说它是如何改进的。
一种替代解决方案是使用SAS程序。 我不知道你的实际SQL在做什么,但是如果你只是做频率(或者其他可以在PROC中完成的事情),你可以这样做:
proc sql;
create view blah as select ... (your join);
quit;
proc freq data=blah;
tables id/out=summary(rename=count=total keep=id count);
run;
或任何数量的其他选项(PROC MEANS,PROC TABULATE等)。 这可能比在SQL中总和更快(取决于一些细节,例如您的数据组织方式,实际执行的内容以及可用内存量)。 如果您在数据库中创建视图可能会更快,那么SAS可能会选择在数据库中执行此操作。 (实际上,如果你只是从基表运行freq,它可能会更快,然后将结果连接到较小的表)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.