日時値の時間部分を削除するにはどうすればよいですか(SQLServer)?


84

これが私が使用するものです:

SELECT CAST(FLOOR(CAST(getdate() as FLOAT)) as DATETIME)

もっと上品でエレガントな方法があるのではないかと思います。

要件:

  • できるだけ速くする必要があります(キャストが少ないほど良い)。
  • 最終的な結果はdatetime、文字列ではなく型である必要があります。

回答:


116

SQL Server2008以降

もちろん、SQL Server 2008以降では、最速の方法はConvert(date, @date)です。これは、datetimeまたはdatetime2必要に応じてキャストバックできます。

SQL Server 2005以前で本当に最高のものは何ですか?

SQL Serverの日付からの時間を切り捨てるのに最も速いものについて一貫性のない主張を見てきました。また、テストを行ったと言う人もいますが、私の経験は異なります。それでは、もっと厳しいテストを行い、全員にスクリプトを持たせて、間違いがあった場合に人々が私を訂正できるようにしましょう。

フロート変換は正確ではありません

まず、私は変換から離れて滞在するdatetimeにはfloat、それが正しく変換されませんので、。時間の削除を正確に行うことでうまくいくかもしれませんが、これは安全な操作であり、そうではないことを開発者に暗黙的に伝えるため、使用するのは悪い考えだと思います。見てください:

declare @d datetime;
set @d = '2010-09-12 00:00:00.003';
select Convert(datetime, Convert(float, @d));
-- result: 2010-09-12 00:00:00.000 -- oops

これは、コードやオンラインの例で人々に教えるべきものではありません。

また、それは最速の方法でもありません!

証明–パフォーマンステスト

いくつかのテストを自分で実行して、さまざまなメソッドが実際にどのように積み重なっているかを確認する場合は、次のセットアップスクリプトを使用して、テストをさらに下に実行する必要があります。

create table AllDay (Tm datetime NOT NULL CONSTRAINT PK_AllDay PRIMARY KEY CLUSTERED);
declare @d datetime;
set @d = DateDiff(Day, 0, GetDate());
insert AllDay select @d;
while @@ROWCOUNT != 0
   insert AllDay
   select * from (
      select Tm =
         DateAdd(ms, (select Max(DateDiff(ms, @d, Tm)) from AllDay) + 3, Tm)
      from AllDay
   ) X
   where Tm < DateAdd(Day, 1, @d);
exec sp_spaceused AllDay;  -- 25,920,000 rows

これにより、データベースに427.57 MBのテーブルが作成され、実行に15〜30分かかることに注意してください。データベースが小さく、10%の拡張に設定されている場合、最初に十分なサイズを設定した場合よりも時間がかかります。

次に、実際のパフォーマンステストスクリプトについて説明します。これは2600万行で非常に高価であり、メソッド間のパフォーマンスの違いを隠すため、クライアントに行を返さないことが目的であることに注意してください。

パフォーマンス結果

set statistics time on;
-- (All queries are the same on io: logical reads 54712)
GO
declare
    @dd date,
    @d datetime,
    @di int,
    @df float,
    @dv varchar(10);

-- Round trip back to datetime
select @d = CONVERT(date, Tm) from AllDay; -- CPU time = 21234 ms,  elapsed time = 22301 ms.
select @d = CAST(Tm - 0.50000004 AS int) from AllDay; -- CPU = 23031 ms, elapsed = 24091 ms.
select @d = DATEDIFF(DAY, 0, Tm) from AllDay; -- CPU = 23782 ms, elapsed = 24818 ms.
select @d = FLOOR(CAST(Tm as float)) from AllDay; -- CPU = 36891 ms, elapsed = 38414 ms.
select @d = CONVERT(VARCHAR(8), Tm, 112) from AllDay; -- CPU = 102984 ms, elapsed = 109897 ms.
select @d = CONVERT(CHAR(8), Tm, 112) from AllDay; -- CPU = 103390 ms,  elapsed = 108236 ms.
select @d = CONVERT(VARCHAR(10), Tm, 101) from AllDay; -- CPU = 123375 ms, elapsed = 135179 ms.

-- Only to another type but not back
select @dd = Tm from AllDay; -- CPU time = 19891 ms,  elapsed time = 20937 ms.
select @di = CAST(Tm - 0.50000004 AS int) from AllDay; -- CPU = 21453 ms, elapsed = 23079 ms.
select @di = DATEDIFF(DAY, 0, Tm) from AllDay; -- CPU = 23218 ms, elapsed = 24700 ms
select @df = FLOOR(CAST(Tm as float)) from AllDay; -- CPU = 29312 ms, elapsed = 31101 ms.
select @dv = CONVERT(VARCHAR(8), Tm, 112) from AllDay; -- CPU = 64016 ms, elapsed = 67815 ms.
select @dv = CONVERT(CHAR(8), Tm, 112) from AllDay; -- CPU = 64297 ms,  elapsed = 67987 ms.
select @dv = CONVERT(VARCHAR(10), Tm, 101) from AllDay; -- CPU = 65609 ms, elapsed = 68173 ms.
GO
set statistics time off;

いくつかのとりとめのない分析

これについてのいくつかのメモ。まず、GROUP BYまたは比較を実行するだけの場合は、に変換し直す必要はありませんdatetime。したがって、表示目的で最終値が必要でない限り、これを回避することでCPUを節約できます。変換されていない値をGROUPBYして、変換をSELECT句にのみ入れることもできます。

select Convert(datetime, DateDiff(dd, 0, Tm))
from (select '2010-09-12 00:00:00.003') X (Tm)
group by DateDiff(dd, 0, Tm)

また、数値変換がに戻るのに少し時間がかかるだけでdatetimevarchar変換がほぼ2倍になることを確認してください。これにより、クエリの日付計算に専念しているCPUの部分が明らかになります。日付の計算を伴わないCPU使用率の部分があり、これは上記のクエリでは19875ミリ秒に近いもののようです。次に、変換には追加の量が必要になるため、変換が2つある場合、その量は約2回使用されます。

その他の検査はに比べてことが明らかになったConvert(, 112)Convert(, 101)(それは長く使用しているため、クエリは、いくつかの追加のCPU費用を持っているvarchar?)に第2の変換バックがあるため、dateへの初期変換限り費用がかからないvarcharで、しかしConvert(, 112)それは近い同じ20000 msCPU基本コスト。

上記の分析に使用したCPU時間の計算は次のとおりです。

     method   round  single   base
-----------  ------  ------  -----
       date   21324   19891  18458
        int   23031   21453  19875
   datediff   23782   23218  22654
      float   36891   29312  21733
varchar-112  102984   64016  25048
varchar-101  123375   65609   7843
  • roundは、に戻る往復のCPU時間ですdatetime

  • singleは、代替データ型(時間部分を削除するという副作用があるもの)への単一の変換のCPU時間です。

  • baseは、single2つの呼び出しの差から減算する計算ですsingle - (round - single)。これは、そのデータ型との間の変換を想定した球場の図でありdatetime、どちらの方向でもほぼ同じです。この仮定は完全ではないようですが、1つの例外を除いて、値はすべて20000ミリ秒に近いため、近いです。

もう1つの興味深い点は、基本コストが単一のConvert(date)方法とほぼ等しいことです(サーバーは、datetimeデータ型の最初の4バイトから整数の日部分を内部的に抽出できるため、コストはほぼ0である必要があります)。

結論

つまり、一方向varchar変換法は約1.8μs、一方向変換法は約0.18μsかかるように見えDateDiffます。これは、25,920,000行で合計18458ミリ秒のテストで最も保守的な「ベースCPU」時間に基づいているため、23218ミリ秒/ 25920000 =0.18μsです。明らかな10倍の改善はかなりのように思えますが、数十万行を処理するまでは率直に言ってかなり小さいです(617k行= 1秒の節約)。

この小さな絶対的な改善があったとしても、私の意見では、このDateAdd方法はパフォーマンスと明快さの最良の組み合わせであるため、勝ちます。の「マジックナンバー」を必要とする答えは、0.50000004いつか誰かを噛むことになるでしょう(5つのゼロまたは6 ???)、さらにそれは理解するのが難しいです。

その他の注意事項

時間があれ0.50000004'12:00:00.003'、に変更して、その動作を確認します。同じdatetime値に変換され、覚えやすいと思います。

興味のある方のために、上記のテストは@@ Versionが以下を返すサーバーで実行されました。

Microsoft SQL Server 2008(RTM)-10.0.1600.22(Intel X86)2008年7月9日14:43:34 Copyright(c)1988-2008 Microsoft Corporation Standard Edition on Windows NT 5.2(Build 3790:Service Pack 2)


1
+1ところで、これをテストしたSQL Serverのバージョンは何ですか?
マーティンスミス

1
あなたが持っているように見えますシングルラウンドテーブル内の後方を。また、char代わりに使用した場合、時間差はありますvarcharか?
Gabe 2010

1
@Gabeありがとう、修正。Charはvarcharとまったく同じように見えます。
ErikE 2010

Oracleにはありselect round(sysdate) from dual、SQLServerには間違いなく必要です。
Denis Valeev 2010

3
@Roman SQL Server 2008以降を使用している場合dateは、上記のテストで示したように、データ型への変換が最も高速です。
ErikE 2013年

30

SQL Server 2008には新しい日付データ型があり、これによりこの問題が次のように単純化されます。

SELECT CAST(CAST(GETDATE() AS date) AS datetime)

1
2018年ではなく0218を年として誤って入力DATEADD(DATEDIFF())したため、時間部分をカットする方法で例外が発生します。私はキャストするときに結果バックdatetime2あなたの方法ではうまく動作しますselect cast(CAST(convert(datetime2(0), '0218-09-12', 120) AS date) as datetime2)
ベルンハルトDöbler

18

Itzik Ben-Gan in DATETIME Calculations、Part 1(SQL Server Magazine、2007年2月)は、このような変換を実行する3つの方法を示しています(最も遅い方法から最も速い方法、2番目と3番目の方法の違いは小さい)。

SELECT CAST(CONVERT(char(8), GETDATE(), 112) AS datetime)

SELECT DATEADD(day, DATEDIFF(day, 0, GETDATE()), 0)

SELECT CAST(CAST(GETDATE() - 0.50000004 AS int) AS datetime)

あなたのテクニック(フロートへのキャスト)は、雑誌の4月号の読者によって提案されています。彼によると、それは上に提示された2番目の技術のそれに匹敵する性能を持っています。


1
私の意見では、フロートへのキャストは最善ではありません。私の答えを見て
ErikE 2010

1
@Emtucifor 3番目の方法は0.50000004の値のために非常にあいまいであることに同意しますが、これが最速の方法であり、テストで確認されています。したがって、可能な限り高速な要件を満たします。
Marek Grzenkowicz 2010

1
@Emtuciforまた、リンクした記事の0.50000004値についての説明は次のとおりです。この式は短いですが(後で説明するように効率的です)、不安を感じると言わざるを得ません。正確な理由を正確に把握できるかどうかはわかりません。おそらく、技術的すぎて、日時関連のロジックが表示されていないためです。
Marek Grzenkowicz 2010

2
この方法を使用する場合は、SELECT CAST(CAST(GETDATE() - '12:00:00.003' AS int) AS datetime)代わりに使用することをお勧めします。これは、私にとって何かを意味し、覚えやすいためです。
ErikE 2010

6
これはSQL2008で最速になりましたConvert(date, GetDate())
ErikE 2011年

12

あなたCAST- FLOOR-CASTすでに少なくともMS SQL Server 2005の上で、最適な方法であると思われます。

私が見た他のいくつかのソリューションにはSelect Convert(varchar(11), getdate(),101)、それらのように文字列変換があり、10倍遅くなります。


1
マイケル・スタムが提案した方法を製品の1つに使用しており、それは魅力のように機能します。
クリス・ロバーツ

3
これは、かなり最適な方法ではありません。この同じページで私の答えを見ください。
ErikE 2010


1

SQL2005:dateaddの代わりにキャストすることをお勧めします。例えば、

select cast(DATEDIFF(DAY, 0, datetimefield) as datetime)

私のデータセットでは、平均して約10%高速です。

select DATEADD(DAY, DATEDIFF(DAY, 0, datetimefield), 0)

(そしてsmalldatetimeへのキャストはさらに高速でした)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.