【问题标题】:SAS/SQL - Create SELECT Statement Using Custom FunctionSAS/SQL - 使用自定义函数创建 SELECT 语句
【发布时间】:2013-07-13 14:10:14
【问题描述】:

更新

鉴于这种使用INTNX 的新方法,我想我可以使用循环来进一步简化事情。如果我做了一个数组呢:

data;
    array period [4] $ var1-var4 ('day' 'week' 'month' 'year');
run;

然后尝试为每个元素做一个循环:

%MACRO sqlloop;
  proc sql;
    %DO k = 1 %TO dim(period);  /* in case i decide to drop something from array later */
      %LET bucket = &period(k)
      CREATE TABLE output.t_&bucket AS (
        SELECT INTX( "&bucket.", date_field, O, 'E') AS test FROM table);
    %END
  quit;
%MEND
%sqlloop

这不太行,但它抓住了我想要的想法。它可以只为 INTX 中的每个值运行查询。这有意义吗?


我之前有几个问题要合并为一个。我从其他人那里得到了一些非常有用的建议,希望这可以将它们联系在一起。

我有以下函数创建一个动态字符串来填充 SAS proc sql; 代码块中的 SELECT 语句:

proc fcmp outlib = output.funcs.test;
    function sqlSelectByDateRange(interval $, date_field $) $;
        day = date_field||" AS day, ";
        week = "WEEK("||date_field||") AS week, ";
        month = "MONTH("||date_field||") AS month, ";
        year = "YEAR("||date_field||") AS year, ";

        IF interval = "week" THEN
            do;
                day = '';
            end;
        IF interval = "month" THEN
            do;
                day = '';
                week = '';
            end;
        IF interval = "year" THEN
            do;
                day = '';
                week = '';
                month = '';
            end;
        where_string = day||week||month||year;
    return(where_string);
    endsub;
quit;

我已经验证这会创建我想要的那种字符串:

data _null_;
    q = sqlSelectByDateRange('month', 'myDateColumn');
    put q =;
run;

这会产生:

q=MONTH(myDateColumn) AS month, YEAR(myDateColumn) AS year,

这正是我想要的 SQL 字符串。从之前的问题来看,我相信我需要在MACRO 中调用这个函数。然后我想要这样的东西:

%MACRO sqlSelectByDateRange(interval, date_field);
  /* Code I can't figure out */
%MEND

PROC SQL;
  CREATE TABLE output.t AS (
    SELECT 
      %sqlSelectByDateRange('month', 'myDateColumn')
    FROM
      output.myTable
  );
QUIT;

我无法理解如何让代码调用此宏并将其解释为 SQL SELECT 字符串的一部分。我已经在其他答案中尝试了一些前面的例子,但我无法让它工作。我希望这个更具体的问题可以帮助我填补这个缺失的步骤,以便我将来可以学习如何做到这一点。

【问题讨论】:

    标签: sql sas


    【解决方案1】:

    两件事:

    首先,您应该能够使用%SYSFUNC 来调用您的自定义函数。

    %MACRO sqlSelectByDateRange(interval, date_field);
        %SYSFUNC( sqlSelectByDateRange(&interval., &date_field.) )
    %MEND;
    

    请注意,通过 SYSFUNC 调用函数时不应使用引号。另外,you cannot use SYSFUNC with FCMP functions until SAS 9.2。如果您使用的是早期版本,这将不起作用。

    其次,您的选择子句中有一个尾随逗号。您可能需要一个虚拟列,如下所示:

    PROC SQL;
      CREATE TABLE output.t AS (
        SELECT 
          %sqlSelectByDateRange('month', 'myDateColumn')
          0 AS dummy
        FROM
          output.myTable
      );
    QUIT;
    

    (注意dummy 之前没有逗号,因为逗号已经嵌入到您的宏中。)


    更新

    我阅读了您对另一个答案的评论:

    我还需要能够针对不同的日期范围和非常临时的基础上执行此操作,因此我想说“从 6 月到 12 月按月”或“两年每周一次”等有人提出请求。

    我想我可以推荐一种更简单的方法来完成你正在做的事情。首先,我将创建一个包含日期和值的非常简单的数据集。日期分布在不同的日、周、月和年:

    DATA Work.Accounts;
    
        Format      Opened      yymmdd10.
                    Value       dollar14.2
                    ;
    
        INPUT       Opened      yymmdd10.
                    Value       dollar14.2
                    ;
    
    DATALINES;
    2012-12-31  $90,000.00
    2013-01-01 $100,000.00
    2013-01-02 $200,000.00
    2013-01-03 $150,000.00
    2013-01-15 $250,000.00
    2013-02-10 $120,000.00
    2013-02-14 $230,000.00
    2013-03-01 $900,000.00
    RUN;
    

    您现在可以使用INTNX 函数创建第三列以将“已打开”列四舍五入到某个时间段,例如'WEEK''MONTH''YEAR'(请参阅此complete list ):

    %LET Period = YEAR;
    
    PROC SQL NOPRINT;
    
        CREATE TABLE Work.PeriodSummary AS
        SELECT   INTNX( "&Period.", Opened, 0, 'E' ) AS Period_End FORMAT=yymmdd10.
               , SUM( Value )                        AS TotalValue FORMAT=dollar14.
        FROM     Work.Accounts
        GROUP BY Period_End
        ;
    
    QUIT;
    

    WEEK 的输出:

    Period_End   TotalValue
    2013-01-05     $540,000
    2013-01-19     $250,000
    2013-02-16     $350,000
    2013-03-02     $900,000
    

    MONTH 的输出:

    Period_End   TotalValue
    2012-12-31      $90,000
    2013-01-31     $700,000
    2013-02-28     $350,000
    2013-03-31     $900,000
    

    YEAR 的输出:

    Period_End   TotalValue
    2012-12-31      $90,000
    2013-12-31   $1,950,000
    

    【讨论】:

    • @JeffreyKramer - 我可能有一个更简单的方法让你实现你的目标。看看上面。
    • 你没有在你的函数中定义事物的长度,所以你冒着事情变得非常大的风险——也许所有这些手动连接都会发生这种情况 (||);猫/等更好,因为它们具有预定义的默认长度。
    • 另外 - 如果你可以使用 INTNX 的东西,你应该;它对其他人来说更加灵活和有用。您还可以定义自定义时间间隔,这非常有用。
    • 天哪,这比我尝试做的要好得多。它完全实现了相同的目标。非常感谢你们的帮助。
    • @JeffreyKramer - SAS 非常强大和灵活。如果你发现自己在一堆复杂的代码中挣扎着做一些看起来相当简单的事情,你可能应该停下来看看是否没有函数等来完成任务。 :)
    【解决方案2】:

    正如 Cyborg37 所说,您可能应该去掉函数中的尾随逗号。但请注意,您实际上并不需要创建宏来执行此操作,只需直接使用 %SYSFUNC 函数即可:

    proc sql;
      create table output.t as
      select %sysfunc( sqlSelectByDateRange(month, myDateColumn) )
             * /* to avoid the trailing comma */
      from output.myTable;
    quit;
    

    此外,虽然这是对用户定义函数的巧妙使用,但不清楚您为什么要这样做。可能有更好的解决方案不会在您的代码中造成太多潜在的混乱。用户定义的函数,如用户编写的宏,可以让生活更轻松,但它们也可能带来管理上的噩梦。

    【讨论】:

    • 是的,这是有道理的鲍勃。不过,我得到了涉及长度的相同错误。我不确定那是关于什么的。至于为什么,我很想听听你的想法。我基本上会运行一些需要按时间范围(日、周、月、年等)分组的报告。理想情况下,我宁愿每天运行数据集并按照我的意愿聚合它。不幸的是,我要查询的主要领域之一是不同的个体,他们可能会在数周和数月内有所不同。鉴于数据的大小,我想在导出摘要之前在给定级别执行 COUNT DISTINCT。
    • 我还需要能够在不同的日期范围和非常临时的基础上执行此操作,因此我想说“按月从 6 月到 12 月”或“每周两年”等,当有人提出要求时。
    【解决方案3】:

    对于您为什么会出错,我可以做出各种猜测,但从根本上说,不要这样做。您可以在数据步骤中完成您想要做的事情,这比 FCMP 功能更容易排除故障并且更容易实现,而 FCMP 功能实际上只是试图成为一个数据步骤。

    步骤: 1. 创建一个包含您可能的日期拉取数据的数据集。如果您经常使用它,您可以将它放在您的 SAS AUTOEXEC 中定义的永久库中。 2. 创建一个宏,从中提取所需的日期字符串。 3. 如果需要,可以使用 PROC FCMP 将其设为函数式宏,使用 RUN_MACRO。 4. 如果你这样做,使用 %SYSFUNC 来调用它。

    下面是这样做的:

    1:

    data pull_list;
    infile datalines dlm='|';
    length query $50. type $8.;
    input type $ typenum query $;
    datalines;
    day|1|&date_field. as day
    week|2|week(&date_field.) as week
    month|3|month(&date_field.) as month
    year|4|year(&date_field.) as year
    ;;;;
    run;
    

    2:

    %macro pull_list(type=,date_field=);
    %let date_field = datevar;
    %let type = week;
    proc sql noprint;
    select query into :sellist separated by ',' 
    from pull_list
    where typenum >= (select typenum from pull_list where type="&type.");
    quit;
    %mend pull_list;
    

    3:

    proc fcmp outlib = work.functions.funcs;
       function pull_list(type $,date_field $) $;
          rc = run_macro('pull_list', type,date_field);
          if rc eq 0 then return("&sellist.");
          else return(' ');
       endsub;
    run;
    

    4:

    data test;
    input datevar 5.;
    datalines;
    18963
    19632
    18131
    19105
    ;;;;
    run;
    option cmplib = (work.functions);
    
    proc sql;
    select %sysfunc(pull_list(week,datevar)) from test;
    quit;
    

    这样做的一大优点是您可以添加其他类型而不必担心函数的代码 - 只需向 pull_list 添加一行即可。如果你想设置它来做到这一点,我建议对 typenum 使用 1,2,3,4 以外的东西 - 使用 10,20,30,40 或其他东西,这样你就有差距(比如,如果添加了“twoweek” ,它会在 2 到 3 之间,而 25 比 2.5 更容易让人思考)。创建该 pull_list 数据集,将其放在所有用户都可以使用的网络驱动器上(如果您以外的任何人使用它,或者如果没有,则使用个人),然后从那里开始。

    【讨论】:

    • 这对学习也很有帮助。谢谢,我打算在以后需要做的另一个小项目中尝试这种方法。
    猜你喜欢
    • 2015-09-20
    • 2022-01-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-26
    • 2011-06-07
    • 2021-01-28
    • 2019-06-18
    相关资源
    最近更新 更多