Ottieni il primo giorno della settimana in SQL Server


97

Sto cercando di raggruppare i record per settimana, memorizzando la data aggregata come primo giorno della settimana. Tuttavia, la tecnica standard che utilizzo per arrotondare le date non sembra funzionare correttamente con le settimane (anche se funziona per giorni, mesi, anni, trimestri e qualsiasi altro periodo di tempo a cui l'ho applicata).

Ecco l'SQL:

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

Questo ritorna 2011-08-22 00:00:00.000, che è un lunedì, non una domenica. Selezionando @@datefirstritorni 7, che è il codice per domenica, in modo che il server sia configurato correttamente per quanto ne so.

Posso aggirarlo abbastanza facilmente modificando il codice sopra in:

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

Ma il fatto di dover fare un'eccezione del genere mi mette un po 'a disagio. Inoltre, ci scusiamo se questa è una domanda duplicata. Ho trovato alcune domande correlate ma nessuna che affrontasse questo aspetto in modo specifico.


9
(@@DATEFIRST + DATEPART(DW, @SomeDate)) % 7rimane costante indipendentemente @@datefirstdall'impostazione credo. Con lunedì = 2.
Martin Smith

Risposte:


149

Per rispondere al motivo per cui hai un lunedì e non una domenica:

Stai aggiungendo un numero di settimane alla data 0. Cos'è la data 0? 1900-01-01. Qual è stato il giorno del 1900-01-01? Lunedi. Quindi nel tuo codice stai dicendo, quante settimane sono passate da lunedì 1 gennaio 1900? Chiamiamolo [n]. Ok, ora aggiungi [n] settimane a lunedì 1 gennaio 1900. Non dovresti sorprenderti se questo finisce per essere un lunedì. DATEADDnon ha idea che tu voglia aggiungere settimane ma solo fino ad arrivare a una domenica, aggiunge solo 7 giorni, quindi aggiungi altri 7 giorni, ... proprio come DATEDIFFriconosce solo i confini che sono stati oltrepassati. Ad esempio, entrambi restituiscono 1, anche se alcune persone si lamentano del fatto che dovrebbe esserci una logica ragionevole incorporata per arrotondare per eccesso o per difetto:

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

Per rispondere a come ottenere una domenica:

Se vuoi una domenica, scegli una data di base che non sia un lunedì ma piuttosto una domenica. Per esempio:

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

Ciò non si interromperà se modifichi l' DATEFIRSTimpostazione (o il codice è in esecuzione per un utente con un'impostazione diversa), a condizione che tu voglia comunque una domenica indipendentemente dall'impostazione corrente. Se si desidera che queste due risposte a jive, allora si dovrebbe utilizzare una funzione che non dipendono dalla DATEFIRSTimpostazione, ad esempio,

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

Quindi, se modifichi l' DATEFIRSTimpostazione su lunedì, martedì, cosa hai, il comportamento cambierà. A seconda del comportamento che desideri, puoi utilizzare una di queste funzioni:

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

...o...

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

Ora, hai molte alternative, ma quale funziona meglio? Sarei sorpreso se ci fossero differenze importanti, ma ho raccolto tutte le risposte fornite finora e le ho eseguite attraverso due serie di test: uno economico e uno costoso. Ho misurato le statistiche del cliente perché non vedo l'I / O o la memoria che giocano un ruolo nelle prestazioni qui (sebbene queste possano entrare in gioco a seconda di come viene utilizzata la funzione). Nei miei test i risultati sono:

Query di assegnazione "economica":

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

Query di assegnazione "costosa":

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

Posso trasmettere i dettagli dei miei test, se lo desideri, fermandomi qui perché sta già diventando piuttosto prolisso. Sono rimasto un po 'sorpreso di vedere che Curt è uscito come il più veloce nella fascia alta, dato il numero di calcoli e il codice in linea. Forse eseguirò alcuni test più approfonditi e ne parlerò sul blog ... se voi ragazzi non avete obiezioni a che pubblichi le vostre funzioni altrove.


Quindi, se considero che le mie settimane inizino domenica e finiscano sabato, posso ottenere l' ultimo giorno della settimana per qualsiasi data @d come questa: SELECT DATEADD (wk, DATEDIFF (wk, '19041231', @d), '19041231')
Baodad

21

Per questi che devono ottenere:

Lunedì = 1 e domenica = 7:

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

Domenica = 1 e sabato = 7:

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

Sopra c'era un esempio simile, ma grazie al doppio "% 7" sarebbe stato molto più lento.


Funziona benissimo anche per ottenere il numero del giorno dall'inizio della settimana che sia Sun o Mon. Grazie
Fandango68

In alternativa select (datediff(dd,5,cal.D_DATE)%7 + 1)eselect (datediff(dd,6,cal.D_DATE)%7 + 1)
vasja

8

Per coloro che hanno bisogno della risposta al lavoro e la creazione di funzioni è vietata dal proprio DBA, la seguente soluzione funzionerà:

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

Questo dà l'inizio di quella settimana. Qui presumo che la domenica sia l'inizio delle settimane. Se pensi che lunedì sia l'inizio, dovresti usare:

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

5

Questo funziona meravigliosamente per me:

CREA FUNZIONE [dbo]. [StartOfWeek]
(
  @INPUTDATE DATETIME
)
RESI DATETIME

COME
INIZIO
  - QUESTO non funziona in funzione.
  - SET DATEFIRST 1 - imposta il lunedì come primo giorno della settimana.

  DECLARE @DOW INT - per memorizzare il giorno della settimana
  SET @INPUTDATE = CONVERT (VARCHAR (10), @INPUTDATE, 111)
  SET @DOW = DATEPART (DW, @INPUTDATE)

  - Conversione magica di lunedì a 1, martedì a 2, ecc.
  - indipendentemente da ciò che il server SQL pensa all'inizio della settimana.
  - Ma qui abbiamo la domenica contrassegnata come 0, ma lo sistemiamo più tardi.
  SET @DOW = (@DOW + @@ DATEFIRST - 1)% 7
  IF @DOW = 0 SET @DOW = 7 - correzione per domenica

  DATA DI RITORNOADD (GG, 1 - @ DOW, @ INPUTDATE)

FINE

Questo sembra tornare lunedì data la data odierna, non domenica. L'OP ha già una funzione che ritorna lunedì, vuole che ritorni domenica. :-)
Aaron Bertrand

doh! La prossima volta dovrei leggere più attentamente le domande. Tuttavia, la mia soluzione può essere facilmente regolata, se ancora necessario. Sembra che OP sia comunque soddisfatto della risposta accettata -)
trailmax

Questa è la soluzione corretta sulla mia macchina, poiché per me: DATEADD (ww, DATEDIFF (ww, 0, CONVERT (DATE, '2017-10-8')), 0) restituisce 2017-10-9!
Esegui CMD

3

Ho cercato su Google questo script:

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

Forse hai bisogno di questo:

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

O

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

Funzione

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(DWdipende da@@datefirst
Martin Smith

Mi piace la semplicità di questo. Sembra funzionare abbastanza bene anche per set di dati molto grandi.
Quick Joe Smith

2
Perché non creare semplicemente il parametro di input, DATEquindi non è necessario eseguire conversioni sub-ottimali VARCHARe viceversa solo per rimuovere qualsiasi componente temporale accidentale passato.
Aaron Bertrand

La funzione Convert è stata utilizzata perché il valore restituito non necessita di Timevalori.
Gandarez

1
Sì, ma il punto è che la conversione in un varchar e viceversa è costosa. Se hai solo un parametro DATE, non ti interessa se il tempo è stato incluso ... viene rimosso per te.
Aaron Bertrand

2
CREA FUNZIONE dbo.fnFirstWorkingDayOfTheWeek
(
    @currentDate date
)
RESI INT
COME
INIZIO
    - ottenere l'impostazione DATEFIRST
    DICHIARARE @ds int = @@ DATEFIRST 
    - ottenere il numero del giorno della settimana sotto l'impostazione DATEFIRST corrente
    DECLARE @dow int = DATEPART (dw, @ currentDate) 

    DICHIARARE @wd int = 1 + (((@ dow + @ ds)% 7) +5)% 7 - restituisce sempre Mon come 1, Mar come 2 ... Sun come 7 

    DATA DI RITORNOADD (gg, 1- @ wd, @ currentDate) 

FINE

Questa è l'unica funzione che ha funzionato per me in SQL Server 2005. Grazie
Fandango68

@ Fernando68 Puoi spiegare come altre soluzioni non hanno funzionato?
Aaron Bertrand

@AaronBertrand scusa non ricordo, ma penso che mi stavo concentrando su una risposta rapida e ho provato la tua ma per qualche motivo non ha funzionato per me.
Fandango68

@ Fernando68 Beh, è ​​molto utile. : - \
Aaron Bertrand il

2

Per il basic (la domenica della settimana corrente)

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

Se la settimana precedente:

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

Internamente, abbiamo costruito una funzione che lo fa, ma se hai bisogno di veloce e sporco, questo lo farà.


0

Poiché la data giuliana 0 è un lunedì, aggiungi il numero di settimane alla domenica che è il giorno prima di -1 Es. seleziona dateadd (wk, datiff (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

Questa è la mia logica. Imposta il primo della settimana come lunedì, quindi calcola qual è il giorno della settimana in cui è un determinato giorno, quindi utilizzando DateAdd e Case I calcola quale sarebbe stata la data del lunedì precedente di quella settimana.


-1

Non ho problemi con nessuna delle risposte fornite qui, tuttavia penso che la mia sia molto più semplice da implementare e capire. Non ho eseguito alcun test delle prestazioni su di esso, ma dovrebbe essere trascurabile.

Quindi ho derivato la mia risposta dal fatto che le date sono memorizzate nel server SQL come numeri interi (sto parlando solo del componente data). Se non mi credi, prova con SELEZIONA CONVERT (INT, GETDATE ()) e viceversa.

Ora sapendo questo, puoi fare alcune fantastiche equazioni matematiche. Potresti essere in grado di inventarne uno migliore, ma ecco il mio.

/*
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
Trovo che questo non funzioni per me. Anche My @@DATEFIRSTè 7, ma se il tuo @testDateè l'inizio della settimana, restituisce una data che è il giorno prima.
riga 1

-1

Ho avuto un problema simile. Data una data, volevo ottenere la data del lunedì di quella settimana.

Ho usato la seguente logica: trova il numero del giorno della settimana nell'intervallo 0-6, quindi sottrai quello dalla data di origine.

Ho usato: DATEADD (giorno, - (DATEPART (giorno della settimana,) + 5)% 7,)

Poiché DATEPRRT (giorno della settimana,) restituisce 1 = domenicae ... 7 = sabato, DATEPART (giorno della settimana,) + 5)% 7 restituisce 0 = lunedì ... 6 = domenica.

Sottraendo questo numero di giorni dalla data originale si ottiene il lunedì precedente. La stessa tecnica potrebbe essere utilizzata per qualsiasi giorno di inizio della settimana.


-1

L'ho trovato semplice e utile. Funziona anche se il primo giorno della settimana è domenica o lunedì.

DICHIARARE @BaseDate AS Date

SET @BaseDate = GETDATE ()

DICHIARARE @FisrtDOW AS Date

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


-3

Forse sto semplificando eccessivamente qui, e potrebbe essere il caso, ma questo sembra funzionare per me. Non ho ancora avuto problemi con esso ...

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'

Puoi ottenere risposte diverse qui se provi impostazioni diverse per SET DATEFIRST.
Aaron Bertrand

5
Beh, non ho votato per difetto, ma la tua risposta non ha menzionato DATEFIRSTaffatto (da tre anni e mezzo a questa parte), e ancora non lo fa. E dovresti anche evitare formati regionali come m/d/y, anche in scenari in cui m e d sono gli stessi.
Aaron Bertrand
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.