Dapatkan hari pertama dalam seminggu di SQL Server

97

Saya mencoba mengelompokkan catatan berdasarkan minggu, menyimpan tanggal gabungan sebagai hari pertama dalam minggu itu. Namun, teknik standar yang saya gunakan untuk pembulatan tanggal tampaknya tidak bekerja dengan benar dengan minggu (meskipun untuk hari, bulan, tahun, kuartal dan jangka waktu lain yang telah saya terapkan).

Inilah SQL-nya:

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

Ini kembali 2011-08-22 00:00:00.000, yang merupakan hari Senin, bukan hari Minggu. Memilih @@datefirstkembali 7, yang merupakan kode untuk hari Minggu, sehingga server diatur dengan benar sejauh yang saya tahu.

Saya dapat melewati ini dengan cukup mudah dengan mengubah kode di atas menjadi:

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

Tetapi fakta bahwa saya harus membuat pengecualian seperti itu membuat saya sedikit tidak nyaman. Juga, mohon maaf jika ini adalah pertanyaan duplikat. Saya menemukan beberapa pertanyaan terkait tetapi tidak ada yang membahas aspek ini secara khusus.

Cepat Joe Smith
sumber
9
(@@DATEFIRST + DATEPART(DW, @SomeDate)) % 7tetap konstan terlepas dari @@datefirstpengaturan saya pikir. Dengan Senin = 2.
Martin Smith

Jawaban:

150

Untuk menjawab mengapa Anda mendapatkan hari Senin dan bukan hari Minggu:

Anda menambahkan beberapa minggu ke tanggal 0. Apakah tanggal 0 itu? 1900-01-01. Hari apa pada 01-01-2019? Senin. Jadi dalam kode Anda, Anda mengatakan, berapa minggu telah berlalu sejak Senin, 1 Januari 1900? Sebut saja itu [n]. Oke, sekarang tambahkan [n] minggu ke Senin, 1 Januari 1900. Anda tidak perlu heran bahwa ini akhirnya menjadi hari Senin. DATEADDtidak tahu ingin menambah minggu tetapi hanya sampai anda mendapatkan hari minggu, itu hanya menambahkan 7 hari, kemudian menambahkan 7 hari lagi, ... seperti DATEDIFFhanya mengenali batas-batas yang telah dilintasi. Misalnya, keduanya mengembalikan 1, meskipun beberapa orang mengeluh bahwa seharusnya ada beberapa logika yang masuk akal yang dibangun untuk membulatkan ke atas atau ke bawah:

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

Untuk menjawab bagaimana mendapatkan hari Minggu:

Jika Anda menginginkan hari Minggu, pilih tanggal dasar yang bukan hari Senin melainkan hari Minggu. Sebagai contoh:

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

Ini tidak akan rusak jika Anda mengubah DATEFIRSTsetelan Anda (atau kode Anda dijalankan untuk pengguna dengan setelan berbeda) - asalkan Anda masih menginginkan hari Minggu terlepas dari setelan saat ini. Jika Anda ingin dua jawaban jive, maka Anda harus menggunakan fungsi yang tidak bergantung pada DATEFIRSTpengaturan, misalnya

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

Jadi jika Anda mengubah DATEFIRSTpengaturan Anda menjadi Senin, Selasa, apa pun yang Anda, perilakunya akan berubah. Bergantung pada perilaku yang Anda inginkan, Anda dapat menggunakan salah satu fungsi berikut:

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

...atau...

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

Sekarang, Anda memiliki banyak alternatif, tetapi mana yang berkinerja terbaik? Saya akan terkejut jika akan ada perbedaan besar tetapi saya mengumpulkan semua jawaban yang diberikan sejauh ini dan menjalankannya melalui dua rangkaian tes - satu murah dan satu mahal. Saya mengukur statistik klien karena saya tidak melihat I / O atau memori berperan dalam kinerja di sini (meskipun itu mungkin ikut bermain tergantung pada bagaimana fungsi digunakan). Dalam pengujian saya, hasilnya adalah:

Kueri tugas "murah":

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

Kueri tugas "Mahal":

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

Saya dapat menyampaikan detail pengujian saya jika diinginkan - berhenti di sini karena ini sudah cukup bertele-tele. Saya sedikit terkejut melihat Curt keluar sebagai yang tercepat di kelas atas, mengingat jumlah perhitungan dan kode sebaris. Mungkin saya akan menjalankan beberapa tes dan blog yang lebih menyeluruh tentang itu ... jika kalian tidak keberatan saya menerbitkan fungsi Anda di tempat lain.

Aaron Bertrand
sumber
Jadi, jika saya menganggap minggu saya dimulai pada hari Minggu dan berakhir pada hari Sabtu, saya bisa mendapatkan hari terakhir dalam seminggu untuk tanggal berapa pun @d seperti ini: PILIH DATEADD (wk, DATEDIFF (wk, '19041231', @d), '19041231')
Baodad
21

Untuk ini yang perlu mendapatkan:

Senin = 1 dan Minggu = 7:

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

Minggu = 1 dan Sabtu = 7:

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

Di atas ada contoh serupa, tapi berkat menggandakan "% 7" itu akan jauh lebih lambat.

Kakkarot
sumber
Ini juga berfungsi dengan baik untuk mendapatkan nomor hari dari awal minggu menjadi Minggu atau Sen. Terima kasih
Fandango68
Atau select (datediff(dd,5,cal.D_DATE)%7 + 1)danselect (datediff(dd,6,cal.D_DATE)%7 + 1)
vasja
8

Bagi mereka yang membutuhkan jawaban di tempat kerja dan membuat fungsi dilarang oleh DBA Anda, solusi berikut ini akan berfungsi:

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

Ini menunjukkan awal minggu itu. Di sini saya berasumsi bahwa hari Minggu adalah awal minggu. Jika menurut Anda hari Senin adalah permulaan, Anda harus menggunakan:

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

Ini bekerja sangat baik untuk saya:

BUAT FUNGSI [dbo]. [StartOfWeek]
(
  @ INPUTATE DATETIME
)
KEMBALI DATETIME

SEBAGAI
MULAI
  - INI tidak berfungsi.
  - ATUR DATEFIRST 1 - atur senin menjadi hari pertama dalam minggu.

  MENYATAKAN @ DOW INT - untuk menyimpan hari dalam seminggu
  SET @ INPUTDATE = CONVERT (VARCHAR (10), @INPUTDATE, 111)
  SET @DOW = DATEPART (DW, @INPUTDATE)

  - Konversi sulap Senin ke 1, Selasa ke 2, dll.
  - Terlepas dari apa yang dipikirkan SQL server tentang awal minggu.
  - Tapi di sini kami memiliki hari Minggu yang ditandai sebagai 0, tapi kami memperbaikinya nanti.
  SET @DOW = (@DOW + @@ DATEFIRST - 1)% 7
  IF @DOW = 0 SET @DOW = 7 - fix untuk hari Minggu

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

AKHIR
trailmax
sumber
Tampaknya ini kembali hari Senin mengingat tanggal hari ini, bukan hari Minggu. OP sudah memiliki fungsi yang mengembalikan hari Senin, dia ingin mengembalikan hari Minggu. :-)
Aaron Bertrand
doh! Saya harus membaca pertanyaan dengan lebih hati-hati lain kali. Namun, solusi saya dapat dengan mudah disesuaikan, jika masih diperlukan. Sepertinya OP senang dengan jawaban yang diterima -)
trailmax
Ini adalah solusi yang tepat pada mesin saya, karena bagi saya: DATEADD (ww, DATEDIFF (ww, 0, CONVERT (DATE, '2017-10-8')), 0) mengembalikan 2017-10-9!
Jalankan CMD
3

Googled skrip ini:

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

Singkat
sumber
2

Mungkin Anda membutuhkan ini:

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

Atau

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

Fungsi

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
Gandarez
sumber
6
DATEPART(DWtergantung pada@@datefirst
Martin Smith
Saya suka kesederhanaan yang satu ini. Tampaknya berjalan cukup baik untuk kumpulan data yang sangat besar juga.
Quick Joe Smith
2
Mengapa tidak membuat parameter input saja, DATEmaka Anda tidak perlu melakukan konversi sub-optimal ke VARCHARdan kembali hanya untuk menghapus komponen waktu tidak disengaja yang lewat.
Aaron Bertrand
Fungsi Convert digunakan karena nilai yang dikembalikan tidak membutuhkan Timenilai.
Gandarez
1
Ya, tetapi intinya adalah mengonversi ke varchar dan kembali lagi itu mahal. Jika Anda hanya memiliki parameter DATE maka Anda tidak peduli apakah waktu telah disertakan ... itu akan dilucuti untuk Anda.
Aaron Bertrand
2
BUAT FUNGSI dbo.fnFirstWorkingDayOfTheWeek
(
    Tanggal @urrentDate
)
KEMBALI INT
SEBAGAI
MULAI
    - dapatkan pengaturan DATEFIRST
    MENYATAKAN @ds int = @@ DATEFIRST 
    - dapatkan nomor hari kerja di bawah pengaturan DATEFIRST saat ini
    MENYATAKAN @dow int = DATEPART (dw, @ currentDate) 

    MENYATAKAN @wd int = 1 + (((@ dow + @ ds)% 7) +5)% 7 - ini selalu mengembalikan Sen sebagai 1, Sel sebagai 2 ... Minggu sebagai 7 

    KEMBALI DATEADD (hh, 1- @ wd, @ currentDate) 

AKHIR
JG JIN
sumber
Ini adalah satu-satunya fungsi yang berfungsi untuk saya di SQL Server 2005. Terima kasih
Fandango68
@ Fernando68 Dapatkah Anda menjelaskan bagaimana solusi lain tidak berhasil?
Aaron Bertrand
@AaronBertrand maaf tidak ingat, tetapi saya pikir saya fokus pada jawaban cepat dan saya mencoba jawaban Anda tetapi untuk beberapa alasan itu tidak berhasil untuk saya.
Fandango68
@ Fernando68 Yah, itu sangat membantu. : - \
Aaron Bertrand
2

Untuk dasar (Minggu minggu ini)

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

Jika minggu sebelumnya:

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

Secara internal, kami membangun fungsi yang melakukannya tetapi jika Anda perlu cepat dan kotor, ini akan melakukannya.

JamesDavisSr
sumber
0

Karena tanggal Julian 0 adalah hari Senin tambahkan saja jumlah minggu ke hari Minggu yang merupakan hari sebelum -1 Misalnya. pilih dateadd (wk, tanggaliff (wk, 0, getdate ()), - 1)

Iggy
sumber
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

Ini logika saya. Tetapkan hari pertama minggu menjadi Senin lalu hitung hari apa dalam seminggu untuk hari pemberian, kemudian menggunakan DateAdd dan Kasus Saya menghitung tanggal berapa pada hari Senin sebelumnya minggu itu.

pengguna2479728
sumber
-1

Saya tidak memiliki masalah dengan jawaban yang diberikan di sini, namun menurut saya jawaban saya jauh lebih sederhana untuk diterapkan, dan dipahami. Saya belum menjalankan tes kinerja apa pun padanya, tetapi itu harus dapat diabaikan.

Jadi saya mendapatkan jawaban saya dari fakta bahwa tanggal disimpan di SQL server sebagai bilangan bulat, (saya berbicara tentang komponen tanggal saja). Jika Anda tidak percaya, coba SELECT CONVERT (INT, GETDATE ()) ini, dan sebaliknya.

Sekarang mengetahui hal ini, Anda dapat melakukan beberapa persamaan matematika yang keren. Anda mungkin bisa mendapatkan yang lebih baik, tapi ini milik saya.

/*
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))
Ryk
sumber
1
Saya menemukan bahwa ini tidak berhasil untuk saya. Saya @@DATEFIRSTjuga 7, tetapi jika Anda @testDateadalah awal minggu ini, maka ini mengembalikan tanggal pada hari sebelumnya.
baris1
-1

Saya punya masalah serupa. Diberikan tanggal, saya ingin mendapatkan tanggal Senin minggu itu.

Saya menggunakan logika berikut: Temukan nomor hari dalam minggu dalam kisaran 0-6, lalu kurangi dari tanggal awal.

Saya menggunakan: DATEADD (hari, - (DATEPART (hari kerja,) + 5)% 7,)

Karena DATEPRRT (hari kerja,) mengembalikan 1 = Minggu ... 7 = Sabtu, DATEPART (hari kerja,) + 5)% 7 mengembalikan 0 = Senin ... 6 = Minggu.

Pengurangan jumlah hari ini dari tanggal asli menghasilkan hari Senin sebelumnya. Teknik yang sama dapat digunakan untuk setiap hari awal dalam seminggu.

Pat Lane
sumber
-1

Saya menemukan ini sederhana dan berguna. Bekerja meskipun hari pertama dalam seminggu adalah Minggu atau Senin.

MENYATAKAN @BaseDate SEBAGAI Tanggal

SET @BaseDate = GETDATE ()

MENYATAKAN @FisrtDOW SEBAGAI Tanggal

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

ElJoel
sumber
-3

Mungkin saya terlalu menyederhanakan di sini, dan mungkin itu masalahnya, tetapi tampaknya ini berhasil untuk saya. Belum mengalami masalah dengan itu ...

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'
menandai
sumber
Anda bisa mendapatkan jawaban yang berbeda di sini jika Anda mencoba pengaturan yang berbeda untuk SET DATEFIRST.
Aaron Bertrand
5
Yah, saya tidak merendahkan suara, tapi jawaban Anda tidak menyebutkan DATEFIRSTsama sekali (selama tiga setengah tahun sekarang), dan tetap tidak. Dan Anda juga harus menghindari format regional seperti m/d/y, bahkan dalam skenario di mana m dan d sama.
Aaron Bertrand