在SQL Server中获得一周的第一天


96

我正在尝试按周对记录进行分组,将汇总日期存储为一周的第一天。但是,我用于四舍五入的标准技术似乎无法在几周内正常工作(尽管它适用于几天,几个月,几年,季度以及我应用过的其他任何时间范围)。

这是SQL:

select "start_of_week" = dateadd(week, datediff(week, 0, getdate()), 0);

返回2011-08-22 00:00:00.000,这是星期一,而不是星期日。选择@@datefirstreturn 7,这是星期日的代码,据我所知,服务器已正确设置。

通过将上面的代码更改为:

select "start_of_week" = dateadd(week, datediff(week, 0, getdate()), -1);

但是,我必须作例外处理的事实使我有些不安。另外,如果这是一个重复的问题,则表示歉意。我发现了一些相关的问题,但是没有一个问题专门针对此方面。


9
(@@DATEFIRST + DATEPART(DW, @SomeDate)) % 7无论@@datefirst我认为如何,都保持不变。星期一=
马丁·史密斯

Answers:


148

要回答为什么您得到星期一而不是星期日:

您要为日期0添加几周。什么是日期0?1900-01-01。1900-01-01是几号?星期一。因此,在您的代码中,您说的是,自1900年1月1日星期一以来已经过去了几周?我们称它为[n]。好的,现在将[n]个星期添加到1900年1月1日,星期一。您不必惊讶于这最终是星期一。DATEADD不知道您要添加几周,而只是直到您到达一个星期日,才添加了7天,然后又添加了7天,...就像DATEDIFF仅识别已越过的边界一样。例如,尽管有些人抱怨应该内置一些明智的逻辑来向上或向下取整,但它们都返回1:

SELECT DATEDIFF(YEAR, '2010-01-01', '2011-12-31');
SELECT DATEDIFF(YEAR, '2010-12-31', '2011-01-01');

回答如何获得星期日:

如果您想使用星期日,则选择不是星期一,而是星期日的基准日期。例如:

DECLARE @dt DATE = '1905-01-01';
SELECT [start_of_week] = DATEADD(WEEK, DATEDIFF(WEEK, @dt, CURRENT_TIMESTAMP), @dt);

如果您更改DATEFIRST设置(或者代码正在为具有其他设置的用户运行),这不会中断-前提是无论当前设置如何,您仍然希望使用星期日。如果您希望这两个答案,牛仔舞,那么你应该使用一个功能依赖于DATEFIRST设置,例如,

SELECT DATEADD(DAY, 1-DATEPART(WEEKDAY, CURRENT_TIMESTAMP), CURRENT_TIMESTAMP);

因此,如果将DATEFIRST设置更改为星期一,星期二,那么您的行为将会改变。根据所需的行为,可以使用以下功能之一:

CREATE FUNCTION dbo.StartOfWeek1 -- always a Sunday
(
    @d DATE
)
RETURNS DATE
AS
BEGIN
    RETURN (SELECT DATEADD(WEEK, DATEDIFF(WEEK, '19050101', @d), '19050101'));
END
GO

...要么...

CREATE FUNCTION dbo.StartOfWeek2 -- always the DATEFIRST weekday
(
    @d DATE
)
RETURNS DATE
AS
BEGIN
    RETURN (SELECT DATEADD(DAY, 1-DATEPART(WEEKDAY, @d), @d));
END
GO

现在,您有很多选择,但是哪个选择效果最好?如果会出现重大差异,我会感到惊讶,但我收集了到目前为止提供的所有答案,并通过两套测试对它们进行了测试-一套便宜而一套昂贵。我测量了客户端统计信息,因为在这里我看不到I / O或内存在性能中起作用(尽管根据功能的使用方式它们可能会起作用)。在我的测试中,结果是:

“便宜”分配查询:

Function - client processing time / wait time on server replies / total exec time
Gandarez     - 330/2029/2359 - 0:23.6
me datefirst - 329/2123/2452 - 0:24.5
me Sunday    - 357/2158/2515 - 0:25.2
trailmax     - 364/2160/2524 - 0:25.2
Curt         - 424/2202/2626 - 0:26.3

“昂贵”分配查询:

Function - client processing time / wait time on server replies / total exec time
Curt         - 1003/134158/135054 - 2:15
Gandarez     -  957/142919/143876 - 2:24
me Sunday    -  932/166817/165885 - 2:47
me datefirst -  939/171698/172637 - 2:53
trailmax     -  958/173174/174132 - 2:54

如果需要,我可以中继测试的详细信息-在此停止,因为这已经变得很漫长了。鉴于计算和内联代码的数量,看到Curt在​​高端市场上最快出现时,我感到有些惊讶。也许我会进行一些更彻底的测试并发布有关此事的博客...如果你们对我没有异议,请在其他地方发布您的函数。


因此,如果我考虑从周日开始到周六结束的几周,那么我可以获取任何日期@d的一周的最后一天,例如:SELECT DATEADD(wk,DATEDIFF(wk,'19041231',@d), '19041231')
鲍达德

21

对于这些需要获取的内容:

星期一= 1,星期日= 7:

SELECT 1 + ((5 + DATEPART(dw, GETDATE()) + @@DATEFIRST) % 7);

星期日= 1,星期六= 7:

SELECT 1 + ((6 + DATEPART(dw, GETDATE()) + @@DATEFIRST) % 7);

上面有一个类似的示例,但是由于加倍了“%7”,它的速度要慢得多。


这对于从一周开始获取星期几(星期日或星期一)也很有效。感谢
Fandango68

或者select (datediff(dd,5,cal.D_DATE)%7 + 1)select (datediff(dd,6,cal.D_DATE)%7 + 1)
vasja

8

对于那些在工作中需要答案并且创建功能被DBA禁止的人,以下解决方案将起作用:

select *,
cast(DATEADD(day, -1*(DATEPART(WEEKDAY, YouDate)-1), YourDate) as DATE) as WeekStart
From.....

这是该周的开始。在这里,我假设星期日是几周的开始。如果您认为星期一是开始,则应使用:

select *,
cast(DATEADD(day, -1*(DATEPART(WEEKDAY, YouDate)-2), YourDate) as DATE) as WeekStart
From.....

5

这对我来说很棒:

创建功能[dbo]。[StartOfWeek]
(
  @INPUTDATE DATETIME
)
返回日期时间

如
开始
  -此功能不起作用。
  -SET DATEFIRST 1-将星期一设置为一周的第一天。

  DECLARE @DOW INT-存储星期几
  设置@INPUTDATE = CONVERT(VARCHAR(10),@INPUTDATE,111)
  设置@DOW = DATEPART(DW,@INPUTDATE)

  -星期一至1的魔法转换,星期二至2的依此类推。
  -不理会SQL Server对一周开始的想法。
  -但是在这里我们将星期日标记为0,但是稍后我们会解决此问题。
  SET @DOW =(@DOW + @@ DATEFIRST-1)%7
  如果@DOW = 0 SET @DOW = 7-修复星期日

  RETURN DATEADD(DD,1-@ DOW,@ INPUTDATE)

结束

根据今天的日期,这似乎返回星期一,而不是星期日。OP已经有一个返回星期一的函数,他希望它返回星期日。:-)
亚伦·伯特兰

h!下次我应该仔细阅读问题。但是,如果仍然需要,我的解决方案可以轻松调整。好像OP无论如何都会对接受的答案感到满意-)
Trailmax 2011年

这是我机器上的正确解决方案,因为对我来说:DATEADD(ww,DATEDIFF(ww,0,CONVERT(DATE,'2017-10-8')),0)返回2017-10-9!
运行CMD

3

用谷歌搜索此脚本:

create function dbo.F_START_OF_WEEK
(
    @DATE           datetime,
    -- Sun = 1, Mon = 2, Tue = 3, Wed = 4
    -- Thu = 5, Fri = 6, Sat = 7
    -- Default to Sunday
    @WEEK_START_DAY     int = 1 
)
/*
Find the fisrt date on or before @DATE that matches 
day of week of @WEEK_START_DAY.
*/
returns     datetime
as
begin
declare  @START_OF_WEEK_DATE    datetime
declare  @FIRST_BOW     datetime

-- Check for valid day of week
if @WEEK_START_DAY between 1 and 7
    begin
    -- Find first day on or after 1753/1/1 (-53690)
    -- matching day of week of @WEEK_START_DAY
    -- 1753/1/1 is earliest possible SQL Server date.
    select @FIRST_BOW = convert(datetime,-53690+((@WEEK_START_DAY+5)%7))
    -- Verify beginning of week not before 1753/1/1
    if @DATE >= @FIRST_BOW
        begin
        select @START_OF_WEEK_DATE = 
        dateadd(dd,(datediff(dd,@FIRST_BOW,@DATE)/7)*7,@FIRST_BOW)
        end
    end

return @START_OF_WEEK_DATE

end
go

http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=47307


2

也许您需要这样:

SELECT DATEADD(DD, 1 - DATEPART(DW, GETDATE()), GETDATE())

要么

DECLARE @MYDATE DATETIME
SET @MYDATE = '2011-08-23'
SELECT DATEADD(DD, 1 - DATEPART(DW, @MYDATE), @MYDATE)

功能

CREATE FUNCTION [dbo].[GetFirstDayOfWeek]
( @pInputDate    DATETIME )
RETURNS DATETIME
BEGIN

SET @pInputDate = CONVERT(VARCHAR(10), @pInputDate, 111)
RETURN DATEADD(DD, 1 - DATEPART(DW, @pInputDate),
               @pInputDate)

END
GO

6
DATEPART(DW取决于@@datefirst
Martin Smith,

我喜欢这个简单。对于非常大的数据集,它似乎也运行得很好。
快速乔·史密斯,

2
为什么不只是让输入参数DATE,那么你就不必做任何次优转换到VARCHAR和回正好带,其在通过任何意外时组件。
阿龙贝特朗

之所以使用Convert函数,是因为返回的值不需要Time值。
甘达雷斯2011年

1
是的,但要点是转换为varchar并再次返回很昂贵。如果您只有DATE参数,则不必在乎是否包括时间...它会为您剥离。
亚伦·伯特兰

2
创建功能dbo.fnFirstWorkingDayOfTheWeek
(
    @currentDate日期
)
返回整数
如
开始
    -获取DATEFIRST设置
    宣告@ds int = @@ DATEFIRST 
    -在当前DATEFIRST设置下获取星期几
    宣告@dow int = DATEPART(dw,@ currentDate) 

    DECLARE @wd int = 1 +((((@ dow + @ ds)%7)+5)%7-总是返回Mon为1,Tue为2 ... Sun为7 

    RETURN DATEADD(dd,1- @ wd,@ currentDate) 

结束

这是在SQL Server 2005中对我有用的唯一功能。谢谢
Fandango68 2014年

@ Fernando68您能解释一下其他解决方案如何工作的吗?
亚伦·伯特兰

@AaronBertrand抱歉,我不记得了,但是我想我的重点是快速回答,我尝试了您的回答,但由于某种原因,它对我不起作用。
Fandango68年

@ Fernando68好吧,这非常有帮助。:-\
亚伦·伯特兰

2

基本时间(本周的星期日)

select cast(dateadd(day,-(datepart(dw,getdate())-1),getdate()) as date)

如果是前一周:

select cast(dateadd(day,-(datepart(dw,getdate())-1),getdate()) -7 as date)

在内部,我们构建了一个可以执行此操作的函数,但是如果您需要快速又肮脏的操作,则可以执行此操作。


0

由于朱利安日期0是星期一,因此只需在星期几(即-1 Eg的前一天)加上星期数即可。选择dateadd(wk,datediff(wk,0,getdate()),-1)


0
Set DateFirst 1;

Select 
    Datepart(wk, TimeByDay) [Week]
    ,Dateadd(d,
                CASE 
                WHEN  Datepart(dw, TimeByDay) = 1 then 0
                WHEN  Datepart(dw, TimeByDay) = 2 then -1
                WHEN  Datepart(dw, TimeByDay) = 3 then -2
                WHEN  Datepart(dw, TimeByDay) = 4 then -3
                WHEN  Datepart(dw, TimeByDay) = 5 then -4
                WHEN  Datepart(dw, TimeByDay) = 6 then -5
                WHEN  Datepart(dw, TimeByDay) = 7 then -6
                END
                , TimeByDay) as StartOfWeek

from TimeByDay_Tbl

这是我的逻辑。将一周的第一天设置为星期一,然后计算给定日期是星期几,然后使用DateAdd和Case I计算该周的前一个星期一的日期。


-1

我对这里给出的答案没有任何疑问,但是我确实认为我的实现和理解起来要简单得多。我尚未对其进行任何性能测试,但是应该可以忽略不计。

因此,我从日期以整数形式存储在SQL Server中这一事实中得出了我的答案(我只在谈论日期组件)。如果您不相信我,请尝试执行SELECT CONVERT(INT,GETDATE()),反之亦然。

知道了这一点,您就可以做一些很酷的数学方程式了。您也许可以提出一个更好的方案,但这是我的。

/*
TAKEN FROM http://msdn.microsoft.com/en-us/library/ms181598.aspx
First day of the week is
1 -- Monday
2 -- Tuesday
3 -- Wednesday
4 -- Thursday
5 -- Friday
6 -- Saturday
7 (default, U.S. English) -- Sunday
*/

--Offset is required to compensate for the fact that my @@DATEFIRST setting is 7, the default. 
DECLARE @offSet int, @testDate datetime
SELECT @offSet = 1, @testDate = GETDATE()

SELECT CONVERT(DATETIME, CONVERT(INT, @testDate) - (DATEPART(WEEKDAY, @testDate) - @offSet))

1
我发现这对我不起作用。我@@DATEFIRST也是7,但如果您@testDate是一周的开始,那么它将返回前一天的日期。
2012年

-1

我有一个类似的问题。给定日期,我想获取该周星期一的日期。

我使用以下逻辑:在0-6的范围内查找一周中的天数,然后从起始日期中减去该天数。

我使用了:DATEADD(day,-(DATEPART(weekday,)+ 5)%7,)

由于DATEPRRT(weekday,)返回1 = Sundaye ... 7 = Saturday,因此DATEPART(weekday,)+ 5)%7返回0 = Monday ... 6 = Sunday。

从原始日期减去该天数即得出前一个星期一。一周中的任何一天都可以使用相同的技术。


-1

我发现这个简单而有用。即使一周的第一天是周日或周一也可以。

宣告@BaseDate AS日期

SET @BaseDate = GETDATE()

声明@FisrtDOW AS日期

SELECT @FirstDOW = DATEADD(d,DATEPART(WEEKDAY,@ BaseDate)* -1 + 1,@BaseDate)


-3

也许我在这里简化过头了,也许是这种情况,但这似乎对我有用。尚未遇到任何问题...

CAST('1/1/' + CAST(YEAR(GETDATE()) AS VARCHAR(30)) AS DATETIME) + (DATEPART(wk, YOUR_DATE) * 7 - 7) as 'FirstDayOfWeek'
CAST('1/1/' + CAST(YEAR(GETDATE()) AS VARCHAR(30)) AS DATETIME) + (DATEPART(wk, YOUR_DATE) * 7) as 'LastDayOfWeek'

如果您尝试对进行不同的设置,则可以在此处获得不同的答案SET DATEFIRST
亚伦·伯特兰

5
好吧,我没有拒绝投票,但您的答案根本没有提到DATEFIRST(三年半以来),现在仍然没有。而且,即使在m和d相同的情况下,应避免使用像这样的区域格式m/d/y
亚伦·伯特兰
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.