Sql server 使用函数获取两个日期之间的日期列表

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

我的问题类似于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 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