每个 Int 扩展每一行的 SQL 查询。在 COL_A 和 COL_B 之间的范围内
Posted
技术标签:
【中文标题】每个 Int 扩展每一行的 SQL 查询。在 COL_A 和 COL_B 之间的范围内【英文标题】:SQL Query Expanding Each Row Every Int. In Range Between COL_A and COL_B 【发布时间】:2018-12-31 21:31:09 【问题描述】:我的 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 |
+------------+------------+----------------+
【问题讨论】:
我的建议是,出于此查询的目的,将所有数据转换为date
数据类型 - 这样您就可以利用可用的 datetime
函数。然后您可以将结果转换回unusual
int 格式。
【参考方案1】:
使用递归 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;
Here 是一个 dbfiddle。
【讨论】:
比我的优雅多了。我羞愧地低下了头。【参考方案2】:我真的不认为我会实际部署它,但我想知道它会是什么样子。如果你创建一个 TVF 做丑陋的工作,那还不错。我使用 Aaron Bertrand 的 DateDim code 进行了快速修改,以获取传入的两个日期之间的月初日期。
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 ;
星期一不是个好主意吗?
【讨论】:
【参考方案3】:使用计数表的解决方案在较大的数据集上可能会更快。
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))
Here's the db<>fiddle
【讨论】:
以上是关于每个 Int 扩展每一行的 SQL 查询。在 COL_A 和 COL_B 之间的范围内的主要内容,如果未能解决你的问题,请参考以下文章