Obtenez le premier jour de la semaine dans SQL Server

97

J'essaie de regrouper les enregistrements par semaine, en stockant la date agrégée comme premier jour de la semaine. Cependant, la technique standard que j'utilise pour arrondir les dates ne semble pas fonctionner correctement avec des semaines (bien qu'elle le fasse pendant des jours, des mois, des années, des trimestres et tout autre délai auquel je l'ai appliquée).

Voici le SQL:

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

Cela revient 2011-08-22 00:00:00.000, qui est un lundi, pas un dimanche. Sélection des @@datefirstretours 7, qui est le code pour dimanche, afin que le serveur soit configuré correctement pour autant que je sache.

Je peux contourner cela assez facilement en changeant le code ci-dessus en:

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

Mais le fait que je doive faire une telle exception me met un peu mal à l'aise. Aussi, excuses si c'est une question en double. J'ai trouvé des questions connexes, mais aucune ne traitait spécifiquement de cet aspect.

Joe Smith rapide
la source
9
(@@DATEFIRST + DATEPART(DW, @SomeDate)) % 7reste constant quel que soit le @@datefirstréglage je pense. With Monday = 2.
Martin Smith

Réponses:

149

Pour expliquer pourquoi vous obtenez un lundi et non un dimanche:

Vous ajoutez un nombre de semaines à la date 0. Qu'est-ce que la date 0? 1900-01-01. Quel était le jour du 1900-01-01? Lundi. Donc, dans votre code, vous dites, combien de semaines se sont écoulées depuis le lundi 1er janvier 1900? Appelons cela [n]. OK, ajoutez maintenant [n] semaines au lundi 1er janvier 1900. Vous ne devriez pas être surpris que cela finisse par être un lundi. DATEADDn'a aucune idée que vous voulez ajouter des semaines, mais seulement jusqu'à ce que vous arriviez à un dimanche, il s'agit simplement d'ajouter 7 jours, puis d'ajouter 7 jours de plus, ... tout comme DATEDIFFne reconnaît que les limites qui ont été franchies. Par exemple, les deux renvoient 1, même si certaines personnes se plaignent qu'il devrait y avoir une logique sensée intégrée pour arrondir vers le haut ou vers le bas:

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

Pour savoir comment obtenir un dimanche:

Si vous voulez un dimanche, choisissez une date de base qui n'est pas un lundi mais plutôt un dimanche. Par exemple:

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

Cela ne cassera pas si vous modifiez votre DATEFIRSTparamètre (ou si votre code est en cours d'exécution pour un utilisateur avec un paramètre différent) - à condition que vous souhaitiez toujours un dimanche quel que soit le paramètre actuel. Si vous voulez que ces deux réponses à jive, vous devez utiliser une fonction qui ne dépend du DATEFIRSTréglage, par exemple

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

Donc, si vous changez votre DATEFIRSTréglage sur lundi, mardi, qu'as-tu, le comportement changera. Selon le comportement souhaité, vous pouvez utiliser l'une de ces fonctions:

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

...ou...

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

Maintenant, vous avez beaucoup d'alternatives, mais laquelle fonctionne le mieux? Je serais surpris qu'il y ait des différences majeures, mais j'ai rassemblé toutes les réponses fournies jusqu'à présent et les ai passées à travers deux séries de tests - un bon marché et un cher. J'ai mesuré les statistiques du client car je ne vois pas les E / S ou la mémoire jouer un rôle dans la performance ici (bien que celles-ci puissent entrer en jeu en fonction de la façon dont la fonction est utilisée). Dans mes tests, les résultats sont:

Requête d'affectation "bon marché":

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

Requête d'attribution "coûteuse":

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

Je peux relayer les détails de mes tests si vous le souhaitez - en m'arrêtant ici car cela devient déjà assez long. J'ai été un peu surpris de voir que Curt est le plus rapide du haut de gamme, compte tenu du nombre de calculs et du code en ligne. Peut-être que je vais faire des tests plus approfondis et bloguer à ce sujet ... si vous n'avez aucune objection à ce que je publie vos fonctions ailleurs.

Aaron Bertrand
la source
Donc, si je considère que mes semaines commencent le dimanche et se terminent le samedi, je peux obtenir le dernier jour de la semaine pour n'importe quelle date @d comme ceci: SELECT DATEADD (wk, DATEDIFF (wk, '19041231', @d), '19041231')
Baodad
21

Pour ceux qui doivent obtenir:

Lundi = 1 et dimanche = 7:

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

Dimanche = 1 et samedi = 7:

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

Ci-dessus, il y avait un exemple similaire, mais grâce au double "% 7", ce serait beaucoup plus lent.

Kakkarot
la source
Cela fonctionne très bien aussi pour obtenir le numéro du jour à partir du début de la semaine, soit dimanche ou lundi. Merci
Fandango68
Alternativement select (datediff(dd,5,cal.D_DATE)%7 + 1)etselect (datediff(dd,6,cal.D_DATE)%7 + 1)
vasja
8

Pour ceux qui ont besoin de la réponse au travail et la création de fonction est interdite par votre DBA, la solution suivante fonctionnera:

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

Cela donne le début de cette semaine. Ici, je suppose que les dimanches sont le début des semaines. Si vous pensez que le lundi est le début, vous devez utiliser:

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

Cela fonctionne à merveille pour moi:

CRÉER UNE FONCTION [dbo]. [StartOfWeek]
(
  @INPUTDATE DATETIME
)
DATE DE RETOUR

COMME
COMMENCER
  - CECI ne fonctionne pas en fonction.
  - SET DATEFIRST 1 - définit lundi comme premier jour de la semaine.

  DECLARE @DOW INT - pour stocker le jour de la semaine
  SET @INPUTDATE = CONVERT (VARCHAR (10), @INPUTDATE, 111)
  SET @DOW = DATEPART (DW, @INPUTDATE)

  - Conversion magique du lundi au 1, du mardi au 2, etc.
  - indépendamment de ce que pense le serveur SQL du début de la semaine.
  - Mais ici, nous avons dimanche marqué 0, mais nous corrigeons cela plus tard.
  SET @DOW = (@DOW + @@ DATEFIRST - 1)% 7
  IF @DOW = 0 SET @DOW = 7 - correction pour dimanche

  RETOUR DATEADD (JJ, 1 - @ DOW, @ INPUTDATE)

FIN
trailmax
la source
Cela semble revenir lundi étant donné la date d'aujourd'hui, pas dimanche. L'OP a déjà une fonction qui retourne lundi, il veut qu'elle revienne dimanche. :-)
Aaron Bertrand
doh! Je devrais lire les questions plus attentivement la prochaine fois. Cependant, ma solution peut être facilement ajustée, si nécessaire. On dirait que OP est quand même satisfait de la réponse acceptée -)
trailmax
C'est la bonne solution sur ma machine, car pour moi: DATEADD (ww, DATEDIFF (ww, 0, CONVERT (DATE, '2017-10-8')), 0) renvoie 2017-10-9!
Exécutez CMD
3

Googlé ce 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

Sec
la source
2

Peut-être avez-vous besoin de ceci:

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

Ou

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

Fonction

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
la source
6
DATEPART(DWdépend de@@datefirst
Martin Smith
J'aime la simplicité de celui-ci. Il semble également fonctionner assez bien pour de très grands ensembles de données.
Quick Joe Smith
2
Pourquoi ne pas simplement créer le paramètre d'entrée, DATEalors vous n'avez pas à faire de conversions sous-optimales vers VARCHARet inversement juste pour supprimer toute composante temporelle accidentelle qui est passée.
Aaron Bertrand
La fonction Convert a été utilisée car la valeur renvoyée n'a pas besoin de Timevaleurs.
Gandarez
1
Oui, mais le fait est que la conversion en varchar et vice-versa coûte cher. Si vous avez juste un paramètre DATE, vous ne vous souciez pas de savoir si le temps a été inclus ... il est supprimé pour vous.
Aaron Bertrand
2
CRÉER UNE FONCTION dbo.fnFirstWorkingDayOfTheWeek
(
    @currentDate date
)
RETOURS INT
COMME
COMMENCER
    - obtenir le paramètre DATEFIRST
    DÉCLARER @ds int = @@ DATEFIRST 
    - obtenir le numéro du jour de la semaine sous le paramètre DATEFIRST actuel
    DECLARE @dow int = DATEPART (dw, @ currentDate) 

    DECLARE @wd int = 1 + (((@ dow + @ ds)% 7) +5)% 7 - c'est toujours retourner Mon comme 1, Tue comme 2 ... Sun comme 7 

    RETOUR DATEADD (jj, 1- @ wd, @ currentDate) 

FIN
JG JIN
la source
C'est la seule fonction qui a fonctionné pour moi dans SQL Server 2005. Merci
Fandango68
@ Fernando68 Pouvez-vous expliquer comment d'autres solutions n'ont pas fonctionné?
Aaron Bertrand
@AaronBertrand désolé, je ne m'en souviens pas, mais je pense que je me concentrais sur une réponse rapide et j'ai essayé la vôtre, mais pour une raison quelconque, cela n'a pas fonctionné pour moi.
Fandango68
@ Fernando68 Eh bien, c'est très utile. : - \
Aaron Bertrand
2

Pour le basique (le dimanche de la semaine en cours)

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

Si la semaine précédente:

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

En interne, nous avons construit une fonction qui le fait mais si vous avez besoin de rapide et sale, cela le fera.

JamesDavisSr
la source
0

Puisque la date julienne 0 est un lundi, ajoutez simplement le nombre de semaines au dimanche qui est le jour avant -1 Par exemple. sélectionnez dateadd (sem, dateiff (sem, 0, getdate ()), - 1)

Iggy
la source
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

C'est ma logique. Définissez le premier de la semaine sur lundi, puis calculez quel est le jour de la semaine d'un jour donné, puis en utilisant DateAdd et Case, je calcule ce que la date aurait été le lundi précédent de cette semaine.

user2479728
la source
-1

Je n'ai aucun problème avec les réponses données ici, mais je pense que la mienne est beaucoup plus simple à mettre en œuvre et à comprendre. Je n'ai effectué aucun test de performance dessus, mais cela devrait être négligeable.

J'ai donc dérivé ma réponse du fait que les dates sont stockées dans le serveur SQL sous forme d'entiers (je ne parle que du composant de date). Si vous ne me croyez pas, essayez ce SELECT CONVERT (INT, GETDATE ()), et vice versa.

Maintenant, sachant cela, vous pouvez faire des équations mathématiques intéressantes. Vous pourrez peut-être en trouver un meilleur, mais voici le mien.

/*
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
la source
1
Je trouve que cela ne fonctionne pas pour moi. Mon @@DATEFIRSTest également 7, mais si vous @testDateêtes le début de la semaine, cela renvoie une date qui est la veille.
row1
-1

J'avais un problème similaire. Étant donné une date, je voulais obtenir la date du lundi de cette semaine.

J'ai utilisé la logique suivante: trouvez le numéro du jour de la semaine dans la plage de 0 à 6, puis soustrayez-le de la date d'origine.

J'ai utilisé: DATEADD (jour, - (DATEPART (jour de la semaine,) + 5)% 7,)

Puisque DATEPRRT (jour de la semaine,) renvoie 1 = dimanche ... 7 = samedi, DATEPART (jour de la semaine,) + 5)% 7 renvoie 0 = lundi ... 6 = dimanche.

En soustrayant ce nombre de jours de la date d'origine, on obtient le lundi précédent. La même technique pourrait être utilisée pour n'importe quel jour de départ de la semaine.

Pat Lane
la source
-1

J'ai trouvé cela simple et utile. Fonctionne même si le premier jour de la semaine est le dimanche ou le lundi.

DÉCLARER @BaseDate AS Date

SET @BaseDate = GETDATE ()

DÉCLARER @FisrtDOW AS Date

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

ElJoel
la source
-3

Peut-être que je simplifie trop ici, et cela peut être le cas, mais cela semble fonctionner pour moi. Je n'ai pas encore rencontré de problèmes avec ça ...

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'
marque
la source
Vous pouvez obtenir des réponses différentes ici si vous essayez différents paramètres pour SET DATEFIRST.
Aaron Bertrand
5
Eh bien, je n'ai pas voté, mais votre réponse n'a pas du tout mentionné DATEFIRST(depuis trois ans et demi maintenant), et ne le fait toujours pas. Et vous devriez également éviter les formats régionaux comme m/d/y, même dans les scénarios où le m et le d sont identiques.
Aaron Bertrand