web-dev-qa-db-fra.com

Obtenez le premier jour de la semaine dans SQL Server

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 les semaines (bien que ce soit le cas pour les jours, les mois, les années, les 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);

Ceci retourne 2011-08-22 00:00:00.000, qui est un lundi et non un dimanche. Sélection @@datefirst résultats 7, qui est le code de dimanche, le serveur est donc correctement configuré pour autant que je sache.

Je peux contourner ce problème assez facilement en remplaçant le code ci-dessus par:

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

Mais le fait que je sois obligé de faire une telle exception me met un peu mal à l'aise. Aussi, excusez-moi s'il s'agit d'une question en double. J'ai trouvé quelques questions connexes mais aucune ne traitait spécifiquement de cet aspect.

88
Quick Joe Smith

Pour répondre pourquoi vous obtenez un lundi et non un dimanche:

Vous ajoutez un nombre de semaines à la date 0. Quelle est la date 0? 1900-01-01. Quelle était la journée le 1900-01-01? Lundi. Vous dites donc dans votre code combien de semaines se sont écoulées depuis le lundi 1er janvier 1900? Appelons ça [n]. Ok, ajoutez maintenant [n] semaines au lundi 1er janvier 1900. Ne soyez pas surpris que ce soit un lundi. DATEADD n'a aucune idée que vous souhaitiez ajouter des semaines, mais seulement jusqu'à ce que vous arriviez à un dimanche, il ne fait qu'ajouter 7 jours, puis 7 jours supplémentaires, tout comme DATEDIFF ne reconnaît que les limites qui ont été traversés. Par exemple, ils renvoient 1, bien que certaines personnes se plaignent de la logique logique pour arrondir les échelons:

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 un dimanche. Par exemple:

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

Cela ne sera pas interrompu si vous modifiez votre paramètre DATEFIRST (ou si votre code est exécuté 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 se rejoignent, vous devez utiliser une fonction qui dépend du paramètre DATEFIRST, par ex.

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

Donc, si vous modifiez votre réglage DATEFIRST en Lundi, mardi, peu importe, le comportement changera. En fonction du comportement souhaité, vous pouvez utiliser l'une des fonctions suivantes:

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

Vous avez maintenant de nombreuses 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 à ce jour et les ai passées à travers deux séries de tests, l'un bon marché et l'autre cher. J'ai mesuré les statistiques client car je ne voyais aucune entrée/sortie ou mémoire jouer dans la performance (bien que celles-ci puissent entrer en jeu selon l'utilisation de la fonction). Dans mes tests, les résultats sont:

Requête d'affectation "pas cher":

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 "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 je le souhaite - en m'arrêtant ici, cela est déjà assez long. J'ai été un peu surpris de voir Curt arriver 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 pas d'objection à ce que je publie vos fonctions ailleurs.

134
Aaron Bertrand

Pour ceux qui ont besoin d'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.

15
Kakkarot

Cela fonctionne à merveille pour moi:

 CREATE FUNCTION [dbo]. [StartOfWeek] 
 (
 @INPUTDATE DATETIME 
) 
 RETOURNE LA DATETIME 
 
 EN TANT QUE 
 COMMENCE 
 - CECI ne fonctionne pas correctement. 
 - FIXER DATEFIRST 1 - le lundi doit être le premier jour de la semaine. 
 
 DECLARE @DOW INT - pour enregistrer le jour de la semaine 
 SET @INPUTDATE = CONVERT (VARCHAR (10), @INPUTDATE, 111) 
 SET @DOW = DATEPART (DW, @ INPUTDATE) 
 
 - Conversion magique du lundi au mardi 1, du mardi au mardi 2, etc. 
 - quel que soit l’avis du serveur SQL sur le 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 - solution pour le dimanche 
 
 DATE DE RETOUR ADJ (DD, 1 - @ DOW, @ INPUTDATE) 
 
 FIN 
5
trailmax

Pour ceux qui ont besoin de la réponse au travail et que la fonction de création est interdite par votre administrateur de base de données, 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 lundi est le début, vous devriez utiliser:

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

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

3
Curt

Peut-être avez-vous besoin de ça:

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)

Une 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
2
Gandarez
 CREATE FUNCTION dbo.fnFirstWorkingDayOfTheWeek 
 (
 @CurrentDate date 
) 
 RETURNS INT 
 AS 
 BEGIN 
 - récupère le paramètre DATEFIRST 
 DECLARE @ds int = @@ DATEFIRST 
 - récupère le numéro du jour de la semaine sous le paramètre actuel DATEFIRST 
 DECLARE @dow int = DATEPART (dw , @ currentDate) 
 
 DECLARE @wd int = 1 + (((@ dow + @ ds)% 7) +5)% 7 - cela renvoie toujours Lun comme 1, Mardi comme 2 ... Dimanche à 7 
 
 DATE DE RETOUR ADJR (jj, 1- @ jj, @ date du jour) 
 
 FIN 
2
JG JIN

Comme la date julienne 0 est un lundi, il suffit d’ajouter le nombre de semaines au dimanche, qui correspond au jour précédent. sélectionnez dateadd (sem, datiff (sem, 0, getdate ()), - 1)

0
Iggy
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. Réglez le premier jour de la semaine sur lundi, puis calculez le jour de la semaine, puis utilisez DateAdd et Cas I pour calculer la date du lundi précédent de la semaine.

0
user2479728

Pour la base (dimanche de la semaine en cours)

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

Si 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 de sale, cela le fera.

0
JamesDavisSr