web-dev-qa-db-ja.com

DateTimeを保存する好ましい方法

日付と時刻の情報は、いくつかの方法で保存できます。日時情報を保存するための最良の方法は何ですか?

2つの別々の列またはDateTimeを使用して1つの列に日付と時刻を格納しますか?

そのアプローチが優れている理由を説明できますか?

(参照用のMySQLドキュメントへのリンク、質問はMySQLに固有ではなく一般的なものです)
日付と時刻のタイプ: 日付と時刻

19
Julian

データは密接にリンクされているため、データを単一の列に格納することをお勧めします。ある時点は、2つの情報ではなく、1つの情報です。

多くの製品で「舞台裏」で採用されている日付/時刻データを格納する一般的な方法は、それを10進数値に変換することです。「日付」は10進数値の整数部分で、「時刻」は小数値。したがって、1900-01-01 00:00:00は0.0として格納され、2016年9月20日9:34:00は42631.39861として格納されます。 42631は、1900-01-01からの日数です。 .39861は、真夜中から経過した時間の一部です。これを行うために10進数型を直接使用しないでください。明示的な日付/時刻型を使用してください。ここでの私のポイントは単なる例です。

データを2つの別々の列に保存するということは、特定の時点が保存された値よりも早いか遅いかを確認したい場合はいつでも、両方の列の値を組み合わせる必要があるということです。

値を個別に格納すると、常に検出が困難な「バグ」に遭遇します。次の例を見てください:

IF OBJECT_ID('tempdb..#DT') IS NOT NULL
DROP TABLE #DT;
CREATE TABLE #DT
(
    dt_value DATETIME NOT NULL
    , d_value DATE NOT NULL
    , t_value TIME(0) NOT NULL
);


DECLARE @d DATETIME = '2016-09-20 09:34:00';

INSERT INTO #DT (dt_value, d_value, t_value)
SELECT @d, CONVERT(DATE, @d), CONVERT(TIME(0), @d);

SET @d = '2016-09-20 11:34:00';

INSERT INTO #DT (dt_value, d_value, t_value)
SELECT @d, CONVERT(DATE, @d), CONVERT(TIME(0), @d);

/* show all rows with a date after 2016-07-01 11:00 am */
SELECT *
FROM #DT dt
WHERE dt.dt_value >= '2016-07-01 11:00:00';

/* show all rows with a date after 2016-07-01 11:00 am */
SELECT *
FROM #DT dt
WHERE dt.d_value >= CONVERT(DATE, '2016-07-01')
    AND dt.t_value >= CONVERT(TIME(0), '11:00:00');

上記のコードでは、テストテーブルを作成し、2つの値を入力してから、そのデータに対して単純なクエリを実行しています。最初のSELECTは両方の行を返しますが、2番目のSELECTは単一の行のみを返します。これは望ましい結果ではない可能性があります。

enter image description here

コメントの@ypercubeで指摘されているように、値が個別の列にある日付/時刻範囲をフィルタリングする正しい方法は次のとおりです。

WHERE dt.d_value > CONVERT(DATE, '2016-07-01') /* note there is no time component here */
    OR (
        dt.d_value = CONVERT(DATE, '2016-07-01') 
        AND dt.t_value >= CONVERT(TIME(0), '11:00:00')
    )

時間コンポーネントを分離する必要がある場合分析用の目的で、値の時間部分に計算された永続的な列を追加することを検討できます。

ALTER TABLE #DT
ADD dt_value_time AS CONVERT(TIME(0), dt_value) PERSISTED;

SELECT *
FROM #dt;

enter image description here

次に、永続化された列にインデックスを付けて、時刻による高速ソートなどを可能にします。

表示のために日付と時刻を2つのフィールドに分割することを検討している場合、フォーマットはサーバーではなくクライアントで行う必要があることを理解する必要があります。

23
Max Vernon

他の答えには反対意見を述べます。

日付と時刻の両方のコンポーネントが一緒に必要な場合、つまりエントリに一方が含まれ、他方が含まれていない場合(または一方がNULLである場合)が無効である場合、単一の列に格納することは、他の理由で意味があります。答えます。

ただし、1つまたは両方のコンポーネントが個別にオプションである場合もあります。その場合、単一の列に格納するのは正しくありません。これを行うと、NULL値を任意の方法で表現する必要があります。時間を00:00:00として保存します。

次に例をいくつか示します。

  • あなたはマイレージ税控除のために車の旅を記録しています。正確な移動時間を知ることは有用ですが、従業員がメモしておかずに忘れてしまった場合でも、日付を記録する必要があります(必須の日付、任意の時間)。

  • あなたは人々が昼食を食べる時間を見つけるために調査を実施していて、日付を含む彼らの昼食時間のサンプルをフォームに記入するよう参加者に依頼します。一部の人は日付を入力しなくてもよく、それは本当に気になる時間(オプションの日付、必要な時間)であるため、データを破棄したくありません。

代替アプローチについては、 この関連する質問 を参照してください。

11
JBentley

特定のビジネス/アプリケーションの要求がない限り、常に単一の列として格納することを好みます。以下が私のポイントです-

  • タイムスタンプから時間を抽出することは問題ではありません
  • 両方を一緒に保存できるのに、時間のためだけに列を追加する理由
  • クエリを実行するたびに日付と時刻を追加しないようにするため。
5
Ashwini Mohan

SQL Serverでは、DataTimeを1つのフィールドとして格納するのが最適です。 DataTime列にインデックスを作成すると、日付検索および日付時刻検索として使用できます。したがって、特定の日付に存在するすべてのレコードを制限する必要がある場合でも、特別なことをしなくてもインデックスを使用できます。時間部分をクエリする必要がある場合は、同じインデックスを使用できません。したがって、DateTimeよりも時間の方を重視するビジネスケースがある場合は、作成する必要があるので、個別に保存する必要があります。それにインデックスを付け、パフォーマンスを向上させます。

3

実際、これは残念です。これには、標準のクロスDBMSタイプがありません(INTやVARCHARは整数と文字列値用です)。これまでに出会った2つのクロスデータベースアプローチは、VARCHAR/CHAR列を使用して、DataTime値をISO 8601(より便利で人間が読める)標準に従ってフォーマットされた文字列として格納し、BIGINTを使用してPOSIXタイムスタンプ(より多く格納)として格納します効率的、高速、数学的に操作しやすい)。

1
Ivan

たくさんのものを読んだ後、BIGINTでのUTC Unix時間は最適なソリューションのようです。 [〜#〜] tzdb [〜#〜] 必要に応じてタイムゾーンストレージ用のVARCHARのtimesone ID。いくつかの引数:

  1. TIMESTAMPとDATETIMEは、複雑で明確ではないように見える、バックグラウンドで多くのギミックな変換を行います。サーバーが現地時間からUTCに、またはサーバー時間に切り替わったり、時々切り替わったりします。すべての機能についての隠れたオーバーヘッドの束。

  2. BIGINT(8kb)は、少なくともxxxxxx.xxxxxx形式のストレージに必要なDECIMALと少なくとも同じかそれより軽い 実際には2つのINT + MySQLによって何か です。そして、何世紀も先に保存するのに十分です。

  3. ほとんどすべての主要なプログラミング言語には、Unix時間で動作する標準関数のライブラリがあります。

  4. BIGINTを使用した数学演算は、どのハードウェアでも他の何よりも高速または高速である必要があります。

もちろん、上記のすべては大きな国際プロジェクトに関連しています。小さなものについては、選択されたフレームワークのデフォルトのフォーマットで十分です。

0
Arthur Tarasov