SQL Serverで週の最初の日を取得する


96

集計した日付を週の最初の日として保存し、レコードを週ごとにグループ化しようとしています。ただし、日付を四捨五入するために使用する標準的な手法は、週では正しく機能しないようです(ただし、日、月、年、四半期、および私が適用した他の時間枠では機能します)。

SQLは次のとおりです。

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

これは2011-08-22 00:00:00.000、日曜日ではなく月曜日であるを返します。を選択すると、日曜日のコードであるが@@datefirst返される7ので、サーバーは私の知る限り正しくセットアップされます。

上記のコードを次のように変更することで、これを簡単にバイパスできます。

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

しかし、私がそのような例外を作らなければならないという事実は、私を少し不安にさせます。また、これが重複する質問である場合はお詫び申し上げます。私はいくつかの関連する質問を見つけましたが、この側面に特に対処するものはありませんでした。


9
(@@DATEFIRST + DATEPART(DW, @SomeDate)) % 7@@datefirst設定に関係なく一定のままです。月曜日= 2の場合
マーティンスミス

回答:


148

あなたが日曜日ではなく月曜日を受け取っている理由に答えるには:

日付0に週数を追加しています。日付0とは何ですか?1900-01-01。1900-01-01の日は何でしたか?月曜。つまり、あなたのコードでは、1900年1月1日月曜日から何週間経過したのでしょうか。それを[n]と呼びましょう。では、[n]週を1900年1月1日月曜日に追加します。これが月曜日になることに驚かないでください。DATEADD週を追加したいという考えはありませんが、日曜日になるまで、7日が追加され、さらに7日が追加されます... DATEDIFF超えられた境界のみを認識するように。たとえば、一部の人々は切り上げまたは切り捨てのためにいくつかの賢明なロジックが組み込まれているべきだと不平を言っていますが、これらはどちらも1を返します。

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

日曜日を取得する方法に答えるには:

日曜日が必要な場合は、月曜日ではなく日曜日の基準日を選択してください。例えば:

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

DATEFIRST現在の設定に関係なく日曜日が必要な場合は、設定を変更した場合(またはコードが別の設定のユーザーに対して実行されている場合)に問題は発生しません。あなたはジャイブに、これら2つの答えをしたい場合は、関数を使用する必要がありに依存しDATEFIRST、例えば設定を

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

したがって、DATEFIRST設定を月曜日、火曜日に変更すると、動作が変わります。必要な動作に応じて、次の関数のいずれかを使用できます。

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

...または...

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

今、あなたにはたくさんの選択肢がありますが、どれが最も効果的ですか?大きな違いがあるとしたら驚きますが、これまでに提供されたすべての回答を収集し、2つのテストセットを実行しました。I / Oやメモリがパフォーマンスに影響を与えていないので、クライアントの統計情報を測定しました(ただし、関数の使用方法によっては、これらが関係する場合があります)。私のテストでは、結果は次のとおりです。

「格安」割り当てクエリ:

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

「高価な」割り当てクエリ:

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

必要に応じて、テストの詳細を中継することができます。これはすでにかなり時間がかかっているため、ここで停止します。計算とインラインコードの数を考えると、Curtがハイエンドで最速となるのを見て、私は少し驚きました。多分私はそれについていくつかのより徹底的なテストとブログを実行します...もし皆さんが私にあなたの関数を他の場所に公開することに異議がない場合は。


そのため、週を日曜日に開始し土曜日に終了することを考慮した場合、次のように任意の日付@d の最後の曜日を取得できます。SELECT DATEADD(wk、DATEDIFF(wk、 '19041231'、@d)、 '19041231')
Baodad

21

これらを取得する必要がある場合:

月曜日= 1および日曜日= 7:

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

日曜日= 1および土曜日= 7:

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

上記には同様の例がありましたが、ダブル "%7"のおかげではるかに遅くなります。


これは、週の初めから日または月である日番号を取得する場合にも有効です。ありがとう
Fandango68

代わりselect (datediff(dd,5,cal.D_DATE)%7 + 1)select (datediff(dd,6,cal.D_DATE)%7 + 1)
vasja

8

職場での回答が必要であり、関数の作成がDBAによって禁止されている場合は、次のソリューションが機能します。

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

これでその週の始まりがわかります。ここでは、日曜日が週の始まりであると想定しています。月曜日が開始だと思う場合は、以下を使用する必要があります。

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

5

これは私にとって素晴らしい働きをします:

CREATE FUNCTION [dbo]。[StartOfWeek]
(
  @INPUTDATE DATETIME
)
日時を返します

なので
ベギン
  -これは機能しません。
  -SET DATEFIRST 1-月曜日を週の最初の日として設定します。

  DECLARE @DOW INT-曜日を格納する
  SET @INPUTDATE = CONVERT(VARCHAR(10)、@INPUTDATE、111)
  SET @DOW = DATEPART(DW、@INPUTDATE)

  -月曜日を1に、火曜日を2に、などの魔法の変換
  -SQLサーバーが週の初めについてどう考えているかに関係なく。
  -しかし、ここでは日曜日を0とマークしていますが、後で修正します。
  SET @DOW =(@DOW + @@ DATEFIRST-1)%7
  IF @DOW = 0 SET @DOW = 7-日曜日の修正

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

終わり

これは、日曜日ではなく、今日の日付で月曜日に返されるようです。OPにはすでに月曜日を返す関数があり、彼はそれが日曜日を返すことを望んでいます。:-)
アーロンベルトラン

どー!次回はもっと注意深く質問を読みましょう。ただし、私のソリューションは、必要に応じて簡単に調整できます。OPはとにかく受け入れられた回答に満足しているようです-)
Trailmax

これは私のマシンでは正しい解決策です。私にとっては、DATEADD(ww、DATEDIFF(ww、0、CONVERT(DATE、 '2017-10-8'))、0)は2017-10-9を返すからです!
CMD

3

このスクリプトをグーグル化:

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

多分あなたはこれが必要です:

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

または

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

関数

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(DW依存している@@datefirst
マーティン・スミス

私はこれのシンプルさが好きです。非常に大規模なデータのセットでも非常にうまく動作するようです。
クイックジョースミス

2
理由だけではなく、入力パラメータをしないでDATE、あなたがに任意のサブ最適な変換を行う必要はありませんVARCHARし、バックだけで渡された任意の不慮の時間コンポーネントを削除する。
アーロン・ベルトラン

戻り値はTime値を必要としないため、Convert関数が使用されました。
ガンダレス2011

1
はい。ただし、varcharに変換して再度変換するのはコストがかかるという点が重要です。DATEパラメータしかない場合は、時間が含まれているかどうかは関係ありません...取り除かれます。
Aaron Bertrand

2
関数の作成dbo.fnFirstWorkingDayOfTheWeek
(
    @currentDateの日付
)
INTを返す
なので
ベギン
    -DATEFIRST設定を取得します
    DECLARE @ds int = @@ DATEFIRST 
    -現在のDATEFIRST設定の下の曜日番号を取得します
    DECLARE @dow int = DATEPART(dw、@ currentDate) 

    DECLARE @wd int = 1 +((((@ dow + @ ds)%7)+5)%7-これは常にMonを1、Tueを2 ... Sunを7として返します 

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

終わり

これは、SQL Server 2005で私のために機能した唯一の機能です。ありがとう
Fandango68

@ Fernando68他の解決策がうまくいかなかった方法を説明できますか?
Aaron Bertrand

@AaronBertrand申し訳ありませんが、私は迅速な回答に焦点を当てていたと思います。私はあなたの回答を試してみましたが、何らかの理由でそれがうまくいきませんでした。
Fandango68 2016年

@ Fernando68まあ、それは非常に役に立ちます。:-\
Aaron Bertrand

2

基本(今週の日曜日)

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

前週の場合:

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

内部的には、それを実行する関数を作成しましたが、すばやくダーティが必要な場合は、これで実行できます。


0

ユリウス日付0は月曜日なので、日曜日に週の数を追加するだけです。select dateadd(wk、datediff(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

これが私の論理です。週の最初を月曜日に設定してから、与えられた日が何曜日かを計算し、次にDateAddとCaseを使用して、その週の前の月曜日の日付を計算します。


-1

私はここで与えられた答えのいずれについても問題はありませんが、私の方が実装と理解がはるかに簡単だと思います。パフォーマンステストは実行していませんが、ごくわずかです。

だから私は日付がSQLサーバーに整数として格納されているという事実から私の答えを導き出しました(私は日付コンポーネントのみについて話しています)。信じられない場合は、このSELECT CONVERT(INT、GETDATE())を試してください。逆も同様です。

これを知った今、あなたはいくつかのクールな数学の方程式を行うことができます。あなたはもっと良いものを思いつくことができるかもしれませんが、これが私のものです。

/*
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
これではうまくいかないことがわかりました。私@@DATEFIRSTも7ですが、あなた@testDateが週の初めなら、これは前日の日付を返します。
row1

-1

同様の問題がありました。日付を指定すると、その週の月曜日の日付を取得したいと思いました。

私は次のロジックを使用しました:週の日数を0から6の範囲で見つけ、それを元の日付から減算します。

私が使用した:DATEADD(day、-(DATEPART(weekday、)+ 5)%7、)

DATEPRRT(weekday、)は1 =日曜日... 7 =土曜日を返すため、DATEPART(weekday、)+ 5)%7は0 =月曜日... 6 =日曜日を返します。

元の日付からこの日数を引くと、前の月曜日になります。週のどの曜日にも同じ手法を使用できます。


-1

私はこれがシンプルで便利だと思いました。週の最初の日が日曜日または月曜日であっても機能します。

DECLARE @BaseDate AS Date

SET @BaseDate = GETDATE()

DECLARE @FisrtDOW AS日付

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


-3

ここで単純化しすぎているのかもしれませんが、そうかもしれませんが、これでうまくいくようです。まだ問題は発生していません...

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'

の別の設定を試すと、ここで別の答えを得ることができますSET DATEFIRST
アーロンバートランド

5
ええと、私は反対票を投じませんでしたが、あなたの答えはDATEFIRSTまったく(3年半の間)言及していませんでしたが、まだ言及していません。そして、あなたはすべきのような地域の形式を避けるためm/d/yにもMとDが同じであるシナリオでは、。
Aaron Bertrand
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.