Holen Sie sich den ersten Wochentag in SQL Server

95

Ich versuche, Datensätze nach Woche zu gruppieren und das aggregierte Datum als ersten Tag der Woche zu speichern. Die Standardtechnik, die ich zum Abrunden von Daten verwende, scheint jedoch mit Wochen nicht richtig zu funktionieren (obwohl dies für Tage, Monate, Jahre, Quartale und andere Zeiträume gilt, auf die ich sie angewendet habe).

Hier ist die SQL:

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

Dies kehrt zurück 2011-08-22 00:00:00.000, was ein Montag ist, kein Sonntag. Wenn Sie @@datefirstReturns auswählen 7, ist dies der Code für Sonntag, damit der Server meines Wissens korrekt eingerichtet ist.

Ich kann dies leicht genug umgehen, indem ich den obigen Code in ändere:

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

Aber die Tatsache, dass ich eine solche Ausnahme machen muss, macht mich ein wenig unruhig. Ich entschuldige mich auch, wenn dies eine doppelte Frage ist. Ich habe einige verwandte Fragen gefunden, aber keine, die diesen Aspekt speziell angesprochen haben.

Schnell Joe Smith
quelle
9
(@@DATEFIRST + DATEPART(DW, @SomeDate)) % 7bleibt konstant, unabhängig von der @@datefirstEinstellung, denke ich. Mit Montag = 2.
Martin Smith

Antworten:

147

Um zu beantworten, warum Sie einen Montag und keinen Sonntag bekommen:

Sie fügen dem Datum 0 eine Anzahl von Wochen hinzu. Was ist das Datum 0? 1900-01-01. Was war der Tag am 1900-01-01? Montag. In Ihrem Code sagen Sie also, wie viele Wochen sind seit Montag, dem 1. Januar 1900 vergangen? Nennen wir das [n]. Ok, fügen Sie jetzt [n] Wochen zum Montag, dem 1. Januar 1900, hinzu. Sie sollten sich nicht wundern, dass dies ein Montag wird. DATEADDhat keine Ahnung, dass Sie Wochen hinzufügen möchten, aber nur bis Sie zu einem Sonntag kommen, es werden nur 7 Tage und dann 7 weitere Tage hinzugefügt, ... genau wie DATEDIFFnur Grenzen erkannt werden, die überschritten wurden. Zum Beispiel geben beide 1 zurück, obwohl sich einige Leute darüber beschweren, dass eine vernünftige Logik eingebaut sein sollte, um auf- oder abzurunden:

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

Um zu beantworten, wie man einen Sonntag bekommt:

Wenn Sie einen Sonntag wünschen, wählen Sie ein Basisdatum, das kein Montag, sondern ein Sonntag ist. Beispielsweise:

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

Dies wird nicht unterbrochen, wenn Sie Ihre DATEFIRSTEinstellung ändern (oder Ihr Code für einen Benutzer mit einer anderen Einstellung ausgeführt wird) - vorausgesetzt, Sie möchten unabhängig von der aktuellen Einstellung weiterhin einen Sonntag. Wenn Sie möchten, dass diese beiden Antworten funktionieren, sollten Sie eine Funktion verwenden, die von der DATEFIRSTEinstellung abhängt , z

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

Wenn Sie also Ihre DATEFIRSTEinstellung auf Montag, Dienstag ändern, ändert sich das Verhalten. Je nachdem, welches Verhalten Sie möchten, können Sie eine der folgenden Funktionen verwenden:

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

...oder...

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

Jetzt haben Sie viele Alternativen, aber welche ist die beste? Ich wäre überrascht, wenn es größere Unterschiede geben würde, aber ich habe alle bisher gegebenen Antworten gesammelt und zwei Testreihen durchlaufen - eine billige und eine teure. Ich habe Client-Statistiken gemessen, weil ich nicht sehe, dass E / A oder Speicher hier eine Rolle bei der Leistung spielen (obwohl diese je nach Verwendung der Funktion ins Spiel kommen können). In meinen Tests sind die Ergebnisse:

Zuweisungsabfrage "Günstig":

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

"Teure" Zuweisungsabfrage:

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

Auf Wunsch kann ich die Details meiner Tests weitergeben - hier anhalten, da dies bereits ziemlich langwierig wird. Ich war ein bisschen überrascht zu sehen, dass Curt's angesichts der Anzahl der Berechnungen und des Inline-Codes am schnellsten als der schnellste herauskam. Vielleicht führe ich gründlichere Tests durch und blogge darüber ... wenn ihr keine Einwände dagegen habt, dass ich eure Funktionen woanders veröffentliche.

Aaron Bertrand
quelle
Wenn ich also davon ausgehe , dass meine Wochen am Sonntag beginnen und am Samstag enden, kann ich den letzten Tag der Woche für ein beliebiges Datum @d wie folgt abrufen: SELECT DATEADD (wk, DATEDIFF (wk, '19041231', @d), '19041231')
Baodad
20

Für diese, die bekommen müssen:

Montag = 1 und Sonntag = 7:

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

Sonntag = 1 und Samstag = 7:

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

Oben gab es ein ähnliches Beispiel, aber dank doppeltem "% 7" wäre es viel langsamer.

Kakkarot
quelle
Dies funktioniert auch hervorragend, um die Tagesnummer von Anfang der Woche als Sonne oder Mo zu erhalten. Danke
Fandango68
Alternativ select (datediff(dd,5,cal.D_DATE)%7 + 1)undselect (datediff(dd,6,cal.D_DATE)%7 + 1)
Vasja
8

Für diejenigen, die die Antwort bei der Arbeit benötigen und das Erstellen von Funktionen von Ihrem DBA verboten ist, funktioniert die folgende Lösung:

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

Dies gibt den Beginn dieser Woche. Hier gehe ich davon aus, dass Sonntage der Beginn von Wochen sind. Wenn Sie denken, dass Montag der Anfang ist, sollten Sie verwenden:

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

Das funktioniert wunderbar für mich:

FUNKTION ERSTELLEN [dbo]. [StartOfWeek]
(
  @ INPUTDATE DATETIME
)
RÜCKGABE DATETIME

WIE
START
  - DIESES funktioniert nicht in Funktion.
  - SET DATEFIRST 1 - Setze Montag auf den ersten Tag der Woche.

  DECLARE @DOW INT - zum Speichern des Wochentags
  SET @INPUTDATE = CONVERT (VARCHAR (10), @INPUTDATE, 111)
  SET @DOW = DATEPART (DW, @INPUTDATE)

  - Magische Umrechnung von Montag auf 1, Dienstag auf 2 usw.
  - unabhängig davon, was SQL Server über den Wochenbeginn denkt.
  - Aber hier haben wir Sonntag als 0 markiert, aber wir beheben dies später.
  SET @DOW = (@DOW + @@ DATEFIRST - 1)% 7
  IF @DOW = 0 SET @DOW = 7 - Fix für Sonntag

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

ENDE
Trailmax
quelle
Dies scheint Montag angesichts des heutigen Datums zurückzukehren, nicht Sonntag. Das OP hat bereits eine Funktion, die Montag zurückgibt, er möchte, dass sie Sonntag zurückgibt. :-)
Aaron Bertrand
doh! Ich sollte die Fragen beim nächsten Mal sorgfältiger lesen. Meine Lösung kann jedoch bei Bedarf problemlos angepasst werden. Scheint, als ob OP mit der akzeptierten Antwort trotzdem zufrieden ist -)
trailmax
Dies ist die richtige Lösung auf meinem Computer, da für mich: DATEADD (ww, DATEDIFF (ww, 0, CONVERT (DATE, '2017-10-8')), 0) 2017-10-9 zurückgibt!
Führen Sie CMD
3

Googelte dieses Skript:

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

Curt
quelle
2

Vielleicht brauchst du das:

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

Oder

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

Funktion

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
quelle
6
DATEPART(DWist abhängig von@@datefirst
Martin Smith
Ich mag die Einfachheit dieses. Es scheint auch für sehr große Datenmengen recht gut zu funktionieren.
Schnell Joe Smith
2
Warum nicht einfach den Eingabeparameter eingeben, DATEdann müssen Sie keine suboptimalen Konvertierungen hin VARCHARund zurück durchführen, um eine versehentliche
Aaron Bertrand
Die Konvertierungsfunktion wurde verwendet, da der zurückgegebene Wert keine TimeWerte benötigt .
Gandarez
1
Ja, aber der Punkt ist, dass die Umstellung auf einen Varchar und wieder zurück teuer ist. Wenn Sie nur einen DATE-Parameter haben, ist es Ihnen egal, ob die Zeit enthalten war ... sie wird für Sie entfernt.
Aaron Bertrand
2
CREATE FUNCTION dbo.fnFirstWorkingDayOfTheWeek
(
    @currentDate Datum
)
RÜCKGABE INT
WIE
START
    - DATEFIRST einstellen
    DECLARE @ds int = @@ DATEFIRST 
    - Holen Sie sich die Wochentagsnummer unter der aktuellen DATEFIRST-Einstellung
    DECLARE @dow int = DATEPART (dw, @ currentDate) 

    DECLARE @wd int = 1 + (((@ dow + @ ds)% 7) +5)% 7 - dies ist immer Mon als 1, Di als 2 ... So als 7 

    RETURN DATEADD (dd, 1- @ wd, @ currentDate) 

ENDE
JG JIN
quelle
Dies ist die einzige Funktion, die für mich in SQL Server 2005 funktioniert hat. Vielen Dank
Fandango68
@ Fernando68 Kannst du erklären, wie andere Lösungen nicht funktionierten?
Aaron Bertrand
@ AaronBertrand Entschuldigung, erinnere mich nicht, aber ich glaube, ich habe mich auf eine schnelle Antwort konzentriert und deine ausprobiert, aber aus irgendeinem Grund hat es bei mir nicht funktioniert.
Fandango68
@ Fernando68 Nun, das ist sehr hilfreich. : - \
Aaron Bertrand
2

Für die Basis (der Sonntag der aktuellen Woche)

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

Wenn letzte Woche:

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

Intern haben wir eine Funktion erstellt, die dies tut. Wenn Sie jedoch schnell und schmutzig arbeiten müssen, ist dies der Fall.

JamesDavisSr
quelle
0

Da das julianische Datum 0 ein Montag ist, addieren Sie einfach die Anzahl der Wochen zum Sonntag, der der Tag vor -1 ist. Wählen Sie dateadd (wk, dateiff (wk, 0, getdate ()), - 1).

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

Das ist meine Logik. Stellen Sie den ersten der Woche auf Montag ein und berechnen Sie dann den Wochentag, an dem ein bestimmter Tag ist. Berechnen Sie dann mit DateAdd und Case I, wie das Datum am vorherigen Montag dieser Woche gewesen wäre.

user2479728
quelle
-1

Ich habe keine Probleme mit den hier gegebenen Antworten, aber ich denke, meine ist viel einfacher zu implementieren und zu verstehen. Ich habe keine Leistungstests durchgeführt, aber es sollte vernachlässigbar sein.

Daher habe ich meine Antwort aus der Tatsache abgeleitet, dass Datumsangaben als Ganzzahlen in SQL Server gespeichert sind (ich spreche nur von der Datumskomponente). Wenn Sie mir nicht glauben, versuchen Sie es mit SELECT CONVERT (INT, GETDATE ()) und umgekehrt.

Wenn Sie dies wissen, können Sie einige coole mathematische Gleichungen machen. Vielleicht können Sie sich eine bessere einfallen lassen, aber hier ist meine.

/*
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
quelle
1
Ich finde, dass das bei mir nicht funktioniert. Mein @@DATEFIRSTist auch 7, aber wenn Ihr @testDateder Beginn der Woche ist, gibt dies ein Datum zurück, das der Tag zuvor ist.
Reihe1
-1

Ich hatte ein ähnliches Problem. Angesichts eines Datums wollte ich das Datum des Montags dieser Woche erhalten.

Ich habe die folgende Logik verwendet: Finden Sie die Tagesnummer in der Woche im Bereich von 0-6 und subtrahieren Sie diese vom Ursprungsdatum.

Ich habe verwendet: DATEADD (Tag, - (DATEPART (Wochentag,) + 5)% 7,)

Da DATEPRRT (Wochentag) 1 = Sonntag ... 7 = Samstag zurückgibt, gibt DATEPART (Wochentag) + 5)% 7 0 = Montag ... 6 = Sonntag zurück.

Wenn Sie diese Anzahl von Tagen vom ursprünglichen Datum abziehen, erhalten Sie den vorherigen Montag. Die gleiche Technik kann für jeden Starttag der Woche angewendet werden.

Pat Lane
quelle
-1

Ich fand das einfach und nützlich. Funktioniert auch, wenn der erste Wochentag Sonntag oder Montag ist.

DECLARE @BaseDate AS Date

SET @BaseDate = GETDATE ()

DECLARE @FisrtDOW AS Date

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

ElJoel
quelle
-3

Vielleicht bin ich hier zu stark vereinfacht, und das mag der Fall sein, aber das scheint für mich zu funktionieren. Ich habe noch keine Probleme damit gehabt ...

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'
Kennzeichen
quelle
Sie können hier unterschiedliche Antworten erhalten, wenn Sie unterschiedliche Einstellungen für ausprobieren SET DATEFIRST.
Aaron Bertrand
5
Nun, ich habe nicht abgestimmt, aber Ihre Antwort hat es überhaupt nicht erwähnt DATEFIRST(seit dreieinhalb Jahren) und tut es immer noch nicht. Und Sie sollten auch regionale Formate wie vermeiden m/d/y, selbst in Szenarien, in denen m und d gleich sind.
Aaron Bertrand