在 SQLServer 中获取每周的第一天

我试图按周对记录进行分组,将聚合日期存储为每周的第一天。然而,我用来四舍五入日期的标准技术似乎并不适用于几周(尽管它适用于几天、几个月、几年、几个季度和我所应用的任何其他时间框架)。

以下是 SQL 语句:

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

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

我可以很容易地绕过这一点,通过改变以上代码:

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

但我不得不破例,这让我有点不安。此外,如果这是一个重复的问题,我道歉。我发现了一些相关的问题,但是没有一个是针对这个方面的。

253146 次浏览

Googled this script:

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

Maybe you need this:

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

Or

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

Function

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

This works wonderfully for me:

CREATE FUNCTION [dbo].[StartOfWeek]
(
@INPUTDATE DATETIME
)
RETURNS DATETIME


AS
BEGIN
-- THIS does not work in function.
-- SET DATEFIRST 1 -- set monday to be the first day of week.


DECLARE @DOW INT -- to store day of week
SET @INPUTDATE = CONVERT(VARCHAR(10), @INPUTDATE, 111)
SET @DOW = DATEPART(DW, @INPUTDATE)


-- Magic convertion of monday to 1, tuesday to 2, etc.
-- irrespect what SQL server thinks about start of the week.
-- But here we have sunday marked as 0, but we fix this later.
SET @DOW = (@DOW + @@DATEFIRST - 1) %7
IF @DOW = 0 SET @DOW = 7 -- fix for sunday


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


END

To answer why you're getting a Monday and not a Sunday:

You're adding a number of weeks to the date 0. What is date 0? 1900-01-01. What was the day on 1900-01-01? Monday. So in your code you're saying, how many weeks have passed since Monday, January 1, 1900? Let's call that [n]. Ok, now add [n] weeks to Monday, January 1, 1900. You should not be surprised that this ends up being a Monday. DATEADD has no idea that you want to add weeks but only until you get to a Sunday, it's just adding 7 days, then adding 7 more days, ... just like DATEDIFF only recognizes boundaries that have been crossed. For example, these both return 1, even though some folks complain that there should be some sensible logic built in to round up or down:

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

To answer how to get a Sunday:

If you want a Sunday, then pick a base date that's not a Monday but rather a Sunday. For example:

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

This will not break if you change your DATEFIRST setting (or your code is running for a user with a different setting) - provided that you still want a Sunday regardless of the current setting. If you want those two answers to jive, then you should use a function that does depend on the DATEFIRST setting, e.g.

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

So if you change your DATEFIRST setting to Monday, Tuesday, what have you, the behavior will change. Depending on which behavior you want, you could use one of these functions:

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

...or...

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

Now, you have plenty of alternatives, but which one performs best? I'd be surprised if there would be any major differences but I collected all the answers provided so far and ran them through two sets of tests - one cheap and one expensive. I measured client statistics because I don't see I/O or memory playing a part in the performance here (though those may come into play depending on how the function is used). In my tests the results are:

"Cheap" assignment query:

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

"Expensive" assignment query:

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

I can relay the details of my tests if desired - stopping here as this is already getting quite long-winded. I was a bit surprised to see Curt's come out as the fastest at the high end, given the number of calculations and inline code. Maybe I'll run some more thorough tests and blog about it... if you guys don't have any objections to me publishing your functions elsewhere.

I don't have any issues with any of the answers given here, however I do think mine is a lot simpler to implement, and understand. I have not run any performance tests on it, but it should be neglegable.

So I derived my answer from the fact that dates are stored in SQL server as integers, (I am talking about the date component only). If you don't believe me, try this SELECT CONVERT(INT, GETDATE()), and vice versa.

Now knowing this, you can do some cool math equations. You might be able to come up with a better one, but here is mine.

/*
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))

Maybe I'm over simplifying here, and that may be the case, but this seems to work for me. Haven't ran into any problems with it yet...

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'

I had a similar problem. Given a date, I wanted to get the date of the Monday of that week.

I used the following logic: Find the day number in the week in the range of 0-6, then subtract that from the originay date.

I used: DATEADD(day,-(DATEPART(weekday,)+5)%7,)

Since DATEPRRT(weekday,) returns 1 = Sundaye ... 7=Saturday, DATEPART(weekday,)+5)%7 returns 0=Monday ... 6=Sunday.

Subtracting this number of days from the original date gives the previous Monday. The same technique could be used for any starting day of the week.

CREATE FUNCTION dbo.fnFirstWorkingDayOfTheWeek
(
@currentDate date
)
RETURNS INT
AS
BEGIN
-- get DATEFIRST setting
DECLARE @ds int = @@DATEFIRST
-- get week day number under current DATEFIRST setting
DECLARE @dow int = DATEPART(dw,@currentDate)


DECLARE @wd  int =  1+(((@dow+@ds) % 7)+5) % 7  -- this is always return Mon as 1,Tue as 2 ... Sun as 7


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


END

For these that need to get:

Monday = 1 and Sunday = 7:

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

Sunday = 1 and Saturday = 7:

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

Above there was a similar example, but thanks to double "%7" it would be much slower.

Since Julian date 0 is a Monday just add the number of weeks to Sunday which is the day before -1 Eg. select dateadd(wk,datediff(wk,0,getdate()),-1)

I found this simple and usefull. Works even if first day of week is Sunday or Monday.

DECLARE @BaseDate AS Date

SET @BaseDate = GETDATE()

DECLARE @FisrtDOW AS Date

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

For those who need the answer at work and creating function is forbidden by your DBA, the following solution will work:

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

This gives the start of that week. Here I assume that Sundays are the start of weeks. If you think that Monday is the start, you should use:

select *,
cast(DATEADD(day, -1*(DATEPART(WEEKDAY, YouDate)-2), YourDate) as DATE) as WeekStart
From.....
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

This is my logic. Set the first of the week to be Monday then calculate what is the day of the week a give day is, then using DateAdd and Case I calculate what the date would have been on the previous Monday of that week.

For the basic (the current week's Sunday)

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

If previous week:

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

Internally, we built a function that does it but if you need quick and dirty, this will do it.

I found some of the other answers long-winded or didn't actually work if you wanted Monday as the start of the week.

Sunday

SELECT DATEADD(week, DATEDIFF(week, -1, GETDATE()), -1) AS Sunday;

Monday

SELECT DATEADD(week, DATEDIFF(week, 0, GETDATE() - 1), 0) AS Monday;

This is a useful function for me

/* MeRrais 211126
select [dbo].[SinceWeeks](0,NULL)
select [dbo].[SinceWeeks](5,'2021-08-31')
*/
alter Function [dbo].[SinceWeeks](@Weeks int, @From datetime=NULL)
Returns date
AS
Begin


if @From is null
set @From=getdate()


return cast(dateadd(day, -(@Weeks*7+datepart(dw,@From)-1), @From) as date)
END