使用带有Teradata的Proc sql在SAS中编写高效查询

时间:2013-07-10 15:53:25

标签: sql sas 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。然后,我将这些成员的活动汇总到所有类别。

上述方法远远慢于:

  1. 运行第一个查询以获取子集
  2. 运行第二个查询,对每个ID进行总结
  3. 运行内部连接两个结果集的第三个查询。
  4. 如果我理解正确,那么确保我的所有代码都完全通过而不是交叉加载可能会更有效。


    在昨天发布问题之后,一位成员建议我可以通过提出一个更具体针对我的情况的单独的绩效问题而受益。

    我正在使用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并不过分熟悉所以我正在寻找有关如何更有效地提高效率或加快速度的建议。我对任何想法或建议持开放态度,如果我能提供更多细节,请告诉我。我想我很惊讶第二个查询需要很长时间来处理。

5 个答案:

答案 0 :(得分:8)

使用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,并且连接操作本身在数据库中运行。

您在问题中显示的示例代码是 NOT 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环境之一。我喜欢两种编程。

答案 1 :(得分:1)

您暗示假设您的第一个查询中的90k记录都是唯一的id。那是明确的吗?

我问,因为你的第二个问题的含义是它们不是唯一的    - 一个id可以随时间推移具有多个值,并且具有不同的somevalue s

如果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

答案 2 :(得分:1)

如果ID是唯一的且是单个值,那么您可以尝试构建格式。

创建一个如下所示的数据集:

fmtname, start, label

其中fmtname对于所有记录都是相同的,是合法的格式名称(以字母开头和结尾,包含字母数字或_); start是ID值;和标签是1.然后为fmtname添加一行,空白开始,标签为0,另一个变量hlo='o'(对于'其他')。然后使用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;

现在,您可以“正常”进行查询,但使用and put(id,$IDF.)='1'的附加where子句,而不是进行连接。这不会使用索引或任何内容进行优化,但可能比连接更快。 (它可能也不会更快 - 取决于SQL优化器的工作方式。)

答案 3 :(得分:1)

如果id是唯一的,您可以向该表添加UNIQUE PRIMARY INDEX(id),否则默认为非唯一PI。 了解uniquenes有助于优化者制定更好的计划。

如果没有像Explain这样的更多信息(只是将EXPLAIN放在SELECT之前),很难说它是如何改进的。

答案 4 :(得分:0)

另一种解决方案是使用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,它可能会更快,然后将结果连接到较小的表)。