我需要填充一个表来存储两个给定日期之间的日期范围:09/01/11 - 10/10/11
因此在这种情况下,表将从 09/01/11 开始并每天存储,直到 10/10/11 我想知道在 SQL Server 中是否有一种巧妙的方法可以做到这一点 - 我目前正在使用 SQL Server 2008。谢谢
易于使用 SQL 2005+;如果您有数字或统计表,则更容易。 我在下面伪造了它:
DECLARE @StartDate DATE = '20110901'
, @EndDate DATE = '20111001'
SELECT DATEADD(DAY, nbr - 1, @StartDate)
FROM ( SELECT ROW_NUMBER() OVER ( ORDER BY c.object_id ) AS nbr
FROM sys.columns c
) nbrs
WHERE nbr - 1 <= DATEDIFF(DAY, @StartDate, @EndDate)
如果您有统计表,请将子查询替换为该表。 没有递归。
编辑:由于人们似乎对统计表有疑问,让我使用从零开始的统计表重写它。 首先,这里有一些用于创建和填充表的代码。
CREATE TABLE [dbo].[nbrs](
[nbr] [INT] NOT NULL
) ON [PRIMARY]
GO
CREATE UNIQUE CLUSTERED INDEX [clidx] ON [dbo].[nbrs]
(
[nbr] ASC
)
GO
INSERT INTO dbo.nbrs (nbr)
SELECT nbr-1
FROM ( SELECT ROW_NUMBER() OVER ( ORDER BY c.object_id ) AS nbr
FROM sys.columns c
) nbrs
GO
现在,您已将数字表作为数据库中的永久对象,您可以将其重新用于查询而不是子查询。 该查询也已被编辑为使用从零开始的计算。
DECLARE @StartDate DATE = '20110901'
, @EndDate DATE = '20111001'
SELECT DATEADD(DAY, nbr, @DateStart)
FROM nbrs
WHERE nbr <= DATEDIFF(DAY, @DateStart, @DateEnd)
高性能,无递归。
如果您使用的是 SQL Server 2005 或更高版本,请尝试此操作:
WITH Dates AS (
SELECT
[Date] = CONVERT(DATETIME,'09/01/2011')
UNION ALL SELECT
[Date] = DATEADD(DAY, 1, [Date])
FROM
Dates
WHERE
Date < '10/10/2011'
) SELECT
[Date]
FROM
Dates
OPTION (MAXRECURSION 45)
这是一个使用 CTE 可以做的很酷的事情的好例子。
-- 声明
DECLARE @dates TABLE(dt DATE)
DECLARE @dateFrom DATE
DECLARE @dateTo DATE
SET @dateFrom = '2001/01/01'
SET @dateTo = '2001/01/12'
-- 查询:
WHILE(@dateFrom <= @dateTo)
BEGIN
INSERT INTO @dates
SELECT @dateFrom
SELECT @dateFrom = DATEADD(day, 1, @dateFrom)
END
-- 输出
SELECT * FROM @dates
这是一个不需要递归的解决方案,同时这个表值函数可以在许多查询中重复使用,而不需要再次重复声明样板变量。对于那些不想递归的人来说,这是唯一的选择。
创建这个简单的函数:
CREATE FUNCTION [dbo].[GenerateDateRange]
(@StartDate AS DATE,
@EndDate AS DATE,
@Interval AS INT
)
RETURNS @Dates TABLE(DateValue DATE)
AS
BEGIN
DECLARE @CUR_DATE DATE
SET @CUR_DATE = @StartDate
WHILE @CUR_DATE <= @EndDate BEGIN
INSERT INTO @Dates VALUES(@CUR_DATE)
SET @CUR_DATE = DATEADD(DAY, @Interval, @CUR_DATE)
END
RETURN;
END;
然后选择:
select *
from dbo.GenerateDateRange('2017-01-03', '2017-12-01', 1)
我意识到这是一个旧线程,但我不得不承认我对这里给出的过多的递归和循环解决方案感到沮丧。我想知道有多少人意识到递归只不过是一个非常昂贵的循环?我理解创建表值函数的愿望,但我建议以下方法更加有效,因为它是基于集合的,无需循环、递归或重复的单个插入语句:
CREATE FUNCTION dbo.GenerateDateRange(@StartDate AS DATE, @EndDate AS DATE)
RETURNS TABLE WITH SCHEMABINDING AS
WITH e1(n) AS (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS x(n)) -- 16 records
,e2(n) AS (SELECT 1 FROM e1 a CROSS JOIN e1 b) -- 16^2 or 256 records (16*16)
,cteTally(n) AS (SELECT ROW_NUMBER() over (ORDER BY 1) AS n FROM e2 a CROSS JOIN e2 b) -- 16^4 or 65,536 records (256*256)
SELECT DATEADD(DAY, n-1, @StartDate)
FROM cteTally
WHERE n <= DATEDIFF(DAY, @StartDate, @EndDate) + 1;
GO
这是一个旧线程,但如果它对任何人有帮助,这是我在支持 CTE 的 SQL Server 现代版本中使用的。这还为您提供了星期几,并且可以对其进行调整以提供您可能需要的其他值(即季度、月份等)。
DECLARE @StartDate datetime
DECLARE @EndDate datetime
SET @StartDate = '1/1/2020'
SET @EndDate = '12/31/2020'
DECLARE @DayTable Table(theDate date, theDayOfWeek nvarchar(50));
WITH DayTable AS (SELECT CAST(@StartDate AS DATETIME) theDate, DATENAME(dw, @StartDate) theDayOfWeek UNION ALL SELECT DATEADD(dd, 1, theDate), DATENAME(dw,DATEADD(dd, 1, theDate)) FROM DayTable s WHERE DATEADD(dd, 1, theDate) <= CAST(@EndDate AS DATETIME))
INSERT INTO @DayTable(theDate, theDayOfWeek) SELECT theDate, theDayOfWeek FROM DayTable OPTION (MAXRECURSION 365);
SELECT * FROM @DayTable
使用MVJ的F_TABLE_DATE函数,简直太棒了:
http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=61519
一旦实现,只需传入开始和结束日期,您就可以插入其间的所有日期。
如果由于某种原因你不能使用
declare
变量,例如在Looker中使用派生表时,你可以像这样:
select
dateadd(day, nbr - 1, convert(date, '2017-01-01')) as d
from (
select row_number() over (order by c.object_id) as nbr from sys.columns c
) nbrs
where
nbr - 1 <= datediff(
day,
convert(date, '2017-01-01'),
convert(date, '2018-12-31')
)
顺便说一下,这就是您的日期系列视图在 LookerML 中的样子:
view: date_series {
derived_table: {
sql:
select
dateadd(day, nbr - 1, convert(date, '2017-01-01')) as d
from (
select row_number() over (order by c.object_id) as nbr from sys.columns c
) nbrs
where
nbr - 1 <= datediff(day, convert(date, '2017-01-01'), convert(date, '2018-12-31')) ;;
}
dimension: date {
primary_key: yes
type: date
sql: ${TABLE}.d ;;
}
}
尝试以下代码:
DECLARE @DateStart DATE = '2021-01-20' , @DateEnd DATE = '2021-01-29';
with Extract_Dates_CTE (MyDate) as (
select @DateStart
Union ALL
select DATEADD(day, 1, MyDate)
from Extract_Dates_CTE
where MyDate < @DateEnd
)
select ROW_NUMBER() OVER(ORDER BY a.MyDate) AS RowDateID, a.MyDate AS ExtractedDates
from Extract_Dates_CTE a;
检查性能,我发现使用 CTE 方法具有更好的性能,如图所示。为此,我使用了两个查询并使用 SQL Server 工具显示性能。
DECLARE @DateStart DATE = '2021-01-20' , @DateEnd DATE = '2021-01-29';
with Extract_Dates_CTE (MyDate) as (
select @DateStart
Union ALL
select DATEADD(day, 1, MyDate)
from Extract_Dates_CTE
where MyDate < @DateEnd
)
select ROW_NUMBER() OVER(ORDER BY a.MyDate) AS RowDateID, a.MyDate AS ExtractedDates
from Extract_Dates_CTE a;
SELECT DATEADD(DAY, nbr - 1, @DateStart)
FROM ( SELECT ROW_NUMBER() OVER ( ORDER BY c.object_id ) AS nbr
FROM sys.columns c
) nbrs
WHERE nbr - 1 <= DATEDIFF(DAY, @DateStart, @DateEnd)
使用 @Abe Miesler 的答案,为了其他人的方便,我将其构建到 SQL Server 2008 及以后的 TVF 中。它可能对其他人有帮助 - 我必须找到一种方法将 CTE 包含在 TVF 中!
--Generate a range of dates with interval option, courtesy of Abe Miessler for the core query here!
CREATE OR ALTER FUNCTION [dbo].[DateRange]
(@startDate AS DATE,
@EndDate AS DATE,
@interval AS INT
)
RETURNS @Dates TABLE(dateValue DATE)
AS
BEGIN
WITH Dates
AS (
SELECT [Date] = CONVERT( DATETIME, @startDate)
UNION ALL
SELECT [Date] = DATEADD(DAY, ISNULL(@interval, 1), [Date])
FROM Dates
WHERE Date < @EndDate)
INSERT INTO @Dates
SELECT [Date]
FROM Dates
OPTION(MAXRECURSION 900);
RETURN;
END;
Declare @StartDate datetime = '2015-01-01'
Declare @EndDate datetime = '2016-12-01'
declare @DaysInMonth int
declare @tempDateRange Table
(
DateFrom datetime,
DateThru datetime
);
While @StartDate<=@EndDate
begin
SET @DaysInMonth=DAY(DATEADD(DD,-1,DATEADD(MM,DATEDIFF(MM,-1,@StartDate),0)))
IF DAY(@StartDate)=1
SET @EndDate=DATEADD(DAY,14,@StartDate)
ELSE IF DAY(@StartDate)=16 AND @DaysInMonth=30
SET @EndDate=DATEADD(DAY,14,@StartDate)
ELSE IF DAY(@StartDate)=16 AND @DaysInMonth=31
SET @EndDate=DATEADD(DAY,15,@StartDate)
ELSE IF DAY(@StartDate)=16 AND @DaysInMonth=28
SET @EndDate=DATEADD(DAY,12,@StartDate)
ELSE IF DAY(@StartDate)=16 AND @DaysInMonth=29
SET @EndDate=DATEADD(DAY,13,@StartDate)
INSERT INTO @tempDateRange (DateFrom,DateThru)
VALUES
(
@StartDate,
@EndDate
)
SET @StartDate=DATEADD(DAY,1,@EndDate)
IF @EndDate< '2016-12-31'
IF DAY(@StartDate)=1
SET @EndDate=DATEADD(DAY,14,@StartDate)
ELSE IF DAY(@StartDate)=16 AND @DaysInMonth=30
SET @EndDate=DATEADD(DAY,14,@StartDate)
ELSE IF DAY(@StartDate)=16 AND @DaysInMonth=31
SET @EndDate=DATEADD(DAY,15,@StartDate)
ELSE IF DAY(@StartDate)=16 AND @DaysInMonth=28
SET @EndDate=DATEADD(DAY,12,@StartDate)
ELSE IF DAY(@StartDate)=16 AND @DaysInMonth=29
SET @EndDate=DATEADD(DAY,13,@StartDate)
end ;
select * from @tempDateRange
+++++++++++++++++++++++++++++
Result:
DateFrom |DateThru
CREATE table #ProductSales (ProjectID Int, ProjectName varchar(100), TotalBillableFees Money, StartDate Date, EndDate Date, DataDate Date)
Insert into #ProductSales
Values
(373104,'Product Sales - Flex Creation Test',40000.00,'2019-04-01','2020-06-01','2019-08-01'),
(375111,'Product Sales - SMART',40000.00,'2019-04-01','2019-09-01','2019-08-01')
;WITH Dates AS (
SELECT ProjectiD
,Convert(decimal(10,2),TotalBillableFees/IIF(DATEDIFF(MONTH,StartDate,EndDate)=0,1,DATEDIFF(MONTH,StartDate,EndDate))) AS BillableFeesPerMonths,EndDate
,[Date] = CONVERT(DATETIME,EOMONTH(StartDate))
FROM #ProductSales
UNION ALL SELECT ProjectiD,BillableFeesPerMonths,EndDate,
[Date] = DATEADD(MONTH, 1, [Date])
FROM
Dates
WHERE
Date < EOMONTH(EndDate)
) SELECT ProjectID,BillableFeesPerMonths,
CAST([Date] as Date) Date
FROM
Dates
OPTION (MAXRECURSION 45)
我在 Databricks 上使用 ANSI 标准 SQL 方言,下面的代码是创建月份行的更简单方法:
SELECT
EXPLODE(
SEQUENCE(
CURRENT_DATE() + MAKE_INTERVAL(0, -11),
CURRENT_DATE() + MAKE_INTERVAL(0, 1),
INTERVAL 1 MONTH))
当我们无法在数据库中创建函数时,递归查询是一个不错的选择。
WITH RECURSIVE dates AS (
SELECT '2022-01-01' AS _day -- Your start date
UNION ALL
SELECT DATE_ADD(_day, INTERVAL 1 DAY)
FROM dates
WHERE _day < '2022-10-12' -- Your end date
)
WITH RECURSIVE dates AS (
SELECT DATE('2022-01-01') AS _day -- Your start date
UNION ALL
SELECT DATE(_day + INTERVAL '1 day')
FROM dates
WHERE _day < '2022-10-12' -- Your end date
)
要在 SELECT 语句中加入这些日期,您可以使用
JOIN dates ON true
复制日期范围内每个日期的行。
[WITH statement according to your database]
SELECT col1, col2, _day
FROM my_table
JOIN dates ON true
如果您的数据库支持递归公用表表达式,这很容易做到。
这是 MySQL 8 上的示例。
WITH
RECURSIVE
my_dates AS (
SELECT
date('2021-10-01') as s
UNION ALL
SELECT
DATE_ADD(s, INTERVAL 1 DAY)
from
my_dates
WHERE
s < '2022-10-31' # Desired End Date
)
SELECT
*
FROM
my_dates;
例如,如果您希望日期向后计数,则可以使用 DATE_SUB() 并相应地调整 where 子句。您还可以根据需要将日期增加一天以上,只需以超过一天的间隔进行日期计算即可。
参考: https://nucco.org/2023/02/generate-a-series-of-dates-in-sql.html