Nhận ngày đầu tiên trong tuần trong SQL Server


96

Tôi đang cố gắng nhóm các bản ghi theo tuần, lưu trữ ngày tổng hợp là ngày đầu tiên trong tuần. Tuy nhiên, kỹ thuật tiêu chuẩn mà tôi sử dụng để làm tròn ngày dường như không hoạt động chính xác với các tuần (mặc dù kỹ thuật này áp dụng cho ngày, tháng, năm, quý và bất kỳ khung thời gian nào khác mà tôi đã áp dụng).

Đây là SQL:

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

Điều này trở lại 2011-08-22 00:00:00.000, đó là Thứ Hai, không phải Chủ Nhật. Chọn @@datefirsttrả hàng 7, đó là mã cho Chủ nhật, vì vậy máy chủ được thiết lập chính xác theo như tôi biết.

Tôi có thể bỏ qua điều này đủ dễ dàng bằng cách thay đổi mã trên thành:

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

Nhưng việc phải ngoại lệ như vậy khiến tôi có chút bất an. Ngoài ra, xin lỗi nếu đây là một câu hỏi trùng lặp. Tôi tìm thấy một số câu hỏi liên quan nhưng không có câu hỏi nào giải quyết cụ thể khía cạnh này.


9
(@@DATEFIRST + DATEPART(DW, @SomeDate)) % 7vẫn không đổi bất kể @@datefirstcài đặt tôi nghĩ. Với Thứ Hai = 2.
Martin Smith

Câu trả lời:


148

Để trả lời tại sao bạn lại nhận được Thứ Hai mà không phải là Chủ Nhật:

Bạn đang thêm một số tuần vào ngày 0. Ngày 0 là gì? 1900-01-01. Ngày 1900-01-01 là ngày gì? Thứ Hai. Vì vậy, trong mã của bạn, bạn đang nói, bao nhiêu tuần đã trôi qua kể từ Thứ Hai, ngày 1 tháng 1 năm 1900? Hãy gọi đó là [n]. Được rồi, bây giờ hãy thêm [n] tuần vào Thứ Hai, ngày 1 tháng 1 năm 1900. Bạn không nên ngạc nhiên rằng điều này kết thúc là Thứ Hai. DATEADDKhông có ý tưởng rằng bạn muốn thêm tuần nhưng chỉ cho đến khi bạn đến Chủ nhật, nó chỉ là thêm 7 ngày, rồi thêm 7 ngày nữa, ... giống như DATEDIFFchỉ nhận ra những ranh giới đã bị vượt qua. Ví dụ, cả hai đều trả về 1, mặc dù một số người phàn nàn rằng cần có một số logic hợp lý được tích hợp để làm tròn lên hoặc xuống:

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

Để trả lời làm thế nào để có được một ngày Chủ nhật:

Nếu bạn muốn có một ngày Chủ nhật, thì hãy chọn một ngày cơ bản không phải là Thứ Hai mà là Chủ nhật. Ví dụ:

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

Điều này sẽ không bị phá vỡ nếu bạn thay đổi DATEFIRSTcài đặt của mình (hoặc mã của bạn đang chạy cho người dùng có cài đặt khác) - với điều kiện bạn vẫn muốn có một ngày Chủ nhật bất kể cài đặt hiện tại. Nếu bạn muốn những hai câu trả lời cho jive, sau đó bạn nên sử dụng một chức năng mà không phụ thuộc vào các DATEFIRSTthiết lập, ví dụ như

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

Vì vậy, nếu bạn thay đổi DATEFIRSTcài đặt của mình thành Thứ Hai, Thứ Ba, những gì bạn có, hành vi sẽ thay đổi. Tùy thuộc vào hành vi bạn muốn, bạn có thể sử dụng một trong các chức năng sau:

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

...hoặc là...

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

Bây giờ, bạn có rất nhiều lựa chọn thay thế, nhưng lựa chọn nào hoạt động tốt nhất? Tôi sẽ ngạc nhiên nếu có bất kỳ sự khác biệt lớn nào nhưng tôi đã thu thập tất cả các câu trả lời được cung cấp cho đến nay và chạy chúng qua hai bộ bài kiểm tra - một rẻ và một đắt. Tôi đã đo lường số liệu thống kê của khách hàng vì tôi không thấy I / O hoặc bộ nhớ đóng một phần trong hiệu suất ở đây (mặc dù chúng có thể phát huy tác dụng tùy thuộc vào cách sử dụng chức năng). Trong các thử nghiệm của tôi, kết quả là:

Truy vấn bài tập "rẻ":

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

Truy vấn chuyển nhượng "Đắt":

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

Tôi có thể chuyển tiếp chi tiết các bài kiểm tra của mình nếu muốn - dừng ở đây vì điều này đã trở nên khá dài dòng. Tôi hơi ngạc nhiên khi thấy Curt's xuất hiện nhanh nhất ở phân khúc cao cấp, dựa trên số lượng phép tính và mã nội tuyến. Có lẽ tôi sẽ chạy một số bài kiểm tra kỹ lưỡng hơn và viết blog về nó ... nếu các bạn không phản đối việc tôi xuất bản các chức năng của bạn ở nơi khác.


Vì vậy, nếu tôi coi các tuần của mình bắt đầu vào Chủ nhật và kết thúc vào Thứ Bảy, tôi có thể lấy ngày cuối cùng trong tuần cho bất kỳ ngày nào @d như sau: CHỌN DATEADD (tuần, DATEDIFF (tuần, '19041231', @d), '19041231')
Baodad

21

Đối với những thứ cần nhận được:

Thứ Hai = 1 và Chủ Nhật = 7:

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

Chủ nhật = 1 và thứ bảy = 7:

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

Ở trên có một ví dụ tương tự, nhưng nhờ có "% 7" gấp đôi, nó sẽ chậm hơn nhiều.


Điều này cũng hoạt động tuyệt vời để có được số ngày đầu tuần là Chủ nhật hoặc Thứ hai. Cảm ơn
Fandango68.

Ngoài ra select (datediff(dd,5,cal.D_DATE)%7 + 1)select (datediff(dd,6,cal.D_DATE)%7 + 1)
vasja

8

Đối với những người cần câu trả lời tại nơi làm việc và chức năng tạo bị cấm bởi DBA của bạn, giải pháp sau sẽ hoạt động:

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

Điều này cho thấy bắt đầu của tuần đó. Ở đây tôi giả định rằng Chủ nhật là ngày bắt đầu của các tuần. Nếu bạn nghĩ rằng Thứ Hai là ngày bắt đầu, bạn nên sử dụng:

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

5

Điều này hoạt động tuyệt vời đối với tôi:

TẠO CHỨC NĂNG [dbo]. [StartOfWeek]
(
  @INPUTDATE DATETIME
)
QUAY LẠI NGÀY

NHƯ
BẮT ĐẦU
  - NÀY không hoạt động trong chức năng.
  - SET DATEFIRST 1 - đặt thứ hai là ngày đầu tiên trong tuần.

  DECLARE @DOW INT - để lưu trữ ngày trong tuần
  SET @INPUTDATE = CHUYỂN ĐỔI (VARCHAR (10), @INPUTDATE, 111)
  SET @DOW = DATEPART (DW, @INPUTDATE)

  - Chuyển đổi ma thuật từ thứ hai thành 1, thứ ba thành 2, v.v.
  - không tôn trọng những gì máy chủ SQL nghĩ về đầu tuần.
  - Nhưng ở đây chúng tôi có chủ nhật được đánh dấu là 0, nhưng chúng tôi sẽ sửa lỗi này sau.
  ĐẶT @DOW = (@DOW + @@ DATEFIRST - 1)% 7
  IF @DOW = 0 SET @DOW = 7 - sửa cho chủ nhật

  QUAY LẠI DATEADD (DD, 1 - @ DOW, @ INPUTDATE)

KẾT THÚC

Điều này dường như trở lại thứ Hai cho ngày hôm nay, không phải Chủ nhật. OP đã có một hàm trả về thứ Hai, anh ta muốn nó trả về Chủ nhật. :-)
Aaron Bertrand

làm! Tôi nên đọc câu hỏi cẩn thận hơn vào lần sau. Tuy nhiên, giải pháp của tôi có thể được điều chỉnh dễ dàng, nếu vẫn được yêu cầu. Có vẻ như OP là hài lòng với câu trả lời được chấp nhận anyway -)
trailmax

Đây là giải pháp chính xác trên máy của tôi, vì đối với tôi: DATEADD (ww, DATEDIFF (ww, 0, CONVERT (DATE, '2017-10-8')), 0) trả về 2017-10-9!
Chạy CMD

3

Googled tập lệnh này:

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

Có thể bạn cần cái này:

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

Hoặc là

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

Chức năng

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(DWphụ thuộc vào@@datefirst
Martin Smith

Tôi thích sự đơn giản của cái này. Nó dường như cũng chạy khá tốt đối với các bộ dữ liệu rất lớn.
Quick Joe Smith

2
Tại sao không chỉ làm cho các tham số đầu vào DATEsau đó bạn không cần phải làm bất kỳ chuyển đổi phụ tối ưu để VARCHARvà trở lại chỉ để tước bất kỳ thành phần thời gian ngẫu nhiên mà được thông qua trong.
Aaron Bertrand

Hàm Convert được sử dụng vì giá trị trả về không cần Timegiá trị.
Gandarez

1
Có nhưng vấn đề là chuyển đổi sang varchar và quay lại một lần nữa rất tốn kém. Nếu bạn chỉ có một tham số DATE thì bạn không quan tâm liệu thời gian có được bao gồm hay không ... nó sẽ bị tước bỏ cho bạn.
Aaron Bertrand

2
TẠO CHỨC NĂNG dbo.fnFirstWorkingDayOfTheWeek
(
    @currentDate ngày
)
QUAY LẠI VÀO
NHƯ
BẮT ĐẦU
    - nhận cài đặt DATEFIRST
    DECLARE @ds int = @@ DATEFIRST 
    - nhận số ngày trong tuần trong cài đặt DATEFIRST hiện tại
    DECLARE @dow int = DATEPART (dw, @ currentDate) 

    DECLARE @wd int = 1 + (((@ dow + @ ds)% 7) +5)% 7 - điều này luôn trả về Thứ 2 là 1, Thứ 3 là 2 ... Chủ nhật là 7 

    QUAY LẠI NGÀYADD (dd, 1- @ wd, @ currentDate) 

KẾT THÚC

Đây là chức năng duy nhất mà làm việc cho tôi trong SQL Server 2005. Cảm ơn bạn
Fandango68

@ Fernando68 Bạn có thể giải thích cách các giải pháp khác không hoạt động không?
Aaron Bertrand

@AaronBertrand xin lỗi không nhớ lại, nhưng tôi nghĩ rằng tôi đang tập trung vào một câu trả lời nhanh và tôi đã thử câu trả lời của bạn nhưng vì một số lý do mà nó không hoạt động với tôi.
Fandango68

@ Fernando68 Chà, điều đó rất hữu ích. : - \
Aaron Bertrand

2

Đối với cơ bản (Chủ nhật của tuần hiện tại)

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

Nếu tuần trước:

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

Trong nội bộ, chúng tôi đã xây dựng một chức năng thực hiện điều đó nhưng nếu bạn cần nhanh chóng và tiện lợi, chức năng này sẽ làm được.


0

Vì ngày 0 của Julian là ngày thứ Hai, chỉ cần thêm số tuần vào Chủ nhật là ngày trước -1 Ví dụ: select dateadd (wk, dateiff (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

Đây là logic của tôi. Đặt ngày đầu tiên của tuần là thứ Hai, sau đó tính toán ngày trong tuần một ngày cụ thể là bao nhiêu, sau đó sử dụng DateAdd và Case, tôi tính ngày sẽ là ngày thứ Hai trước đó của tuần đó.


-1

Tôi không có bất kỳ vấn đề nào với bất kỳ câu trả lời nào được đưa ra ở đây, tuy nhiên tôi nghĩ rằng tôi thực hiện đơn giản hơn rất nhiều và hiểu được. Tôi chưa chạy bất kỳ bài kiểm tra hiệu suất nào trên nó, nhưng nó sẽ không thể bỏ qua.

Vì vậy, tôi đã rút ra câu trả lời của mình từ thực tế là ngày tháng được lưu trữ trong máy chủ SQL dưới dạng số nguyên, (tôi chỉ nói về thành phần ngày tháng). Nếu bạn không tin tôi, hãy thử CHỌN CHUYỂN ĐỔI (INT, GETDATE ()) và ngược lại.

Bây giờ biết điều này, bạn có thể làm một số phương trình toán học thú vị. Bạn có thể nghĩ ra một cái tốt hơn, nhưng đây là của tôi.

/*
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
Tôi thấy rằng điều này không hiệu quả với tôi. Của tôi @@DATEFIRSTcũng là 7, nhưng nếu của bạn @testDatelà đầu tuần, thì điều này trả về một ngày là ngày trước đó.
row1

-1

Tôi đã có một vấn đề tương tự. Đã có một ngày, tôi muốn lấy ngày thứ Hai của tuần đó.

Tôi đã sử dụng logic sau: Tìm số ngày trong tuần trong phạm vi 0-6, sau đó trừ số đó cho ngày gốc.

Tôi đã sử dụng: DATEADD (ngày, - (DATEPART (ngày trong tuần,) + 5)% 7,)

Vì DATEPRRT (ngày trong tuần,) trả về 1 = Chủ Nhật ... 7 = Thứ Bảy, DATEPART (ngày trong tuần,) + 5)% 7 trả về 0 = Thứ Hai ... 6 = Chủ Nhật.

Trừ số ngày này so với ngày ban đầu sẽ có Thứ Hai trước đó. Kỹ thuật tương tự có thể được sử dụng cho bất kỳ ngày bắt đầu nào trong tuần.


-1

Tôi thấy điều này đơn giản và hữu ích. Hoạt động ngay cả khi ngày đầu tiên của tuần là Chủ Nhật hoặc Thứ Hai.

DECLARE @BaseDate AS Ngày

SET @BaseDate = GETDATE ()

DECLARE @FisrtDOW AS Date

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


-3

Có lẽ tôi đang đơn giản hóa quá mức ở đây, và đó có thể là trường hợp, nhưng điều này có vẻ hiệu quả với tôi. Chưa gặp bất kỳ vấn đề nào với nó ...

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'

Bạn có thể nhận được các câu trả lời khác nhau ở đây nếu bạn thử các cài đặt khác nhau cho SET DATEFIRST.
Aaron Bertrand,

5
Chà, tôi đã không bỏ phiếu, nhưng câu trả lời của bạn hoàn toàn không đề cập đến DATEFIRST(trong ba năm rưỡi nay), và vẫn chưa. Và bạn cũng nên tránh các định dạng khu vực như m/d/y, ngay cả trong các tình huống mà m và d giống nhau.
Aaron Bertrand
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.