2011-08-23 87 views
74

Tôi đang cố gắng nhóm các bản ghi theo tuần, lưu trữ ngày được tổng hợp làm ngày đầu tiên trong tuần. Tuy nhiên, kỹ thuật tiêu chuẩn tôi sử dụng để làm tròn ngày không có vẻ hoạt động chính xác với các tuần (mặc dù nó 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).Nhận ngày đầu tiên trong tuần trong SQL Server

Đây là SQL:

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

này trả 2011-08-22 00:00:00.000, mà là một thứ hai, không phải là một chủ nhật. Chọn @@datefirst trả về 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 một cách dễ dàng đủ bằng cách thay đổi mã ở trên để:

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

Nhưng thực tế là tôi phải làm như vậy một ngoại lệ làm cho tôi một chút không thoải mái. Ngoài ra, xin lỗi nếu đây là 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 vấn đề này một cách cụ thể.

+8

'(@@ DATEFIRST + DATEPART (DW, @SomeDate))% 7' vẫn không thay đổi bất kể cài đặt' @@ datefirst' mà tôi nghĩ. Với Monday = 2. –

Trả lời

116

Để trả lời lý do tại sao bạn đang nhận được một thứ hai và không phải là một Chủ nhật:

Bạn đang thêm một số tuần tới ngày 0. Ngày 0 là gì? 1900-01-01. Ngày nào là ngày 1900-01-01? 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]. Ok, bây giờ thêm [n] tuần đến 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à một thứ hai. DATEADD không có ý tưởng rằng bạn muốn thêm tuần nhưng chỉ cho đến khi bạn nhận được một chủ nhật, nó chỉ thêm 7 ngày, sau đó thêm 7 ngày nữa, ... giống như DATEDIFF chỉ nhận ra ranh giới đã được vượt qua. Ví dụ, những cả hai trở lại 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 xây dựng trong để 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 như thế nào để có được một Chủ nhật:

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

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

này sẽ không phá vỡ nếu bạn thay đổi DATEFIRST thiết lập của bạn (hoặc mã của bạn đang chạy cho một người dùng với một môi trường khác) - với điều kiện là bạn vẫn muốn có một chủ nhật không phụ thuộc vào thiết lập hiện tại.Nếu bạn muốn hai câu trả lời đó cho jive, thì bạn nên sử dụng hàm không phụ thuộc vào cài đặt DATEFIRST, ví dụ:

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

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

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 ...

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 mà 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 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 thông qua hai bộ kiểm tra - một trong những giá rẻ và đắt tiền. Tôi đo số liệu thống kê khách hàng bởi 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ù những người có thể đi vào chơi tùy thuộc vào cách chức năng được sử dụng). Trong các thử nghiệm của tôi kết quả là:

"giá rẻ" truy vấn phân công:

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 

"Đắt" truy vấn phân công:

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 các chi tiết của các bài kiểm tra của tôi nếu muốn - dừng lại ở đây vì điều này đã nhận được khá dài. Tôi hơi ngạc nhiên khi thấy Curt xuất hiện nhanh nhất ở cấp cao, với số lượng tính toán 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à blog về nó ... nếu các bạn không có bất kỳ phản đối nào để tôi xuất bản các chức năng của bạn ở nơi khác.

+0

Vì vậy, nếu tôi xem xét các tuần bắt đầu vào Chủ Nhật và kết thúc vào Thứ Bảy, tôi có thể nhận được ngày _last_ trong tuần cho bất kỳ ngày nào @d như thế này: SELECT DATEADD (wk, DATEDIFF (wk, '19041231', @d) , '19041231') – Baodad

3

google kịch bản 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ó lẽ bạn cần điều này:

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

Hoặc

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 
+5

'DATEPART (DW' phụ thuộc vào' @@ datefirst' –

+0

@Martin Smith có đầu óc! – Gandarez

+0

Tôi thích sự đơn giản của điều này. Dường như nó chạy khá tốt cho các tập dữ liệu rất lớn. –

4

này hoạt động tuyệt vời cho tôi:

 
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 
+0

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

+0

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

+0

Đây là giải pháp đúng 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! –

-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 bản thân của tôi đơn giản hơn nhiều để triển khai và hiểu. Tôi đã không chạy bất kỳ thử nghiệm hiệu suất trên nó, nhưng nó nên được neglegable.

Vì vậy, tôi bắt nguồn câu trả lời của tôi từ thực tế là ngày được lưu trữ trong SQL máy chủ như số nguyên, (Tôi đang nói về các thành phần ngày chỉ). Nếu bạn không tin tôi, hãy thử SELECT CONVERT này (INT, GETDATE()) và ngược lại.

Bây giờ khi biết điều này, bạn có thể thực hiện một số phương trình toán học thú vị. Bạn có thể tìm 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ả đối với tôi. '@@ DATEFIRST' của tôi cũng là 7, nhưng nếu' @ testDate' của bạn là ngày bắt đầu của tuần, thì điều này trả về một ngày là ngày hôm trước. – row1

-3

Có thể tôi đang đơn giản hóa ở đây và điều này có thể đúng, nhưng điều này có vẻ phù hợp với tôi. Chưa gặp bất kỳ sự cố 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' 
+0

Bạn có thể nhận các câu trả lời khác nhau tại đây nếu bạn thử các cài đặt khác nhau cho 'SET DATEFIRST'. –

+0

Chỉ cần SET DATEFIRST 1, đã giải quyết được sự cố. – mark

+5

Vâng, tôi đã không bỏ phiếu, nhưng câu trả lời của bạn đã không đề cập đến 'DATEFIRST' ở tất cả (trong ba năm rưỡi bây giờ), và vẫn không. Và bạn cũng nên * cũng * tránh các định dạng vùng như 'm/d/y', ngay cả trong các trường hợp mà m và d là như nhau. –

-1

Tôi cũng gặp vấn đề tương tự. Cho một ngày, tôi muốn có được ngày thứ Hai của tuần đó.

Tôi đã sử dụng logic sau: Tìm số ngày trong tuần trong khoảng 0-6, sau đó trừ số đó khỏi ngày bắt đầu.

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

Kể từ DATEPRRT (ngày trong tuần,) trả về 1 = Sundaye ... 7 = Thứ bảy, DATEPART (các ngày trong tuần ,) + 5)% 7 trả về 0 = Thứ hai ... 6 = Chủ nhật.

Trừ số ngày này khỏi ngày ban đầu cho 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.

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+(((@[email protected]) % 7)+5) % 7 -- this is always return Mon as 1,Tue as 2 ... Sun as 7 

    RETURN DATEADD(dd,[email protected],@currentDate) 

END 
+0

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

+0

@ 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? –

+0

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

10

Đối với những điều đó cần phải 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 đó là một ví dụ tương tự, nhưng nhờ tăng gấp đôi "% 7", nó sẽ chậm hơn nhiều.

+0

Công trình này cũng tuyệt vời để có được số ngày từ đầu tuần là Mặt trời hoặc Thứ Hai. Cảm ơn – Fandango68

+0

Cách khác, chọn (ngàyiff (dd, 5, cal.D_DATE)% 7 + 1) 'và' select (dateiff (dd, 6, cal.D_DATE)% 7 + 1) ' – vasja

0

Kể từ ngày Julian 0 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ụ. chọn dateadd (wk, dateiff (wk, 0, getdate()), - 1)

-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 ngày

CHỌN @FirstDOW = DATEADD (d, DATEPART (WEEKDAY, @ BaseDate) * - 1 + 1, @BaseDate)

Các vấn đề liên quan