web-dev-qa-db-ja.com

「ファジー日付」をどのようにデータベースに保存しますか?

これは私が何度か遭遇した問題です。データベーステーブルに保存するレコードがあるとします。このテーブルには、「date_created」というDateTime列があります。この特定のレコードはかなり前に作成されたものであり、正確な日付はよくわかりませんが、年と月はわかっています。あなたがちょうど年を知っている他のレコード。あなたが日、月、年を知っている他のレコード。

「1978年5月」は有効な日付ではないため、DateTimeフィールドは使用できません。複数の列に分割すると、クエリを実行できなくなります。他の誰かがこれに遭遇しましたか?

私が構築しているシステムを明確にするために、それはアーカイブを追跡するシステムです。一部のコンテンツはずっと前に作成されたもので、私たちが知っているのは「1978年5月」だけです。 1978年5月1日として保存することもできましたが、この日付は月にのみ正確であることを示すためのいくつかの方法があります。そうすれば、数年後、そのアーカイブを取得するときに、日付が一致しなくても混乱しません。

私の目的では、「1978年5月の不明な日」を「1978年5月1日」と区別することが重要です。また、ほとんどのデータベースシステムは無効な日付値としてそれを拒否するため、「1978年5月0日」のように、未知数を0として保存したくありません。

129
nbv4

すべての日付をデータベースの通常のDATEフィールドに格納し、追加の精度フィールドに、実際のDATEフィールドの精度を追加します。

date_created DATE,
date_created_accuracy INTEGER, 

date_created_accuracy:1 =正確な日付、2 =月、3 =年。

日付が曖昧な場合(たとえば、1980年5月)、期間の開始時に(たとえば、1980年5月1日)に保存します。または、日付が年(1980など)に対して正確な場合は、1月1日として保存します。 1980と対応する精度の値。

この方法では、やや自然な方法で簡単にクエリを実行できますが、日付がどれほど正確であるかについての概念がまだあります。たとえば、これにより、Jan 1st 1980およびFeb 28th 1981、あいまいな日付を取得1980およびMay 1980

150
Juha Syrjälä

この種のデータを通常の日時情報として使用する必要がない場合は、単純な文字列フォーマットで十分です。

ただし、すべての機能を維持する必要がある場合は、2つの回避策が考えられます。どちらもデータベースに格納されている追加情報が必要です。

  1. 作成min dateおよびmax dateフィールド。「不完全な」データの値は異なりますが、正確な日付では一致します。
  2. 不正確な日付の種類ごとにタイプを作成します(なし_ 0、date_missing _ 1、month_missing _ 2、year_missing_4など_を組み合わせて、それらを組み合わせることができます)。 typeフィールドをレコードに追加し、欠落している情報を保持します。
27
superM

これは実際には技術的な問題というよりも要件の定義です。焦点を当てる必要があるのは「過去の日付をどのように定義できるか」であり、技術的な解決策が流れます。

私がこのようなものにアプローチしなければならなかった時間は、私たちが通常持ってきました:

  • マッピング方法を定義します- MichaelTの提案のように 、月/日として定義されているものはすべて、その月の1日の午前0時として定義されることを決定します。これは通常、ほとんどの目的で十分です。正確な日付がそれほど重要である場合、おそらく35年後の記録があります。
  • これを追跡する必要があるかどうかを判断します-IE、作成日がわずかに作成されたレコードには、そのように言うフラグが必要ですか?または、それはユーザートレーニングの問題だけなので、人々はそれを理解し、それに応じて行動できます。

場合によっては、日付をあいまいにするなどの操作が必要になることがあります。たとえば、1つの日付が1978年5月のクエリに応答する必要がある場合があります。これは実行可能です。create_date2フィールドを作成するだけで、古いレコードは30になります日は適切に分散し、新しい日は2つの同じ値を取得します。

20
Wyatt Barnett

日付が正確かどうかを示す最も簡単な方法は、デフォルトのNULLで精度フィールドINT(1)を作成することです

日付が正確な場合、「date_created」に日時を格納し、正確さはNULLのままにします。

日付が月に対してのみ正確である場合、日付時刻を正確な値で月の1日として保存します1

日付が1年の1月1日のストアの日付と時刻の精度のみで正確な場合2

異なる数値を使用して、第1四半期などの異なる値を保持できます

18
david strachan

過去には、正確な日付を開始日と終了日として保存していました。 may21,2012の日は、start = 12 am,may21,2012およびend = 12 am,may22,2012として表されます。 2012年は、start = 12 am,Jan1,2012 end = 12 am,Jan1,2013として表されます。

このアプローチをお勧めするかどうかはわかりません。ユーザーに情報を表示するときは、特定の2つのエンドポイント(夏時間などに対応する)ではなく、「5月25日」を表示するために、日付範囲が1日を正確にカバーしていることを正しく検出する必要があります。

ただし、人間に翻訳しようとしない場合、エンドポイントを使用したプログラミングは、center + accuracyよりもはるかに簡単です。あなたは多くのケースに終わるわけではありません。それはかなりいいです。

17
Craig Gidney

2つの日付を保存しませんか。

Created_AfterおよびCreated_Before。 「以降に作成された」および「以前に作成された」実際のセマンティクス

したがって、正確な日付がわかっている場合、Created_AfterとCreated_Beforeは同じ日付になります。

2000年5月の最初の週であることがわかっている場合は、Created_After = '2000-05-01'およびCreated_Before = '2000-05-07'です。

1999年5月だけを知っている場合、値は「1999-05-01」と「1999-05-30」になります。

「Summer of '42」の場合、値は「1942-06-01」と「1942-08-31」になります。

このスキーマは、通常のSQLでクエリを実行するのが簡単で、技術者以外のユーザーでも簡単にフォローできます。

たとえば、2001年5月に作成されたmightであるすべてのドキュメントを検索するには:

SELECT * FROM DOCTAB WHERE Created_After < '2001-05-31' And Created_Before > 2001-05-01;

逆に、2001年5月に作成された間違いなくであるすべてのドキュメントを検索するには、次のように入力します。

SELECT * FROM DOCTAB WHERE Created_After > '2001-05-01' And Created_Before < 2001-05-31;
14
James Anderson

ISO 8601 日時形式には期間の定義が付属しています。

2012-01-01P1M(読み取り:2012年1月1日、期間:1か月)は、「2012年1月」であるべきです。

これを使用して、データをstoreします。そのためには、String型のデータベースフィールドが必要になる場合があります。それについて賢明な検索を行う方法は別のトピックです。

10
Matthias Ronge

別のオプションは、YYYYMMDDの形式の整数として日付を格納することです。

  • あなたは年が1951年であることだけ知っています:19510000として保存
  • あなたは月と年が1951年3月であることを知っています:19510300として保存
  • あなたは完全な日付が1951年3月14日であることを知っています:19510314として保存
  • 完全に不明な日付:0として保存

利点

他の多くの回答が示唆するように、2つの日付フィールドまたは日付と精度ではなく、1つのフィールドにファジー日付を格納できます。

クエリはまだ簡単です:

  • 1951年のすべてのレコード-SELECT * FROM table WHERE thedate>=19510000 and thedate<19520000
  • 1951年3月のすべてのレコード-SELECT * FROM table where thedate>=19510300 and thedate<19510400
  • 1951年3月14日のすべてのレコード-SELECT * FROM table where thedate=19510314

ノート

  • GUIには、実装が非常に簡単なGetDateString(int fuzzyDate)が必要です。
  • ソートはint形式で簡単です。未知の日付が最初に来ることを知っておくべきです。これを逆にするには、月または日の99ではなく、「パディング」に00を使用します。
3
Rick

複数の列に分割すると、クエリを実行できなくなります。

誰が言ったのですか?ここではあなたが何をすべきかです:

  1. 3つの列、Day、Month、Year、それぞれのint型、および4番目の列のDateTime型のTheDateがあります。
  2. TheDateがnullのままで、1つ以上の日、月、年のフィールドに値がある場合、日、月、年の3つの列を使用してTheDateを構築するトリガーがあります。
  3. TheDateが指定されているが、これらのフィールドが指定されていない場合に、日、月、年のフィールドにデータを入力するトリガーを用意します。

したがって、insert into thistable (Day, Month, Year) values (-1, 2, 2012);のような挿入を行うと、TheDateは2013年2月1日になりますが、Dayフィールドが-1であるため、実際は2012年2月の不確定な日付になります。

I insert into thistable (TheDate) values ('2/5/2012');の場合、日は5、月は2、年は2012となり、どれも-1ではないため、これが正確な日付であることがわかります。

挿入/更新トリガーにより、3つのフィールド(日、月、年)が常にクエリ可能なTheDateのDateTime値を生成するので、クエリ機能を失うことはありません。

3
junk

一般的に、私はまだそれらを一般的なクエリビジネスへの日付として格納しますが、少し正確でなくてもまだ可能です。

過去に精度を知ることが重要な場合は、精度の「ウィンドウ」を+/- 10進数またはルックアップ(日、月、年など)として保存しました。ウィンドウの代わりに、元の日付の値を文字列として保存し、できる限り日付時刻に変換します。例として、1978-05-01 00:00:00と「1978年5月」を使用します。

3
Bill

ISO 8601では、「ファジー日付」の構文も指定されています。 2012年2月12日午後3時は「2012-02-12T15」となり、2012年2月は単に「2012-02」となります。これは、標準的な辞書式ソートを使用してうまく拡張されます。

$ (echo "2013-03"; echo "2013-03"; echo "2012-02-12T15"; echo "2012-02"; echo "2011") | sort
2011
2012
2012-02
2012-02-12T15
2013-03
1
AnAnswer

これが私の見解です:

ファジー日付から日付時刻オブジェクトに移動します(データベースに収まります)

import datetime
import iso8601

def fuzzy_to_datetime(fuzzy):
    flen = len(fuzzy)
    if flen == 4 and fuzzy.isdigit():
        dt = datetime.datetime(year=int(fuzzy), month=1, day=1, microsecond=111111)

    Elif flen == 7:
        y, m = fuzzy.split('-')
        dt = datetime.datetime(year=int(y), month=int(m), day=1, microsecond=222222)

    Elif flen == 10:
        y, m, d = fuzzy.split('-')
        dt = datetime.datetime(year=int(y), month=int(m), day=int(d), microsecond=333333)

    Elif flen >= 19:
        dt = iso8601.parse_date(fuzzy)

    else:
        raise ValueError("Unable to parse fuzzy date: %s" % fuzzy)

    return dt

次に、datetimeオブジェクトを取り、それをファジー日付に戻す関数。

def datetime_to_fuzzy(dt):
    ms = str(dt.microsecond)
    flag1 = ms == '111111'
    flag2 = ms == '222222'
    flag3 = ms == '333333'

    is_first = dt.day == 1
    is_jan1 = dt.month == 1 and is_first

    if flag1 and is_jan1:
        return str(dt.year)

    if flag2 and is_first:
        return dt.strftime("%Y-%m")

    if flag3:
        return dt.strftime("%Y-%m-%d")

    return dt.isoformat()

そしてユニットテスト。どんなケースも見逃しましたか?

if __name__ == '__main__':
    assert fuzzy_to_datetime('2001').isoformat() == '2001-01-01T00:00:00.111111'
    assert fuzzy_to_datetime('1981-05').isoformat() == '1981-05-01T00:00:00.222222'
    assert fuzzy_to_datetime('2012-02-04').isoformat() == '2012-02-04T00:00:00.333333'
    assert fuzzy_to_datetime('2010-11-11T03:12:03Z').isoformat() == '2010-11-11T03:12:03+00:00'

    exact = datetime.datetime(year=2001, month=1, day=1, microsecond=231)
    assert datetime_to_fuzzy(exact) == exact.isoformat()

    assert datetime_to_fuzzy(datetime.datetime(year=2001, month=1, day=1, microsecond=111111)) == '2001'
    assert datetime_to_fuzzy(datetime.datetime(year=2001, month=3, day=1, microsecond=222222)) == '2001-03'
    assert datetime_to_fuzzy(datetime.datetime(year=2001, month=6, day=6, microsecond=333333)) == '2001-06-06'

    assert datetime_to_fuzzy(fuzzy_to_datetime('2002')) == '2002'
    assert datetime_to_fuzzy(fuzzy_to_datetime('2002-05')) == '2002-05'
    assert datetime_to_fuzzy(fuzzy_to_datetime('2002-02-13')) == '2002-02-13'
    assert datetime_to_fuzzy(fuzzy_to_datetime('2010-11-11T03:12:03.293856+00:00')) == '2010-11-11T03:12:03.293856+00:00'

2001-01-01T00:00:00.333333で正確に発生したイベントがシステムで "2001"と解釈されるというまれなケースがありますが、それは非常にありそうにありません。

0
nbv4

私は、多くの古い本を扱っている出版会社で働いています。この本では、物事の正確な日付を取得できないことがよくあります。通常、特定の日付エントリには2つのフィールド、日付と circa ブール値があります。

date date
dateCirca enum('Y', 'N')

日付フィールドを使用して、イベントの日付、または実際の日付がわからない場合は「十分に近い」日付を示します。実際の日付がわからない場合は、dateCircaフィールドをYとしてマークし、「1日」としてマークされた十分に近い日付を指定します。

1st March, 2013  // We don't know the day of the month
1st January, 2013  // We don't know the month/day of the year
1st January, 2000  // We don't know the month/day/year, we only know the century
0
user7007

概観

ファジー日付時刻(またはファジー日付のみ)を格納するための多くの可能な表現、つまりデータベーススキーマがあります。

  1. 日時とその精度または正確さを示すコード
  2. 間隔を表すいくつかの可能性がある日時と間隔:
    1. すべての間隔を、固定単位の整数(または他の数値)量として表します。日、分、ナノ秒。
    2. 間隔を整数(またはその他の数値)数量とその単位を示すコードの両方で表します。
  3. 開始日時と終了日時
  4. ストリング
  5. 確率分布:
    1. 特定のファミリーの特定の分布を指定するパラメーターの10進数または浮動小数点の量。正規分布の平均と標準偏差。
    2. 確率分布関数、例えば(ルックアップ)コード(特定の値のパラメーターを持つ可能性がある)として、または十分に表現力のある言語、形式、または表現の式として。

[1]、[2]、および[3]はすべて(暗黙的に)均一な間隔です。つまり、(等しい)可能な時点のセットです。

[4]は最も表現力があります。つまり、可能な(または少なくとも任意に長い)書き言葉の文章やフレーズを許可する場合です。しかし、それはまた、最も扱いにくいものでもあります。限界では、人間レベルのAIは任意の値を処理する必要があります。実際には、可能な値の範囲は厳しく制限する必要があり、代替の「構造化」値がおそらく多くの操作に好まれます。並べ替え、検索。

[5]はおそらく最も一般的なcompact表現であり、(ある程度)実用的です。

均一な間隔

一定間隔は、(可能な)日時値のセットを表す最も簡単でコンパクトな方法です。

[1]の場合、日時値の一部は無視されます。つまり、指定された精度または精度よりも細かい単位に対応する部分です。それ以外の場合、これは[2]と同等であり、精度/精度コードは、同じ単位(および暗黙の数量1)の区間と同等です。

[2]と[3]は表現的に同等です。 [1]では表現できない有効な間隔があるため、[1]はどちらよりも厳密には表現力がありません。日付の境界にまたがる12時間間隔に相当するあいまいな日時。

[1]は、ユーザーが他のどの表現よりも入力するのが簡単であり、一般に(少なくともわずかに)入力を少なくする必要があります。日時をさまざまなテキスト表現で入力できる場合。 「2013」、「2014-3」、「2015-5-2」、「7/30/2016 11p」、「2016-07-31 18:15」、精度または精度は入力から自動的に推測することもできます。

[1]の精度や精度も、ユーザーに伝えられる形式に変換するのが最も簡単です。 「2015-5月の精度」から「2015年5月」、「2015年5月13日2p、プラスまたはマイナス13.5日」(後者は[1]で表すことはできません)。

文字列

実際には、文字列値は、クエリ、並べ替え、またはその他の方法で複数の値を比較するために、他の表現に変換する必要があります。そのため、記述された自然(人間)言語は[1]、[2]、[3]、または[5]よりも厳密に表現力がありますが、標準のテキスト表現またはフォーマットをはるかに超える方法はまだありません。それを考えると、これはおそらく最も有用でない表現それ自体です。

この表現の利点の1つは、実際には値をそのままユーザーに提示でき、変換を容易に理解できるようにする必要がないことです。

確率分布

確率分布は、等間隔表現[1]、[2]、[3]を一般化し、(おそらく)(一般的な)文字列表現[4]と同等です。

文字列に対する確率分布の利点の1つは、前者が明確であることです。

[5-1]は、既存の分布に(ほぼ)適合する値に適しています。測定値が特定の分布に適合することがわかっている(または考えられている)デバイスから出力された日時値。

[5-2]はおそらくコンパクトが任意の「ファジー日時」値を表すための最良の(やや)実用的な方法です。もちろん、特定の確率分布の計算可能性は問題を使用しており、さまざまな値のクエリ、並べ替え、または比較の際に解決すべき興味深い(そしておそらく不可能)問題がありますが、これの多くはおそらく既知であるか、既存のどこかで解決されています数学的および統計的な文献なので、これは間違いなく非常に一般的で明確な表現と言えます。

0
Kenny Evitt