【问题标题】:Writing Efficient Queries in SAS Using Proc sql with Teradata使用 Proc sql 和 Teradata 在 SAS 中编写高效查询
【发布时间】:2013-07-08 15:51:23
【问题描述】:

编辑:这是一组更完整的代码,可以准确显示下面的答案。

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. 运行内部连接两个结果集的第三个查询。

如果我理解正确,确保我的所有代码完全通过而不是交叉加载可能更有效。


在昨天发布问题后,一位成员建议我可能会从针对我的情况提出一个更具体的关于性能的单独问题中受益。

我正在使用 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 并不太熟悉,因此我正在寻找有关如何更有效地执行此操作或加快速度的任何建议。我愿意接受任何想法或建议,如果我能提供更多细节,请告诉我。我想我只是对第二个查询需要这么长时间来处理感到惊讶。

【问题讨论】:

    标签: sql sas teradata


    【解决方案1】:

    您暗示第一个查询中的 90k 条记录都是唯一的ids。确定吗?

    我问是因为您的第二个查询暗示它们不是唯一的。
    - 随着时间的推移,一个id 可以有多个值,并且有不同的somevalues

    如果ids 在第一个数据集中不是唯一的,则需要在第一个查询中使用GROUP BY idDISTINCT

    假设 90k 行由 30k 个唯一的 ids 组成,因此每个 id 平均有 3 行。

    然后想象一下,这 30k 个唯一的 ids 实际上在您的时间窗口中有 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绝对是唯一的。实际的子集查询更复杂,但最终输出是一个DISTINCT 列表。我之前没有使用过HAVING 语句,我会尝试一下,看看是否有帮助。
    • 只是为了测试,我在查询日期范围的一小部分添加了时间戳。如果没有加入,它需要 13.57 (0.41 CPU) 秒才能返回 250k 记录。通过加入,获取 1800 行需要 29.09 (1.12 CPU) 秒。多么奇怪。
    • 另外,首先运行主查询,然后应用连接(子查询)在 15.97 秒内运行。它能够计算所有的总和,然后将它们削减比先计算再求和更快。
    • @JeffreyKramer - 表上的主索引是什么?感觉好像最大的开销是 Teradata 必须在放大器周围混洗数据。
    • 我发现这在任何 SQL 风格中都很常见——先加入,然后再进行分组,而不是一步完成加入 + 分组。我不知道为什么。
    【解决方案2】:

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

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

    如果没有更多信息,例如解释(只需将 EXPLAIN 放在 SELECT 前面),就很难说出如何改进。

    【讨论】:

      【解决方案4】:

      另一种解决方案是使用 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 可能会选择在数据库中执行此操作,这可能会更快。 (事实上​​,如果你只是从基表运行频率,它可能会更快,然后将结果连接到较小的表)。

      【讨论】:

        【解决方案5】:

        在使用 SAS 访问 Teradata(或任何其他外部数据库)中的数据时,要了解的最关键的一点是 SAS 软件准备 SQL 并将其提交到数据库。这个想法是尝试让您(用户)从所有数据库特定的细节中解脱出来。 SAS 使用称为“隐式传递”的概念来执行此操作,这仅意味着 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 环境之一。我喜欢两者的编程。

        【讨论】:

        • 这正是我想要的。非常感谢您的信息。我仍然在学习 SAS 和 Teradata,因为我对这两者都是新手,在我以前的工作中,我几乎在 R 中完成了所有的专有数据库格式。我将在对原始问题的编辑中发布更完整的代码示例。我很好奇你是怎么做的。我还将尝试找出驱动程序表的状态。这似乎是个好主意,尤其是我需要查询这些数据的方式。再次感谢您的建议,我真的很感激并从中学习。
        • 杰夫,您更新的问题表明您确实想要使用直通查询。您现在执行此操作的方式保证必须从 Teradata 下载整个表,因为您要加入 SAS 数据集。我建议你问一个新问题,因为这个问题的答案有点超出范围。执行此操作时,请查看是否可以找到定义 mydb 的原始 LIBNAME 语句。
        猜你喜欢
        • 2015-10-21
        • 1970-01-01
        • 2023-03-18
        • 1970-01-01
        • 2018-08-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多