获取SQL Server中的第一天

时间:2011-08-23 23:50:25

标签: sql-server tsql sql-server-2008 date

我试图按周分组记录,将汇总日期存储为一周的第一天。但是,我用来舍入日期的标准技术似乎在几周内没有正常工作(尽管它确实存在了几天,几个月,几年,几个季度以及我应用它的任何其他时间框架。)

这是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);

但是我必须做出这样的例外让我有点不安。此外,如果这是一个重复的问题,请道歉。我发现了一些相关问题,但没有一个专门解决这个问题。

14 个答案:

答案 0 :(得分:131)

回答为什么你会在星期一而不是星期天:

您在日期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设置(或者您的代码针对具有不同设置的用户运行),则不会中断 - 前提是您仍然需要星期日,而不管当前设置如何。如果您希望这两个答案都是jive,那么您应该使用 依赖于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在​​高端出现最快,我感到有些惊讶。也许我会进行一些更全面的测试和博客...如果你们对我在其他地方发布你的功能没有任何异议。

答案 1 :(得分:14)

对于需要获得的这些:

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

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

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

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

上面有一个类似的例子,但是由于双重“%7”,它会慢得多。

答案 2 :(得分:4)

这对我来说非常有用:

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

答案 3 :(得分:3)

用Google搜索此脚本:

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

答案 4 :(得分:3)

对于那些在工作中需要答案并且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 :(得分: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 :(得分:2)

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

答案 7 :(得分:0)

由于朱利安日期0是星期一,所以只需将周数添加到星期日 这是前一天-1例如。选择dateadd(wk,datediff(wk,0,getdate()), - 1)

答案 8 :(得分: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计算该星期一的星期一的日期。

答案 9 :(得分:0)

基本(当前周的星期日)

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

如果是上周:

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

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

答案 10 :(得分:-1)

我对这里给出的任何答案都没有任何问题,但是我认为我的实施和理解要简单得多。我没有对它进行任何性能测试,但它应该是可以忽略的。

所以我从日期作为整数存储在SQL服务器中的事实得出了答案(我只讨论日期组件)。如果您不相信我,请尝试使用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))

答案 11 :(得分:-1)

我有类似的问题。鉴于约会,我想得到那个星期一的星期日。

我使用了以下逻辑:在0-6范围内找到一周中的日期编号,然后从原始日期中减去该数字。

我用过:DATEADD(白天, - (DATEPART(工作日)+ 5)%7,)

由于DATEPRRT(工作日)返回1 =星期日... 7 =星期六, DATEPART(工作日,)+ 5)%7返回0 =星期一... 6 =星期日。

从原始日期减去这个天数给出了上一个星期一。同一技术可用于一周的任何开始日。

答案 12 :(得分:-1)

我发现这很简单有用。即使一周的第一天是星期日或星期一,也能正常工作。

宣布@BaseDate AS日期

SET @BaseDate = GETDATE()

DECLARE @FisrtDOW AS Date

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

答案 13 :(得分:-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'