【问题标题】:How to generate dynamic table having begin month date and end month date Teradata or SAS SQL如何生成具有开始月份日期和结束月份日期的动态表 Teradata 或 SAS SQL
【发布时间】:2026-01-20 15:00:02
【问题描述】:

我想生成一个动态表格,其中一个月的开始日期作为一列,一个月的结束日期作为另一列。

理想情况下,我想提供两年,f.e. 2016 年和 2021 年。提供这两年时,我希望得到的输出如下:

Begin_of_Month End_of_Month
2016-01-01     2016-01-31
2016-02-01     2016-02-29
.
.
.
2021-12-01     2021-12-31

请注意,我需要从 2016 年到 2021 年所有年份的输出。在我上面的示例中,这意味着应包括 2017 2018 2019 2020。

我曾尝试使用 Teradata 的时间序列函数,但未能获得结果。

我尝试在 Teradata 中重新创建的解决方案是:How to generate calendar table having begin month date and end month Date

另外,我尝试了Teradata的EXPAND ON PERIOD时间序列功能。

【问题讨论】:

  • 您是否也需要 SAS 解决方案,还是需要基于 SQL 的解决方案?这在 SAS 数据步骤中非常简单。
  • 为什么要创建这个?为什么不直接将 sys_calendar.calendar 合并到您将要使用的任何需要这些日期的查询中?

标签: sql sas teradata teradatasql


【解决方案1】:

我确信有一些很棒的方法可以做到这一点,但我认为只需点击内置日历表可能是最简单的:

SELECT DISTINCT 
    min(calendar_date) OVER (PARTITION BY year_of_calendar, month_of_calendar) as start_of_month, 
    max(calendar_date) OVER (PARTITION BY year_of_calendar, month_of_calendar) as end_of_month  
FROM sys_calendar.calendar
WHERE year_of_calendar BETWEEN 2016 and 2021

要在没有表引用的情况下执行此操作,它会变得有点难看。 EXPAND ON 似乎是一条显而易见的路线,但如果 FROM 子句中没有表引用,则会出错。 UNION 遇到同样的问题,但我们可以通过使用 cte 来欺骗 UNIONEXPAND ON 更挑剔,我们可以劫持 Teradata 的 JSON_TABLE 功能来欺骗它:

SELECT BEGIN(dt), PRIOR(END(dt))
FROM JSON_TABLE
    ( 
        ON (SELECT 1 as id, NEW JSON('{"startdate":"2016-01-01","enddate":"2021-12-31"}') jd)
        USING 
            rowexpr('$')
            colexpr('[{"jsonpath" : "$.startdate", "type" : "DATE"},
                      {"jsonpath" : "$.enddate", "type" : "DATE"}]')
    ) as jt(id, startdate, enddate)
EXPAND ON PERIOD(startdate, enddate) as dt BY ANCHOR MONTH_BEGIN

您也可以使用递归 CTE 来构建月份,这感觉不那么老套,但生成时间更长。

WITH startend AS
(
    SELECT 
        DATE '2016-01-01' periodstartdate,
        DATE '2021-12-31' AS periodenddate
)
,RECURSIVE months AS
(
    SELECT periodstartdate,
        periodenddate,
        periodstartdate as monthstartdate,
        1 as monthoffset
    FROM startend
    UNION ALL
    SELECT periodstartdate,
        periodenddate,
        ADD_MONTHS(periodstartdate, monthoffset),
        monthoffset + 1
    FROM 
        months 
    WHERE monthoffset < months_between(periodenddate, periodstartdate) 
)
SELECT monthstartdate, monthstartdate + INTERVAL '1' MONTH - INTERVAL '1' DAY as monthenddate from months;

如果有更优雅的方法来实现这一点,我会非常感兴趣。如果没有 dual 或其他 RDBMS 中存在的序列生成,则构建没有表引用的数据集的选项非常有限。

【讨论】:

  • 我的目标是构建最好不依赖于任何其他表的东西。我会在接受之前等待其他解决方案。
  • 您为什么不想为此使用内置日历表?我想你可以用一个过于复杂的递归 CTE 或其他东西来做到这一点,但这正是你所要求的。
  • 在没有表引用的情况下在 Teradata 中生成数据绝对是一个很高的要求。在其他支持dual 表引用或序列生成或横向连接的RDBMS 中,这很简单,但Teradata 在那里受到了一些限制。对于@Andrew 的观点,我尝试使用递归以及expand onjson_table 来解决错误。
  • 另外,我不禁认为这里可能有更优雅的东西,但是 JSON 示例很好而且很活泼,所以它可能是一条可靠的路线。
  • 虽然是惊人的解决方案。谢谢。
【解决方案2】:

通常 EXPAND ON 仅在从 FROM 中访问表时才有效,但应用诸如 TRUNC 或 TO_DATE 之类的函数会愚弄优化器:

WITH dummy AS 
 (
   SELECT 
      2016 AS yr_start
     ,2021 as yr_end
     ,TO_DATE(TRIM(yr_start) || '-01-01') AS pd_start
     ,TO_DATE(TRIM(yr_end+1) || '-01-01') AS pd_end
 ) 
SELECT 
   BEGIN(pd) AS Begin_of_Month
  ,LAST(pd)  AS End_of_Month
FROM dummy
EXPAND ON PERIOD(pd_start, pd_end) AS pd
          BY INTERVAL '1' MONTH

【讨论】:

  • 这就是为什么我喜欢询问如何以多种方式解决一个问题。我从中学到了很多。
  • TRUNC!我记得那个!我知道会有比猛烈抨击JSON_TABLE() 更漂亮的东西。
  • 嗨 Dnoeth,您介意在 cast 语句中解释一下计算的确切作用吗?有相关文档吗?
  • 此计算基于日期的内部存储 ((year - 1900) * 10000 + month * 100 + day 并返回 1 月 1 日。对于年末,它是下一年的 1 月 1 日,但期间是包含 - 不包含的,即开始是包括在内,但 end 被排除在外。事实上,没有 TRUNC 也有一种更简单的方法。我将编辑我的答案
【解决方案3】:

如果您打算在 SAS 中执行此操作,则不需要 SQL。

data want;
  do year=2016 to 2021;
    do month=1 to 12;
      start_of_month=mdy(month,1,year);
      end_of_month=intnx('month',start_of_month,0,'e');
      output;
    end;
  end;
  format start_of_month end_of_month yymmdd10.;
  drop year month;
run;

【讨论】:

  • 假设我想在 proc SQL 查询中使用这个表,这可能吗?我想我是在问如何在 proc sql 中引用这个表?
  • ?? select * from want ??您是否在问如何将其作为 TERADATA 中的 VOLATILE 表上传?只需定义一个使用该目标模式的库名并上传它。或者甚至直接使用该 libref 编写它。由于它是微小的性能是没有问题的。注意所有的 teradata 陷阱,例如 PRIMARY_INDEX。
  • 是的。 DATA 之后的名称是该步骤正在创建的数据集(也称为“表”)的名称。
  • 我敢打赌,SAS 不需要您使用它的任何东西,并且可以直接在查询中使用 INTNX() 或使用适当的日期格式来实现。
最近更新 更多