SQL查询在列A和列B之间的范围内每整数扩展一行

SQL查询在列A和列B之间的范围内每整数扩展一行,sql,sql-server,Sql,Sql Server,我的SQL Server有一个表。这两列都是整数,但以YYYYMM的形式表示日期。我想查询这个表并返回第三列,对于每一行,在这两列的范围内,每一年/每月都包含一个YYYYMM格式的整数 这是桌子: +------------+------------+ | beg_YYYYMM | end_YYYYMM | +------------+------------+ | 201802 | 201805 | | 201711 | 201801 | +----------

我的SQL Server有一个表。这两列都是整数,但以YYYYMM的形式表示日期。我想查询这个表并返回第三列,对于每一行,在这两列的范围内,每一年/每月都包含一个YYYYMM格式的整数

这是桌子:

+------------+------------+
| beg_YYYYMM | end_YYYYMM |
+------------+------------+
|     201802 |     201805 |
|     201711 |     201801 |
+------------+------------+
期望输出:

+------------+------------+----------------+
| beg_YYYYMM | end_YYYYMM | month_in_range |
+------------+------------+----------------+
|     201802 |     201805 |         201802 |
|     201802 |     201805 |         201803 |
|     201802 |     201805 |         201804 |
|     201802 |     201805 |         201805 |
|     201711 |     201801 |         201711 |
|     201711 |     201801 |         201712 |
|     201711 |     201801 |         201801 |
+------------+------------+----------------+
使用递归CTE:

with cte as (
      select beg_YYYYMM, end_YYYYMM,
             convert(date, convert(varchar(255), beg_YYYYMM) + '01') as dte,
             convert(date, convert(varchar(255), end_YYYYMM) + '01') as end_dte
      from t
      union all
      select beg_YYYYMM, end_YYYYMM,
             dateadd(month, 1, dte),
             end_dte
      from cte
      where dte < end_dte
     )
select beg_yyyymm, end_yyyymm, 
       year(dte) * 100 + month(dte) as yyyymm
  from cte
 order by dte;
是一个数据小提琴。

使用递归CTE:

with cte as (
      select beg_YYYYMM, end_YYYYMM,
             convert(date, convert(varchar(255), beg_YYYYMM) + '01') as dte,
             convert(date, convert(varchar(255), end_YYYYMM) + '01') as end_dte
      from t
      union all
      select beg_YYYYMM, end_YYYYMM,
             dateadd(month, 1, dte),
             end_dte
      from cte
      where dte < end_dte
     )
select beg_yyyymm, end_yyyymm, 
       year(dte) * 100 + month(dte) as yyyymm
  from cte
 order by dte;

是一个dbfiddle。

我真的不认为我会实际部署它,但我想知道它会是什么样子。如果你创造了一个TVF,做了丑陋的工作,它不是太坏。我使用了Aaron Bertrand的DateDim,并对其进行了快速修改,以获得传入的两个日期之间的月初日期

CREATE OR ALTER FUNCTION dbo.tvf_MonthRange (@beg_YYYYMM int, @end_YYYYMM int)  
RETURNS @Results TABLE   
 (month_in_range int)
AS  
BEGIN  

--Have to convert ints to dates
    DECLARE @BegDate DATE; 
    SET @BegDate =  DATEFROMPARTS(CAST(SUBSTRING(CAST(@beg_YYYYMM AS varchar(6)),1,4) AS INT), CAST(SUBSTRING(CAST(@beg_YYYYMM AS varchar(6)),5,2) AS INT), 1);

--This needs to be the second day of the month for the code below to work.
    DECLARE @EndDate DATE;
    SET @EndDate =  DATEFROMPARTS(CAST(SUBSTRING(CAST(@end_YYYYMM AS varchar(6)),1,4) AS INT), CAST(SUBSTRING(CAST(@end_YYYYMM AS varchar(6)),5,2) AS INT), 2);

    INSERT INTO @Results
    SELECT (DATEPART(YEAR, d) *100) + DATEPART(MONTH, d)
    FROM
        (
          SELECT d = DATEADD(DAY, rn - 1, @BegDate)
          FROM 
          (
            SELECT TOP (DATEDIFF(DAY, @BegDate, @EndDate)) 
              rn = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
            FROM sys.all_objects AS s1
            CROSS JOIN sys.all_objects AS s2
            -- on my system this would support > 5 million days
            ORDER BY s1.[object_id]
          ) AS x
        ) AS y
    WHERE DATEPART(DAY, d) = 1;

    RETURN;
END 
那么你可以这样称呼它

DECLARE @Months TABLE (beg_YYYYMM int, end_YYYYMM int)
INSERT INTO @MONTHS SELECT 201802, 201805 
INSERT INTO @MONTHS SELECT 201711, 201801 

SELECT *
FROM @Months m
CROSS APPLY dbo.tvf_MonthRange(m.beg_YYYYMM, m.end_YYYYMM) mr ;

这是一个坏主意,对吗?

我真的不认为我会实际部署它,但我想知道它会是什么样子。如果你创造了一个TVF,做了丑陋的工作,它不是太坏。我使用了Aaron Bertrand的DateDim,并对其进行了快速修改,以获得传入的两个日期之间的月初日期

CREATE OR ALTER FUNCTION dbo.tvf_MonthRange (@beg_YYYYMM int, @end_YYYYMM int)  
RETURNS @Results TABLE   
 (month_in_range int)
AS  
BEGIN  

--Have to convert ints to dates
    DECLARE @BegDate DATE; 
    SET @BegDate =  DATEFROMPARTS(CAST(SUBSTRING(CAST(@beg_YYYYMM AS varchar(6)),1,4) AS INT), CAST(SUBSTRING(CAST(@beg_YYYYMM AS varchar(6)),5,2) AS INT), 1);

--This needs to be the second day of the month for the code below to work.
    DECLARE @EndDate DATE;
    SET @EndDate =  DATEFROMPARTS(CAST(SUBSTRING(CAST(@end_YYYYMM AS varchar(6)),1,4) AS INT), CAST(SUBSTRING(CAST(@end_YYYYMM AS varchar(6)),5,2) AS INT), 2);

    INSERT INTO @Results
    SELECT (DATEPART(YEAR, d) *100) + DATEPART(MONTH, d)
    FROM
        (
          SELECT d = DATEADD(DAY, rn - 1, @BegDate)
          FROM 
          (
            SELECT TOP (DATEDIFF(DAY, @BegDate, @EndDate)) 
              rn = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
            FROM sys.all_objects AS s1
            CROSS JOIN sys.all_objects AS s2
            -- on my system this would support > 5 million days
            ORDER BY s1.[object_id]
          ) AS x
        ) AS y
    WHERE DATEPART(DAY, d) = 1;

    RETURN;
END 
那么你可以这样称呼它

DECLARE @Months TABLE (beg_YYYYMM int, end_YYYYMM int)
INSERT INTO @MONTHS SELECT 201802, 201805 
INSERT INTO @MONTHS SELECT 201711, 201801 

SELECT *
FROM @Months m
CROSS APPLY dbo.tvf_MonthRange(m.beg_YYYYMM, m.end_YYYYMM) mr ;

星期一不是个好主意,对吗?

一个使用理货表的解决方案,在较大的数据集上可能更快

with dates as(
select 
    201501 as beg_YYYYMM
    ,201504 as end_YYYYMM
union all
select '201711', '201801'
union all
select '201807', '201812'

),

--Tally table
ctedaterange AS (
SELECT top 15
    rn = Row_number() OVER(ORDER BY (SELECT NULL)) -1
FROM sys.objects a  
)

SELECT
    dates.*
    ,months_in_range = convert(varchar(6), Dateadd(mm, rn, cast(cast(dates.beg_YYYYMM as varchar(8)) +'01' as date)), 112)

FROM dates
    cross join ctedaterange

WHERE  
    rn <= Datediff(mm, cast(cast(dates.beg_YYYYMM as varchar(8)) +'01' as date), cast(cast(dates.end_YYYYMM as varchar(8)) +'01' as date))

ORDER BY 
    beg_YYYYMM
    ,Dateadd(mm, rn, cast(cast(dates.beg_YYYYMM as varchar(8)) +'01' as date))

使用计数表的解决方案,在较大的数据集上可能更快

with dates as(
select 
    201501 as beg_YYYYMM
    ,201504 as end_YYYYMM
union all
select '201711', '201801'
union all
select '201807', '201812'

),

--Tally table
ctedaterange AS (
SELECT top 15
    rn = Row_number() OVER(ORDER BY (SELECT NULL)) -1
FROM sys.objects a  
)

SELECT
    dates.*
    ,months_in_range = convert(varchar(6), Dateadd(mm, rn, cast(cast(dates.beg_YYYYMM as varchar(8)) +'01' as date)), 112)

FROM dates
    cross join ctedaterange

WHERE  
    rn <= Datediff(mm, cast(cast(dates.beg_YYYYMM as varchar(8)) +'01' as date), cast(cast(dates.end_YYYYMM as varchar(8)) +'01' as date))

ORDER BY 
    beg_YYYYMM
    ,Dateadd(mm, rn, cast(cast(dates.beg_YYYYMM as varchar(8)) +'01' as date))

我的建议是,为了进行此查询,将所有数据转换为日期数据类型-这样您就可以利用可用的日期时间函数。然后,您可以将结果转换回不寻常的int格式。我的建议是,出于此查询的目的,将所有数据转换为日期数据类型-这样您就可以利用可用的datetime函数。然后你可以把结果转换成不寻常的int格式。比我的要优雅得多。我羞愧地低下了头,比我的头优雅多了。我羞愧地低下了头。