【问题标题】:Embedded Implicit Loops in SAS with two Set StatementsSAS 中带有两个 Set 语句的嵌入式隐式循环
【发布时间】:2018-03-26 18:59:44
【问题描述】:

我有以下代码来跟踪特定制造商优惠券的发行。每个问题都可以在已赎回、过期和剩余字段中包含美元。我有一个 ISSUE 表,它给出了所有的发行和金额,还有一个 REDEMPTIONS 表,它记录了赎回或过期的金额。目标是通过在每个类别中放置适当的金额来跟踪每个问题的所有票面金额。

此代码应该循环遍历 ISSUE 表,并将 REDEMPTIONS 表中的匹配记录与 ISSUE 表中的每条记录联系起来。

/*create sample tables*/
    data ISSUE; input
    Coupon_NBR $  AMOUNT  REDEEMED    EXPIRED    REMAINING; datalines;
    A           500     0           0           500
    A           500     0           0           500
    B           500     0           0           500
    B           500     0           0           500
    B           500     0           0           500
    B           1250    0           0           1250
    B           750     0           0           750
    C           500     0           0           500
    C           500     0           0           500
    C           500     0           0           500
    C           500     0           0           500
    C           500     0           0           500
    run;

data REDEMPTIONS; input
Redemp_coupon_NBR $ TRANS_AMOUNT     TYPE $16.; datalines;
A                      -150       REDEMPTION
A                      -350       REDEMPTION
A                      -200       EXPIRATION
B                      -300       REDEMPTION
B                      -200       EXPIRATION
B                     -1000       REDEMPTION
C                     -1500       REDEMPTION
C                      -500       EXPIRATION
run;

/*begin looping code*/
data Tracking;

    if _n_ = 1 then Link get_redemptions;
    set issue;
        if (remaining > 0) and (Coupon_NBR = Redemp_coupon_NBR) then do; 
            if trans_amount = 0 then                
               link get_redemptions;


            if trans_amount + remaining >=0 then do; 
               remaining = remaining + trans_amount;
               if type = 'EXPIRATION' then;         
                  expired = expired - trans_amount;

               if type = 'REDEMPTION' then;         
                  redeemed = redeemed - trans_amount;

               link get_redemptions;        
            end;                                    

            else do;                                 
               remaining = 0;
               if type = 'EXPIRATION' then do;         
                  expired = expired - trans_amount;
               end;                   
               else do;                            
                  redeemed = redeemed - trans_amount;      
                  trans_amount = trans_amount + remaining;
                  remaining = 0;
               end;                                 
            end; 
        end; 

        else do;
            link get_redemptions;
        end;                                        

    return;


    get_redemptions:
    set redemptions;
    return;

run;     

这是我得到的输出:

Coupon_NBR  AMOUNT  REDEEMED    EXPIRED REMAINING   redemp_coupon_nbr   trans_amount    type
A              500    150         150     350               A               -350    REDEMPTION
A              500    350         350     150               A               -200    EXPIRATION
B              500      0           0     500               B               -300    REDEMPTION
B              500    300         300     200               B               -200    EXPIRATION
B              500    200         200     300               B              -1000    REDEMPTION
B             1250   1000        1000     250               C              -1500    REDEMPTION
B              750      0           0     750               C               -500    EXPIRATION

在这个例子中,正确的输出是:

redemp_coupon_nbr   AMOUNT  REDEEMED    EXPIRED REMAINING
A                      500       500          0         0
A                      500         0        200       300
B                      500       300        200         0
B                      500       500          0         0
B                      500       500          0         0
B                     1250         0          0      1250
B                      750         0          0       750
C                      500       500          0         0
C                      500       500          0         0
C                      500       500          0         0
C                      500         0        500         0
C                      500         0          0       500

显然,我的结果与我想要的结果相去甚远。然而,我主要担心的是输出只有七行,当我希望它跟踪每个优惠券问题时,这意味着我需要它有 12 行(对于 ISSUE 表中的每一行)。我认为我的循环存在某种问题,特别是在Get Redemptions 定义中。我调试了一段时间没有成功。

【问题讨论】:

  • 如果您没有任何 output 语句,那么 SAS 将在数据步迭代结束时输出。听起来您的数据步骤仅迭代 7 次。您只需要在准备好后显式输出观察结果。
  • 大多数数据步骤会在您读取输入结束时停止。在处理完issue 中的所有记录之前,您无需阅读到redemptions 的末尾。
  • 您的输出并不能说明全部情况,尤其是在第 3 个 B 中,1000 兑换适用于 500 的优惠券,这意味着有 500 的兑换余额(在以后的优惠券中使用)。我认为一般来说你不想允许超过优惠券的赎回。您将需要相当数量的脚手架,以允许旋转赎回或优惠券。 “脚手架”将是当前余额、赎回和、到期和赎回余额以及到期余额的变量

标签: loops sas


【解决方案1】:

贾罗姆:

一个强大的解决方案需要一种交易分类账方法,以便正确处理获取每个优惠券的多个兑换和跟踪超额以及在获取额外兑换之前将超额应用于优惠券的替代方案。

以下示例代码有许多 put 语句,因此您可以在日志中观察算法决策点。 balance 的目标是在每次交易对账时(从上方或下方)接近零,并跟踪超出目标的任何部分。

对于示例,这些变量名称替换是针对您的数据完成的。

  • coupon_nbr -> G
  • redemp_coupon_nbr -> XG
  • trans_amount -> XAMOUNT

您需要LINK 来获取兑换是正确的。
通过添加BY XGEND=,进一步促进了分组处理。

对 redemptions end= 变量的测试可防止数据步骤过早停止(如果在读取数据集最后一条记录后达到后续无条件集,则会发生这种情况)。

data reconciliation (keep=G AMOUNT REDEEMED EXPIRED REMAINING EXCESS APP_COUNT _redem_bal _expir_bal fetch_sum)
  ; * / debug;

  set issue;
  by G;

  REDEEMED = 0;
  EXPIRED = 0;
  REMAINING = 0;
  EXCESS = 0;

  if 0 then set redemptions; %* prep pdv;

  retain _balance   0;
  retain _redem_sum 0;
  retain _expir_sum 0;
  retain _redem_bal 0;
  retain _expir_bal 0;

if first.g then put / '----------- ' G= '-------------';

put '@set ' _N_=;
put 'balance: ' _balance _redem_bal= _expir_bal=; 
put 'coupon : ' amount first.g= /;

  if first.G then do;
    put @3 '@first in group';
    _balance = amount;
    _redem_sum = 0;
    _redem_bal = 0;
    _expir_sum = 0;
    _expir_bal = 0;

    put @3 'balance: ' _balance _redem_bal= _expir_bal= G= XG=;

    * spin to first matching redemption or first redemption in a higher by-group;
    if (XG ne G) then 
      do while (not EOT);
        link fetch;
        if XG >= G then leave;
      end;

    if (G = XG) then
      link apply_redemp; 

    put @6 'spin: ' G= XG=;
  end;
  else do; * additional couponage;
    put @3 '@next in group';

    if (G = XG) then
      _balance + amount;
    else
      _balance = amount;

    put @3 'balance: ' _balance _redem_bal= _expir_bal=;

    link apply_excess_to_balance;

    put @3 'balance: ' _balance _redem_bal= _expir_bal= xg= last.xg=;

    if (_balance > 0 and G = XG and not last.XG) then
      link fetch_apply;
  end;

  if (G = XG) then
    do while (not EOT and not last.XG and _balance > 0);
      link fetch_apply;
    end;

  redeemed  = _redem_sum;
  expired   = _expir_sum;
  remaining = min (_balance, amount);
  excess = sum (_redem_bal, _expir_bal, max (0, _balance - amount));

  output;

  put @4 'output: ' amount= redeemed= expired= remaining= excess= /;

  _redem_sum = 0;
  _expir_sum = 0;

  return;

apply_excess_to_balance:
  if (_redem_bal > 0 and _balance > 0) then do;
    apply = min ( _balance, _redem_bal );
    _redem_sum + apply;
    _redem_bal + -apply;
    _balance + -apply;
    app_count = sum(app_count,1);

    put @4 'excess: ' apply= _redem_bal= _redem_sum= _balance= 'reduced amount by excess redemption';
  end;

  if (_expir_bal > 0 and _balance > 0) then do;
    apply = min ( _balance, _expir_bal );
    _expir_sum + apply;
    _expir_bal + -apply;
    _balance + -apply;
    app_count = sum(app_count,1);

    put @4 'excess: ' apply= _expir_bal= _expir_sum= _balance= 'reduced amount by excess expiration';
  end;
return;

fetch:
  set redemptions end=EOT;
  by XG;

  put @5 'fetch: ' xg= xamount= type= first.xg= last.xg= EOT=;
return;

fetch_apply:
  link fetch;

  if (G = XG) then
    link apply_redemp; 

return;

apply_redemp:
  if type in: ('RED' 'EXP') then do;

    apply = min (_balance, -XAMOUNT);
    excess = max (0, -XAMOUNT - _balance);

    if type =: 'RED' then do; 
      _redem_sum + apply;
      _redem_bal + excess;
    end;
    else
    if type =: 'EXP' then do; 
      _expir_sum + apply;
      _expir_bal + excess;
    end;

    _balance + -apply;

    app_count = sum(app_count,1);
    fetch_sum = sum(fetch_sum, -xamount);

    put @5 'apply: ' apply= _balance= _redem_sum= _expir_sum= _redem_bal= _expir_bal=;
  end;
  return;
run;

这里有一些额外的示例数据

  • 不匹配的兑换
  • B 组有第一次赎回,这是跨越两张优惠券的大量超额兑换。
  • 没有兑换的优惠券组

复杂的数据

data ISSUE; input
G $  AMOUNT;
A           500   
A           500    
B           500    
B           500    
B           500    
B           1250   
B           750    
B2          100
B2          200
C           500    
C           500    
C           500    
C           500    
C           500    
run;

data REDEMPTIONS; input
XG $ XAMOUNT     TYPE $16.; datalines;
!   -1000       REDEMPTION
A    -150       REDEMPTION
A    -350       REDEMPTION
A    -200       EXPIRATION
B   -1100       REDEMPTION   was -300
B    -200       EXPIRATION
B   -1000       REDEMPTION
C   -1500       REDEMPTION
C    -500       EXPIRATION
run;

【讨论】:

  • 感谢您的回答。我看到我过度简化了这个过程。您的解决方案有效。非常感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-08
  • 2017-01-13
  • 1970-01-01
相关资源
最近更新 更多