Sql server 使用函数获取两个日期之间的日期列表
我的问题类似于MySQL问题,但适用于SQL Server: 是否有函数或查询将返回两个日期之间的天数列表?例如,假设有一个名为ExplodeDates的函数:Sql server 使用函数获取两个日期之间的日期列表,sql-server,date,Sql Server,Date,我的问题类似于MySQL问题,但适用于SQL Server: 是否有函数或查询将返回两个日期之间的天数列表?例如,假设有一个名为ExplodeDates的函数: SELECT ExplodeDates('2010-01-01', '2010-01-13'); 这将返回一个包含以下值的单列表: 2010-01-01 2010-01-02 2010-01-03 2010-01-04 2010-01-05 2010-01-06 2010-01-07 2010-01-08 2010-01-09 201
SELECT ExplodeDates('2010-01-01', '2010-01-13');
这将返回一个包含以下值的单列表:
2010-01-01
2010-01-02
2010-01-03
2010-01-04
2010-01-05
2010-01-06
2010-01-07
2010-01-08
2010-01-09
2010-01-10
2010-01-11
2010-01-12
2010-01-13
我想一个日历/数字表也许能在这里帮助我
更新
我决定看一下提供的三个代码答案,执行结果(占总批次的百分比)是:
: 18%
: 41%
: 41%
越低越好
我接受了Rob Farley的答案,因为这是最快的答案,尽管KM和StingyJack在答案中使用的数字表解决方案是我最喜欢的。罗布·法利的速度快了三分之二
更新2
Alivia的更简洁。我已经更改了公认的答案。一些想法:
WITH mycte AS
(
SELECT YEAR(CONVERT(DATE, '2006-01-01',102)) DateValue
UNION ALL
SELECT DateValue + 1
FROM mycte
WHERE DateValue + 1 < = YEAR(GETDATE())
)
SELECT DateValue
FROM mycte
OPTION (MAXRECURSION 0)
如果您需要列表日期来循环它们,您可以使用开始日期和日期计数参数,并在创建日期和使用日期时执行while循环
使用C CLR存储过程并用C编写代码
在数据库之外的代码中执行此操作我是一名oracle人员,但我相信MS SQL Server支持connect by子句:
select sysdate + level
from dual
connect by level <= 10 ;
Dual只是oracle附带的一个“dummy”表,它包含1行,单词“dummy”作为单列的值 在使用my函数之前,您需要设置一个helper表,每个数据库只需执行一次:
CREATE TABLE Numbers
(Number int NOT NULL,
CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number ASC)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
DECLARE @x int
SET @x=0
WHILE @x<8000
BEGIN
SET @x=@x+1
INSERT INTO Numbers VALUES (@x)
END
输出:
Date
-----------------------
2010-01-01 00:00:00.000
2010-01-02 00:00:00.000
2010-01-03 00:00:00.000
2010-01-04 00:00:00.000
2010-01-05 00:00:00.000
2010-01-06 00:00:00.000
2010-01-07 00:00:00.000
2010-01-08 00:00:00.000
2010-01-09 00:00:00.000
2010-01-10 00:00:00.000
2010-01-11 00:00:00.000
2010-01-12 00:00:00.000
2010-01-13 00:00:00.000
(13 row(s) affected)
所有这些日期都已经在数据库中了,还是您只想知道这两个日期之间的日期?如果是第一次,您可以使用BETWEEN或=来查找BETWEEN之间的日期 例如:
SELECT column_name(s)
FROM table_name
WHERE column_name
BETWEEN value1 AND value2
或
试着这样做:
CREATE FUNCTION dbo.ExplodeDates(@startdate datetime, @enddate datetime)
returns table as
return (
with
N0 as (SELECT 1 as n UNION ALL SELECT 1)
,N1 as (SELECT 1 as n FROM N0 t1, N0 t2)
,N2 as (SELECT 1 as n FROM N1 t1, N1 t2)
,N3 as (SELECT 1 as n FROM N2 t1, N2 t2)
,N4 as (SELECT 1 as n FROM N3 t1, N3 t2)
,N5 as (SELECT 1 as n FROM N4 t1, N4 t2)
,N6 as (SELECT 1 as n FROM N5 t1, N5 t2)
,nums as (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) as num FROM N6)
SELECT DATEADD(day,num-1,@startdate) as thedate
FROM nums
WHERE num <= DATEDIFF(day,@startdate,@enddate) + 1
);
验收后编辑:
请注意。。。如果已经有足够大的nums表,则应使用:
CREATE FUNCTION dbo.ExplodeDates(@startdate datetime, @enddate datetime)
returns table as
return (
SELECT DATEADD(day,num-1,@startdate) as thedate
FROM nums
WHERE num <= DATEDIFF(day,@startdate,@enddate) + 1
);
这些行将创建一个包含1M行的数字表。。。而且比一个接一个的插入要快得多
您不应该使用包含开始和结束的函数创建ExplodeDates函数,因为查询优化器根本无法简化查询。确切地说是一个数字表,但如果您确实需要性能,您可能需要使用Mark Redman的CLR进程/程序集思想 如何创建日期表以及创建数字表的超快速方法
/*Gets a list of integers into a temp table (Jeff Moden's idea from SqlServerCentral.com)*/
SELECT TOP 10950 /*30 years of days*/
IDENTITY(INT,1,1) as N
INTO #Numbers
FROM Master.dbo.SysColumns sc1,
Master.dbo.SysColumns sc2
/*Create the dates table*/
CREATE TABLE [TableOfDates](
[fld_date] [datetime] NOT NULL,
CONSTRAINT [PK_TableOfDates] PRIMARY KEY CLUSTERED
(
[fld_date] ASC
)WITH FILLFACTOR = 99 ON [PRIMARY]
) ON [PRIMARY]
/*fill the table with dates*/
DECLARE @daysFromFirstDateInTheTable int
DECLARE @firstDateInTheTable DATETIME
SET @firstDateInTheTable = '01/01/1998'
SET @daysFromFirstDateInTheTable = (SELECT (DATEDIFF(dd, @firstDateInTheTable ,GETDATE()) + 1))
INSERT INTO
TableOfDates
SELECT
DATEADD(dd,nums.n - @daysFromFirstDateInTheTable, CAST(FLOOR(CAST(GETDATE() as FLOAT)) as DateTime)) as FLD_Date
FROM #Numbers nums
现在您已经有了一个日期表,您可以使用一个函数而不是像KM那样的过程来获取日期表
CREATE FUNCTION dbo.ListDates
(
@StartDate DATETIME
,@EndDate DATETIME
)
RETURNS
@DateList table
(
Date datetime
)
AS
BEGIN
/*add some validation logic of your own to make sure that the inputs are sound.Adjust the rest as needed*/
INSERT INTO
@DateList
SELECT FLD_Date FROM TableOfDates (NOLOCK) WHERE FLD_Date >= @StartDate AND FLD_Date <= @EndDate
RETURN
END
也许如果你想走一条更容易的路,这应该可以做到
WITH date_range (calc_date) AS (
SELECT DATEADD(DAY, DATEDIFF(DAY, 0, CURRENT_TIMESTAMP) - 6, 0)
UNION ALL SELECT DATEADD(DAY, 1, calc_date)
FROM date_range
WHERE DATEADD(DAY, 1, calc_date) < CURRENT_TIMESTAMP)
SELECT calc_date
FROM date_range;
但是临时表也是一个非常好的方法。也许你也会考虑一个填充的日历表。< P>你只需在下面的代码中更改硬编码值。
DECLARE @firstDate datetime
DECLARE @secondDate datetime
DECLARE @totalDays INT
SELECT @firstDate = getDate() - 30
SELECT @secondDate = getDate()
DECLARE @index INT
SELECT @index = 0
SELECT @totalDays = datediff(day, @firstDate, @secondDate)
CREATE TABLE #temp
(
ID INT NOT NULL IDENTITY(1,1)
,CommonDate DATETIME NULL
)
WHILE @index < @totalDays
BEGIN
INSERT INTO #temp (CommonDate) VALUES (DATEADD(Day, @index, @firstDate))
SELECT @index = @index + 1
END
SELECT CONVERT(VARCHAR(10), CommonDate, 102) as [Date Between] FROM #temp
DROP TABLE #temp
在SQLServer中,这几行是这个问题的简单答案
WITH mycte AS
(
SELECT CAST('2011-01-01' AS DATETIME) DateValue
UNION ALL
SELECT DateValue + 1
FROM mycte
WHERE DateValue + 1 < '2021-12-31'
)
SELECT DateValue
FROM mycte
OPTION (MAXRECURSION 0)
这正是你想要的,修改自威尔之前的帖子。不需要辅助表或循环
WITH date_range (calc_date) AS (
SELECT DATEADD(DAY, DATEDIFF(DAY, 0, '2010-01-13') - DATEDIFF(DAY, '2010-01-01', '2010-01-13'), 0)
UNION ALL SELECT DATEADD(DAY, 1, calc_date)
FROM date_range
WHERE DATEADD(DAY, 1, calc_date) <= '2010-01-13')
SELECT calc_date
FROM date_range;
详细信息在双表上,但如果您将此表交换为虚拟表,则此操作有效。答案在此处
SELECT dateadd(dd,DAYS,'2013-09-07 00:00:00') DATES
INTO #TEMP1
FROM
(SELECT TOP 365 colorder - 1 AS DAYS from master..syscolumns
WHERE id = -519536829 order by colorder) a
WHERE datediff(dd,dateadd(dd,DAYS,'2013-09-07 00:00:00'),'2013-09-13 00:00:00' ) >= 0
AND dateadd(dd,DAYS,'2013-09-07 00:00:00') <= '2013-09-13 00:00:00'
SELECT * FROM #TEMP1
派对有点晚了,但我很喜欢这个解决方案
CREATE FUNCTION ExplodeDates(@startDate DateTime, @endDate DateTime)
RETURNS table as
return (
SELECT TOP (DATEDIFF(DAY, @startDate, @endDate) + 1)
DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, @startDate) AS DATE
FROM sys.all_objects a
CROSS JOIN sys.all_objects b
)
-半打中的六个。假设MsSql的另一种方法
Declare @MonthStart datetime = convert(DateTime,'07/01/2016')
Declare @MonthEnd datetime = convert(DateTime,'07/31/2016')
Declare @DayCount_int Int = 0
Declare @WhileCount_int Int = 0
set @DayCount_int = DATEDIFF(DAY, @MonthStart, @MonthEnd)
select @WhileCount_int
WHILE @WhileCount_int < @DayCount_int + 1
BEGIN
print convert(Varchar(24),DateAdd(day,@WhileCount_int,@MonthStart),101)
SET @WhileCount_int = @WhileCount_int + 1;
END;
如果要打印从特定年份开始到当前日期的年份。只是修改了被接受的答案
WITH mycte AS
(
SELECT YEAR(CONVERT(DATE, '2006-01-01',102)) DateValue
UNION ALL
SELECT DateValue + 1
FROM mycte
WHERE DateValue + 1 < = YEAR(GETDATE())
)
SELECT DateValue
FROM mycte
OPTION (MAXRECURSION 0)
此查询适用于Microsoft SQL Server
select distinct format( cast('2010-01-01' as datetime) + ( a.v / 10 ), 'yyyy-MM-dd' ) as aDate
from (
SELECT ones.n + 10 * tens.n + 100 * hundreds.n + 1000 * thousands.n as v
FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ones(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tens(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) hundreds(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) thousands(n)
) a
where format( cast('2010-01-01' as datetime) + ( a.v / 10 ), 'yyyy-MM-dd' ) < cast('2010-01-13' as datetime)
order by aDate asc;
此部分将字符串转换为日期,并从内部查询向其添加一个数字
cast('2010-01-01' as datetime) + ( a.v / 10 )
然后我们将结果转换为您想要的格式。这也是列名
format( cast('2010-01-01' as datetime) + ( a.v / 10 ), 'yyyy-MM-dd' )
接下来,我们只提取不同的值,并为列名提供aDate的别名
distinct format( cast('2010-01-01' as datetime) + ( a.v / 10 ), 'yyyy-MM-dd' ) as aDate
我们使用where子句只在您想要的范围内过滤日期。请注意,我们在这里使用列名,因为SQL Server不接受where子句中的列别名aDate
where format( cast('2010-01-01' as datetime) + ( a.v / 10 ), 'yyyy-MM-dd' ) < cast('2010-01-13' as datetime)
如果您像我一样处于过程和函数被禁止的情况,并且您的sql用户没有插入权限,因此不允许插入,也不允许设置/声明临时变量,例如@c,但您希望生成特定时间段内的日期列表,例如本年以进行聚合,请使用此
select * from
(select adddate('1970-01-01',t4*10000 + t3*1000 + t2*100 + t1*10 + t0) gen_date from
(select 0 t0 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t0,
(select 0 t1 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t1,
(select 0 t2 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t2,
(select 0 t3 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t3,
(select 0 t4 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t4) v
where gen_date between '2017-01-01' and '2017-12-31'
结果:
SQL Server没有内置的双表,您需要创建自己的双表,就像我在示例代码中所做的那样。我认为sysdate在SQL Server中是GETDATE,而connect by是无效的语法。因此,您也可以从SQL Server中的任何位置进行选择。SELECT GETDATE是SQL Server中的有效代码行,而不是Oracle中的有效代码行,即使您将GETDATE函数替换为其SYSDATE同系物。您说得对,Brian,在Oracle中,我们会这样做。Oracle和Oracle中有许多有趣的功能
不包含在TSQL和SQL Server中的PL/SQL。这是Sybase的错!;-SQL Server主要基于Sysbase TSQL语言。SELECT GETDATE不会仅生成一行集合。在Oracle中使用dual可以得到一个集合。Little offtop:实际上列名是DUMMY,值是X-如果性能至关重要,CLR存储过程是一条出路。@StingyJack,不行。数字表的效率会更高,请参见我的答案,以了解如何使用。性能并不重要,因为它最多每小时调用一次,平均每天调用一次,然后缓存。不过,我不想使用CLR来做这件事。@KM-在这里有可能引发一场火焰之战,您应该知道SQL并不是为处理过程操作而设计的,它的性能很差。如果您需要这样做,最好由应用程序代码来处理。@StingyJack,我的函数是如何实现的?除了验证检查之外,它是一个简单的查询,为固定日期添加一个偏移量,以获得可变数量的行。在项目详细信息上进行的计算没有太大的不同,比如根据数量和单价计算总价(有或没有货币)。您只需使用此代码创建一个存储过程,并可能用您的或类似的内容替换当前的时间戳值。有一段时间就有一个循环@吝啬鬼杰克,你疯了吗,我的函数没有循环。我使用一个循环来设置数字表,这样人们就可以很容易地看到它的作用。我可以很容易地从这里使用CTE,但它让一些人感到困惑。对于表的一次性设置,这不是问题。他指的是您在数字表中输入值的位置。@KM。。你的代码中肯定有一个循环。在你打我之前帮我个忙。。。测试使用您和我的方法创建10000个数字的数字表需要多长时间。你会看到我从摩登借来的一个将比循环运算快几个数量级。接下来,测试每个函数需要多长时间。我敢打赌,我提供的解决方案将比您得到的解决方案性能更好。下次你想给某人录像时,你最好确保你的理由是有根据的。我喜欢数字表的想法!它们的功能多得可笑,还可以用于其他用途。SQL中的循环性能将受到影响。请在尝试这些答案时记住这一点。执行时间呢?%总批次的百分比用于确定瓶颈,而不是吞吐量。您是在测试实际的函数调用还是它的所有其他功能?比较小批量和大批量的结果?使用“1/1/1998”、“12/31/2020”调用的所有三个function上的SET STATISTICS TIME,报告相同的CPU时间=0 ms,经过的时间=1 ms。当使用“1/1/1900”、“1921-11-27”调用Rob's和mine时,StingyJacks无法完成Rob的日期范围:CPU时间=93 ms,运行时间=93毫秒。我得到了我的:CPU时间=0毫秒,运行时间=1毫秒,我的看起来好多了。您使用什么测试方法@Dan Atkinson?如果您包括一次性数字表设置,这是一种非常有缺陷的方法,因为它不能反映实际使用性能。@KM和@StingyJack。谢谢你们两位教导我正确的基准测试方法。和KM,感谢您不厌其烦地指出实际的基准测试结果。我将在我的数据库上运行一些,并相应地更新问题。再次感谢!你为什么改变答案?Alivia的回答需要一个提示,以确保它包含足够的值,并且它不是所要求的函数。为什么需要一个日期表,您只需使用数字表计算它们??因为动态计算它们可能会导致性能不佳,特别是当它们被内联使用并为语句访问的每一行求值时。Msg 137,级别15,状态2,第23行必须声明标量变量@。这个SELECT DATEDIFFdd、@firstDateInTheTable、GETDATE+1应该是SELECT DATEDIFFdd、@firstDateInTheTable、GETDATE+1。感谢您花时间改进您的答案。我也不知道使用BEGIN和END会阻止查询优化器完成它的工作。谢谢如果我能不止一次地投票,我会的——它的表现是惊人的。我用一个简单的版本对它进行了测试,nums是一个数字表,数字上有一个聚集索引。如果日期差为2天,则CTE比聚集指数高出28%和72%,但如果日期差为37年,则CTE版本为3%和97%!我希望我知道它为什么这么快…因为它不必做任何I/O。在更复杂的日期集上,我会出现以下错误:语句终止。在语句完成之前,已耗尽最大递归100。因此,我应该指出,对于希望在大范围内使用此答案的其他人,您需要添加一个maxrecursion值-选项maxrecursion 0。您能在示例中提供一个解释吗?这不是一个可行的方法
我的回答有几个原因。1:主表并非总是可用。2:表的长度仅与数据库中的项目数相同。如果这小于实际答案,则返回的该进程列表将不正确。3:答案或多或少是一个使用系统表的数字表。我想知道为什么“2011-01-01”不适合我,而“20110101”适合我。这太棒了!读卡器:根据您的需求,将where子句设置为包含或排除最终日期。投票表决。
Declare @MonthStart datetime = convert(DateTime,'07/01/2016')
Declare @MonthEnd datetime = convert(DateTime,'07/31/2016')
Declare @DayCount_int Int = 0
Declare @WhileCount_int Int = 0
set @DayCount_int = DATEDIFF(DAY, @MonthStart, @MonthEnd)
select @WhileCount_int
WHILE @WhileCount_int < @DayCount_int + 1
BEGIN
print convert(Varchar(24),DateAdd(day,@WhileCount_int,@MonthStart),101)
SET @WhileCount_int = @WhileCount_int + 1;
END;
DECLARE @MinDate DATETIME = '2012-09-23 00:02:00.000',
@MaxDate DATETIME = '2012-09-25 00:00:00.000';
SELECT TOP (DATEDIFF(DAY, @MinDate, @MaxDate) + 1) Dates = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, @MinDate)
FROM sys.all_objects a CROSS JOIN sys.all_objects b;
WITH mycte AS
(
SELECT YEAR(CONVERT(DATE, '2006-01-01',102)) DateValue
UNION ALL
SELECT DateValue + 1
FROM mycte
WHERE DateValue + 1 < = YEAR(GETDATE())
)
SELECT DateValue
FROM mycte
OPTION (MAXRECURSION 0)
select distinct format( cast('2010-01-01' as datetime) + ( a.v / 10 ), 'yyyy-MM-dd' ) as aDate
from (
SELECT ones.n + 10 * tens.n + 100 * hundreds.n + 1000 * thousands.n as v
FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ones(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tens(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) hundreds(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) thousands(n)
) a
where format( cast('2010-01-01' as datetime) + ( a.v / 10 ), 'yyyy-MM-dd' ) < cast('2010-01-13' as datetime)
order by aDate asc;
SELECT ones.n + 10 * tens.n + 100 * hundreds.n + 1000 * thousands.n as v
FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ones(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tens(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) hundreds(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) thousands(n)
) a;
cast('2010-01-01' as datetime) + ( a.v / 10 )
format( cast('2010-01-01' as datetime) + ( a.v / 10 ), 'yyyy-MM-dd' )
distinct format( cast('2010-01-01' as datetime) + ( a.v / 10 ), 'yyyy-MM-dd' ) as aDate
where format( cast('2010-01-01' as datetime) + ( a.v / 10 ), 'yyyy-MM-dd' ) < cast('2010-01-13' as datetime)
order by aDate asc;
select * from
(select adddate('1970-01-01',t4*10000 + t3*1000 + t2*100 + t1*10 + t0) gen_date from
(select 0 t0 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t0,
(select 0 t1 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t1,
(select 0 t2 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t2,
(select 0 t3 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t3,
(select 0 t4 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t4) v
where gen_date between '2017-01-01' and '2017-12-31'
DECLARE @StartDate DATE = '2017-09-13', @EndDate DATE = '2017-09-16'
SELECT date FROM ( SELECT DATE = DATEADD(DAY, rn - 1, @StartDate) FROM (
SELECT TOP (DATEDIFF(DAY, @StartDate, DATEADD(DAY,1,@EndDate)))
rn = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
FROM sys.all_objects AS s1
CROSS JOIN sys.all_objects AS s2
ORDER BY s1.[object_id] ) AS x ) AS y
2017-09-13
2017-09-14
2017-09-15
2017-09-16