รับวันแรกของสัปดาห์ใน SQL Server


98

ฉันพยายามจัดกลุ่มบันทึกตามสัปดาห์โดยจัดเก็บวันที่รวมเป็นวันแรกของสัปดาห์ อย่างไรก็ตามเทคนิคมาตรฐานที่ฉันใช้ในการปัดเศษวันที่ไม่ทำงานอย่างถูกต้องในสัปดาห์ (แม้ว่าจะใช้เป็นวันเดือนปีไตรมาสและกรอบเวลาอื่น ๆ ที่ฉันใช้กับ)

นี่คือ 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);

แต่การที่ฉันต้องทำข้อยกเว้นนั้นทำให้ฉันไม่สบายใจเล็กน้อย นอกจากนี้ขออภัยหากเป็นคำถามที่ซ้ำกัน ฉันพบคำถามที่เกี่ยวข้อง แต่ไม่มีคำถามที่กล่าวถึงประเด็นนี้โดยเฉพาะ


9
(@@DATEFIRST + DATEPART(DW, @SomeDate)) % 7ยังคงคงที่โดยไม่คำนึงถึง@@datefirstการตั้งค่าที่ฉันคิด กับวันจันทร์ = 2
Martin Smith

คำตอบ:


150

หากต้องการตอบว่าทำไมคุณถึงได้รับวันจันทร์ไม่ใช่วันอาทิตย์:

คุณกำลังเพิ่มจำนวนสัปดาห์ในวันที่ 0 วันที่ 0 คืออะไร? 1900-01-01 วันที่ 1900-01-01 คืออะไร? วันจันทร์. ในรหัสของคุณคุณกำลังบอกว่าวันจันทร์ที่ 1 มกราคม 1900 ผ่านไปกี่สัปดาห์แล้ว เรียกสิ่งนั้นว่า [n] ตกลงตอนนี้เพิ่ม [n] สัปดาห์เป็นวันจันทร์ที่ 1 มกราคม 1900 คุณไม่ควรแปลกใจที่สิ่งนี้กลายเป็นวันจันทร์ 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')
Baodad

22

สำหรับสิ่งที่ต้องได้รับ:

วันจันทร์ = 1 และวันอาทิตย์ = 7:

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

วันอาทิตย์ = 1 และวันเสาร์ = 7:

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

ด้านบนมีตัวอย่างที่คล้ายกัน แต่ต้องขอบคุณ "% 7" สองเท่ามันจะช้ากว่ามาก


1
วิธีนี้ได้ผลดีเช่นกันในการรับหมายเลขวันตั้งแต่ต้นสัปดาห์เป็นอาทิตย์หรือจันทร์ ขอบคุณ
Fandango68

อีกทางเลือกหนึ่งselect (datediff(dd,5,cal.D_DATE)%7 + 1)และselect (datediff(dd,6,cal.D_DATE)%7 + 1)
vasja

9

สำหรับผู้ที่ต้องการคำตอบในที่ทำงานและการสร้างฟังก์ชันถูกห้ามโดย 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
)
ผลตอบแทน DATETIME

เช่น
เริ่ม
  - สิ่งนี้ไม่ทำงานในฟังก์ชัน
  - SET DATEFIRST 1 - กำหนดให้วันจันทร์เป็นวันแรกของสัปดาห์

  DECLARE @DOW INT - เพื่อจัดเก็บวันในสัปดาห์
  SET @INPUTDATE = แปลง (VARCHAR (10), @INPUTDATE, 111)
  SET @DOW = DATEPART (DW, @INPUTDATE)

  - การแปลงเวทมนตร์ของวันจันทร์ถึง 1 วันอังคารถึง 2 เป็นต้น
  - ไม่คำนึงถึงสิ่งที่เซิร์ฟเวอร์ SQL คิดเกี่ยวกับการเริ่มต้นสัปดาห์
  - แต่ที่นี่เรากำหนดให้วันอาทิตย์เป็น 0 แต่เราจะแก้ไขในภายหลัง
  SET @DOW = (@DOW + @@ DATEFIRST - 1)% 7
  IF @DOW = 0 SET @DOW = 7 - แก้ไขสำหรับวันอาทิตย์

  กลับ DATEADD (DD, 1 - @ DOW, @ INPUTDATE)

สิ้นสุด

ดูเหมือนว่าจะกลับมาในวันจันทร์ตามวันที่ของวันนี้ไม่ใช่วันอาทิตย์ OP มีฟังก์ชันที่ส่งคืนวันจันทร์อยู่แล้วเขาต้องการให้กลับวันอาทิตย์ :-)
Aaron Bertrand

ทำ! ฉันควรอ่านคำถามอย่างรอบคอบในครั้งต่อไป อย่างไรก็ตามโซลูชันของฉันสามารถปรับเปลี่ยนได้อย่างง่ายดายหากยังจำเป็น ดูเหมือนว่า OP จะพอใจกับคำตอบที่ได้รับการยอมรับอยู่ดี -)
trailmax

นี่เป็นวิธีแก้ปัญหาที่ถูกต้องในเครื่องของฉันเนื่องจากสำหรับฉัน: DATEADD (ww, DATEDIFF (ww, 0, CONVERT (DATE, '2017-10-8')), 0) ส่งคืน 2017-10-9!
เรียกใช้ CMD

3

Googled สคริปต์นี้:

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

ฉันชอบความเรียบง่ายของอันนี้ ดูเหมือนว่าจะทำงานได้ดีสำหรับชุดข้อมูลขนาดใหญ่มากเช่นกัน
Quick Joe Smith

2
ทำไมไม่สร้างพารามิเตอร์อินพุตDATEแล้วคุณไม่ต้องทำการแปลงที่เหมาะสมย่อยใด ๆ ไปยังVARCHARและย้อนกลับเพียงเพื่อตัดองค์ประกอบเวลาโดยไม่ตั้งใจที่ส่งผ่านเข้ามา
Aaron Bertrand

ใช้ฟังก์ชัน Convert เนื่องจากค่าที่ส่งคืนไม่ต้องการTimeค่า
Gandarez

1
ใช่ แต่ประเด็นคือการแปลงเป็น varchar แล้วกลับมาอีกครั้งนั้นมีราคาแพง หากคุณมีพารามิเตอร์ DATE คุณก็ไม่สนใจว่าจะรวมเวลาไว้หรือไม่ ... มันจะถูกปล้นสำหรับคุณ
Aaron Bertrand

2
สร้างฟังก์ชัน dbo.fnFirstWorkingDayOfTheWeek
(
    @currentDate วันที่
)
INT ผลตอบแทน
เช่น
เริ่ม
    - รับการตั้งค่า DATEFIRST
    ประกาศ @ds int = @@ DATEFIRST 
    - รับหมายเลขวันในสัปดาห์ภายใต้การตั้งค่า DATEFIRST ปัจจุบัน
    ประกาศ @dow int = DATEPART (dw, @ currentDate) 

    DECLARE @wd int = 1 + (((@ dow + @ ds)% 7) +5)% 7 - ส่งคืนจันทร์เป็น 1 เสมออ. 2 ... อาเป็น 7 

    ส่งคืน DATEADD (dd, 1- @ wd, @ currentDate) 

สิ้นสุด

นี่เป็นฟังก์ชันเดียวที่ใช้ได้กับฉันใน SQL Server 2005 ขอบคุณ
Fandango68

@ Fernando68 คุณช่วยอธิบายได้ไหมว่าวิธีแก้ปัญหาอื่น ๆไม่ได้ผล?
Aaron Bertrand

@AaronBertrand ขอโทษจำไม่ได้ แต่ฉันคิดว่าฉันกำลังมุ่งเน้นไปที่คำตอบสั้น ๆ และฉันลองของคุณ แต่ด้วยเหตุผลบางอย่างมันไม่ได้ผลสำหรับฉัน
Fandango68

@ Fernando68 ดีที่มีประโยชน์มาก : - \
Aaron Bertrand

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 เช่น เลือก dateadd (wk, วันที่iff (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 เป็นจำนวนเต็ม (ฉันกำลังพูดถึงส่วนประกอบวันที่เท่านั้น) ถ้าคุณไม่เชื่อฉันลอง 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เป็นวันเริ่มต้นของสัปดาห์สิ่งนี้จะส่งคืนวันที่ซึ่งเป็นวันก่อนหน้า
row1

-1

ฉันมีปัญหาที่คล้ายกัน เมื่อระบุวันที่ฉันต้องการรับวันที่ของวันจันทร์ของสัปดาห์นั้น

ฉันใช้ตรรกะต่อไปนี้: ค้นหาหมายเลขวันในสัปดาห์ในช่วง 0-6 จากนั้นลบออกจากวันที่เริ่มต้น

ฉันใช้: DATEADD (วัน - (DATEPART (วันธรรมดา) + 5)% 7,)

ตั้งแต่ DATEPRRT (วันธรรมดา) ส่งกลับ 1 = วันอาทิตย์ ... 7 = วันเสาร์ DATEPART (วันธรรมดา) + 5)% 7 คืน 0 = วันจันทร์ ... 6 = วันอาทิตย์

การลบจำนวนวันนี้ออกจากวันที่เดิมจะทำให้วันจันทร์ก่อนหน้า สามารถใช้เทคนิคเดียวกันนี้ในวันเริ่มต้นของสัปดาห์


-1

ฉันพบว่าสิ่งนี้ง่ายและมีประโยชน์ ทำงานได้แม้ว่าวันแรกของสัปดาห์จะเป็นวันอาทิตย์หรือวันจันทร์

ประกาศ @BaseDate AS Date

SET @BaseDate = GETDATE ()

ประกาศ @FisrtDOW AS Date

เลือก @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คุณสามารถได้คำตอบที่แตกต่างกันที่นี่ถ้าคุณพยายามที่แตกต่างกันสำหรับการตั้งค่า
Aaron Bertrand

5
ฉันไม่ได้ลงคะแนน แต่คำตอบของคุณไม่ได้พูดถึงDATEFIRSTเลย (เป็นเวลาสามปีครึ่งแล้ว) และก็ยังไม่ได้ และคุณควรจะยังหลีกเลี่ยงรูปแบบในระดับภูมิภาคเช่นm/d/yแม้จะอยู่ในสถานการณ์ที่ม. และงเหมือนกัน
Aaron Bertrand
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.