【问题标题】:Create table that splits records on specific dates using SAS创建使用 SAS 在特定日期拆分记录的表
【发布时间】:2014-05-22 18:46:37
【问题描述】:

我希望使用表 A 并创建类似于表 B 的东西,但基于表 C 中包含的任意拆分日期集。

例如,(注意 start_date = inception_date 并不总是正确的,因此必须保留 inception_date 而不是从 start_date 派生;这实际上代表了属于该句点的数百个字段)

我在 SAS 工作,但我希望能够使用 PROC SQL 编写此内容。我认为这样做的一种方法是为表 C 中的记录对创建多个表(包括最后的空值),然后将它们合并在一起。

伪代码示例:

for each record of table_c, concoct the pairs { (., 01-Jan-2012), (01-Jan-2012, 01-Jul-2012), (01-Jul-2012, 01-Jan-2013), (01-Jan-2013, .) }

以下查询可能需要围绕split_date1split_date2 进行一些空测试:

CREATE TABLE subquery1 AS
SELECT 
    a.customer_id
    ,max(a.start_date, x.split_date1) AS start_date
    ,min(a.end_date, x.split_date2 - 1) AS end_date
    ,a.inception_date
FROM table_a AS a
JOIN split_date AS x
;
.... (do for each pair of split dates, and then union all these tables together with some WHERE querying to throw away the nonsensical rows) to produce table_b. The image above indicates which subquery would generate which rows in table_b

请帮我填补空白,或提出替代方法。

table_a:

customer_id start_date  end_date    inception_date      
aaa 18-Jun-11   17-Jun-12   18-Jun-11       
aaa 18-Jun-12   17-Jun-13   18-Jun-12       
bbb 13-Jul-11   12-Jul-12   13-Jul-11       
ccc 14-May-11   13-Nov-11   14-Jul-11       
ddd 21-Jun-11   20-Jun-12   21-Jun-11

table_b:

customer_id start_date  end_date    inception_date      subquery
aaa 18-Jun-11   31-Dec-11   18-Jun-11       (1)
aaa 01-Jan-12   17-Jun-12   18-Jun-11       (2)
aaa 18-Jun-12   30-Jun-12   18-Jun-12       (2)
aaa 01-Jul-12   31-Dec-12   18-Jun-12       (3)
aaa 01-Jan-13   17-Jun-13   18-Jun-12       (4)
bbb 13-Jul-11   31-Dec-11   13-Jul-11       (1)
bbb 01-Jan-12   30-Jun-12   13-Jul-11       (2)
bbb 01-Jul-12   12-Jul-12   13-Jul-11       (3)
ccc 14-May-11   13-Nov-11   14-May-11       (1)
ddd 21-Jun-11   31-Dec-11   21-Jun-11       (1)
ddd 01-Jan-12   20-Jun-12   21-Jun-11       (2)

table_c:

split_dates                 
01-Jan-12                   
01-Jul-12                   
01-Jan-13   

【问题讨论】:

  • 另外,我要指出的是,这在 SQL 中可能是合理可行的(这在技术上当然是可行的),但在数据步骤中您肯定会更轻松。您可以在数据步骤中用非常少的代码行来执行此操作,然后遍历数据,而在 SQL 中,您将不得不处理数据然后合并事物(您的一般策略在那里是合理的)。
  • 如果您尝试使用 SQL,因为这将是更大 SQL 流程中的一个步骤,您可以考虑使用 SAS datastep 视图,在这种情况下,您可以直接在其他 SQL 流程中使用它.
  • 我很高兴看到 DATA 步解决方案,请随时发布。这可能对测试有用 - 我对 DATA 步骤不太熟悉,因为我是 SQL

标签: sql date sas


【解决方案1】:

这是一种混合 SQL/datastep 方法 - 但它更短!输入数据(取自@Joe给出的答案):-

data table_a;
  informat start_date end_date date9.;
  format start_date end_date date9.;
  input customer_id $ start_date end_date;
  datalines;
  aaa 18JUN2011 17JUN2012
  aaa 18JUN2012 17JUN2013
  bbb 13JUL2011 12JUL2012
  ccc 14MAY2011 13NOV2011
  ddd 21JUN2011 20JUN2012
  ;;;;
run;

data table_c;
  informat split_dates date9.;
  format split_dates date9.;
  input split_dates;
  datalines;
  01JAN2012
  01JUL2012
  01JAN2013
  ;;;;
run;

以下将拆分日期复制到宏变量(SQL!),然后使用此宏(datastep!)循环 table_a:-

**  Output the split dates to a macro variable;
proc sql noprint;
  select split_dates format=8. into: c_dates separated by ',' from table_c order by split_dates;
quit;

**  For each period in table_a, look to see if each split date is within it,;
**  outputting a row if so;
data final_out(drop=dt old_end_date);
  set table_a(rename=(end_date = old_end_date));

  format start_date end_date inception_date date11.;
  inception_date = start_date;

  do dt = &c_dates;

    if start_date <= dt <= old_end_date then do;
      end_date = dt - 1;
      output;
      start_date = dt;
    end;

  end;

  **  For the last row per table_a entry;
  end_date = old_end_date;
  output;
run;

如果您事先知道拆分日期,您可以将它们硬编码到数据步中并省略 SQL 位(不建议记住 - 硬编码很少是个好主意)。

【讨论】:

  • 我特别想避免硬编码:) 这非常简洁。仍然希望我可以用纯 SQL 解决这个问题
  • 对于一个较小的表C,这大致相当于数组解决方案,可能更容易理解,尽管它有一个缺点,它不能用作单个视图。不过,这需要一两秒钟的时间,因为proc sql 的开销是不可忽略的。
  • 请注意,这仅在对 table_c 进行排序时才有效——您可以在 DATA 步骤之前在 table_c 上包含 PROC SORT 以防止这种情况发生
  • @JustinJDavies - 只需在 Proc SQL 上粘贴 ORDER BY split_dates 即可。在我看来,这里 Proc SQL 的开销可以忽略不计 - 毕竟,它只是创建一个宏变量。
  • 我刚刚将它添加到代码中 - 它不会造成任何伤害。
【解决方案2】:

数据步解决方案。

首先,示例数据(我省略了另一个日期变量,我认为它对解决方案并不重要,尽管您当然希望在生产中使用它):

data table_a;
informat start_date end_date date9.;
format start_date end_date date9.;
input customer_id $ start_date end_date;
datalines;
aaa 18JUN2011 17JUN2012
aaa 18JUN2012 17JUN2013
bbb 13JUL2011 12JUL2012
ccc 14MAY2011 13NOV2011
ddd 21JUN2011 20JUN2012
;;;;
run;

data table_c;
informat split_dates date9.;
format split_dates date9.;
input split_dates;
datalines;
01JAN2011
01JUL2011
01JAN2012
01JUL2012
01JAN2013
;;;;
run;

现在,解决方案。首先,我们将table_c中的数据加载到一个临时数组中;哈希表也可以工作(如果表 c 很长,可能会更快,因为此解决方案需要遍历所有数组,而哈希表只需找到少数匹配的数组即可)。

然后我们遍历 C 加载到的数组,检查它是否有资格作为一个有用的断点,如果是,则分配开始/结束日期,输出,并重新分配新的开始日期。这里我使用新的开始/结束变量;如果要保留旧变量名,只需将输入时的原始变量重命名为其他变量名,然后将原始变量名用作新变量名,将重命名的原始变量用作旧变量名。

data table_b;
 set table_a;
 format final_start final_end date9.;
 array split_date_list[100] _temporary_; *make sure this 100 is as big or bigger than table_c;
 if _n_=1 then do;
  do _t = 1 to nobsc;  *load the contents of table_c into a temporary array;
    set table_c point=_t nobs=nobsc;
    split_date_list[_t]=split_dates;
  end;
 end;
 final_start=start_date; *You could reuse start_date here, I use new name for consistency;
 do _u= 1 to dim(split_date_list) until (final_end=end_date);
  if final_start le split_date_list[_u] le end_date then do;  *if split date is in between start and end, split it;
   final_end=split_date_list[_u]-1;   *But end_date does need a second variable, else it loses track of the actual end;
   output; *output a row;
   final_start=split_date_list[_u]; *fix the start date to the new value;
  end;
  else if split_date_list[_u] gt end_date then do; *if we have passed the end date;
   final_end=end_date;
   output;
  end;
 end;
 if end_date ne final_end then do; *if we never passed the end date, output the final row;
   final_end=end_date;
   output;
 end;
run;

【讨论】:

  • 嗨,乔,这真的很有用,谢谢。请原谅我不接受这个答案,因为我希望有一种有效的 SQL 方法。有我的支持(希望还有很多人)
  • 我发现了这个问题,我认为这可能是相关的:stackoverflow.com/questions/4245513/… 不幸的是,解决方案没有注释,问题本身也不是很清楚。仅供参考。它确实表明DATA step 方法的好处也可以通过 SQL 实现
  • 另外,table_c 包含的日期不太可能超过 50 个,而且通常要少得多 - 可能最多 10 个左右
【解决方案3】:

另一种基于 SQL 的(迭代)过程将采用以下形式:

subquery_a

SELECT 
a.*
,c.*
FROM table_a AS a
LEFT JOIN table_c AS c
WHERE a.start_date <= c.split_date
AND a.end_date > c.split_date
;

// 为我们提供了一个包含所有记录的表,其中至少需要一次拆分

subquery_b

SELECT  
    min(split_date) AS split_date
,a.customer_id
,a.start_date
,a.end_date
,a.inecption_date
FROM subquery_a AS a
GROUP BY
a.customer_id
,a.start_date
,a.end_date
,a.inecption_date   
;

subquery_c

(SELECT
sqb.customer_id
,sqb.start_date
    ,(sqb.split_date - 1) AS end_date
,sqb.inecption_date
FROM subquery_b AS sqb)
UNION ALL
    (SELECT
sqb.customer_id
,sqb.split_date AS start_date
    ,end_date
,sqb.inecption_date
FROM subquery_b AS sqb)

然后从 table_a 中删除行 subquery_c LEFT JOIN table_a ON customer_id AND start_date,然后是 table_a 与 subquery_c 的 UNION。迭代直到更新的 subquery_c 没有行然后停止。

但是我不知道如何单独用 SQL 来表达最后的迭代步骤。也许有人可以编辑这个答案来帮助我?

【讨论】:

  • 这种方法感觉非常笨重和不雅 - 也许我没有以适当的 SQL 方式思考?
【解决方案4】:

解决这个问题的关系方式:

  • 假设最早的 start_date 是 01-Jan-00 和最新的 us 31-Dec-20

table_c 改写为:

split_start split_end
01-Jan-00 31-Dec-12                   
01-Jan-12 30-Jun-12                   
01-Jul-12 31-Dec-12 
01-Jan-13 31-Dec-20

然后使用以下形式的查询:

SELECT 
  a.customer_id
  ,max(a.start_date, c.split_start) AS start_date
  ,min(a.end_date, c.split_end) AS end_date
  ,a.inception_date
FROM table_a AS a JOIN table_c AS c
WHERE a.start_date < c.split_end
AND a.end_Date > c.split_start
;

由于无法使用WHERE NOT EXISTS(参见http://goo.gl/VD1EHZ)和MONOTONIC()(参见http://goo.gl/7mHwEj)的不可靠性,在SAS 中使用SQL 重新表述table_c 变得很棘手。但是在 MSSQL 和其他 SQL 环境中,这相对简单。

例如(来自http://goo.gl/qt0RIz):

// INSERT INTO table_c to include two extra dates for max and min dates as above
// then...

WITH table_c_old
AS
(
SELECT ROW_NUMBER() OVER (ORDER BY split_date) seq FROM table_c
)
SELECT 
  c1.seq seq1
  ,c1.split_date AS split_start
  ,c2.seq seq2
  ,c2.split_date - 1 AS split_end
FROM table_c_old c1 LEFT JOIN table_c_old c2
ON c1.seq = c2.seq+1;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-14
    • 2013-01-11
    • 2012-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多