実際にタイムスタンプである日付範囲をどのように保存しますか
-
03-07-2019 - |
質問
Java <!> amp; Oracleには両方とも、日付と呼ばれる timestamp タイプがあります。開発者はこれらをカレンダーの日付であるかのように操作する傾向があります。
-
基本的な日付数量の場合、入力時に時間部分を切り落とす、つまり精度を下げることができます。ただし、日付範囲(例: 9 / 29-9 / 30 )でこれを行うと、これら2つの値の差は2ではなく1日になります。また、範囲の比較には1 )切り捨て操作:
start < trunc(now) <= end
、または2)算術:start < now < (end + 24hrs)
。恐ろしくありませんが、 DRY ではありません。 -
別の方法は、真のタイムスタンプを使用することです: 9/29 00:00:00- 10/1 00:00:00。 (真夜中から真夜中まで、10月の部分は含まれません)。現在、期間は本質的に正確であり、範囲比較はより簡単です:
start <= now < end
。内部処理では確かにクリーンですが、終了日は最初の入力(+1)で変換する必要があり、出力(-1)で変換する必要があります。これは、ユーザーレベルでカレンダー日付のメタファーを前提としています。
プロジェクトの期間をどのように処理しますか?他の選択肢はありますか?方程式のJava側とOracle側の両方でこれを処理する方法に特に興味があります。
解決
次のようにします。
-
タイムスタンプを使用します。
-
比較のために半開間隔を使用:
start <= now < end
。
BETWEENがSQLの成功に不可欠であると主張する泣き言を無視します。
これにより、一連の日付範囲の監査が非常に簡単になります。 9/30 to 10/1
のデータベース値には1日(9/30)が含まれます。次の間隔の開始は、前の間隔の終了と等しくなければなりません。このinterval[n-1].end == interval[n].start
ルールは監査に便利です。
表示するときに、必要に応じて、フォーマット済みのstart
およびend
-1を表示できます。結局、<!> quot; end <!> quot;を理解するように人々を教育することができます。実際には、ルールが真実ではなくなった最初の日です。 <!> quot; 9/30から10/1 <!> quot;は、<!> quot; 9/30から有効で、10/1 <!> quotから有効ではなくなりました
他のヒント
Oracleの TIMESTAMPデータ型。 DATEデータ型の年、月、日、さらに時間、分、秒、秒の小数値を格納します。
スレッドasktom.oracle.comで日付演算についてを参照してください。
2番目にS.Lottが説明したこと。日付と時間の範囲を広範囲に使用する製品スイートがあり、そのような範囲で動作することを学んだレッスンの1つです。ちなみに、終了日が範囲の一部ではなくなった場合(IOW、半開間隔)、終了日を exclusive 終了日と呼びます。対照的に、時間部分がない場合にのみ意味がある範囲の一部としてカウントする場合、包括的の終了日です。
ユーザーは通常、包括的な日付範囲の入出力を期待します。とにかく、できるだけ早くユーザー入力を排他的な終了日付範囲に変換し、日付範囲をユーザーに表示する必要がある場合はできるだけ遅く変換します。
データベースでは、常に排他的な終了日付範囲を保存します。包括的終了日付範囲を持つレガシーデータがある場合、可能であればデータベース上でそれらを移行するか、データが読み取られたらできるだけ早く排他的終了日付範囲に変換します。
Oracleの日付データ型を使用し、境界条件に影響する時間コンポーネントの問題について開発者を教育します。
また、データベースの制約により、列内の時間コンポーネントが誤って指定されないようにし、オプティマイザーに値に時間コンポーネントがないことを伝えます。
たとえば、制約CHECK(MY_DATE = TRUNC(MY_DATE))は、00:00:00以外の値がmy_date列に配置されるのを防ぎ、OracleがMY_DATE =などの述語を推測できるようにしますTO_DATE( '2008-09-12 15:00:00')は決して真にならず、したがって、次のように展開できるため、テーブルから行が返されません。
MY_DATE = TO_DATE('2008-09-12 15:00:00') AND
TO_DATE('2008-09-12 15:00:00') = TRUNC(TO_DATE('2008-09-12 15:00:00'))
これはもちろん自動的にfalseです。
20080915のような数値として日付を保存したい場合がありますが、これによりクエリ最適化の問題が発生する可能性があります。たとえば、20,071,231から20,070,101の間にいくつの正当な値がありますか? 2007年12月31日から2008年1月1日までの間はどうですか?また、20070100などの不正な値を入力することもできます。
したがって、時間コンポーネントのない日付がある場合、範囲の定義は簡単になります:
select ...
from ...
where my_date Between date '2008-01-01' and date '2008-01-05'
時間コンポーネントがある場合、次のいずれかを実行できます。
select ...
from ...
where my_date >= date '2008-01-01' and
my_date < date '2008-01-06'
または
select ...
from ...
where my_date Between date '2008-01-01'
and date '2008-01-05'-(1/24/60/60)
マジックナンバーの代わりに(1/24/60/60)を使用していることに注意してください。 Oracleでは、1日の定義された小数部を追加して日付の算術演算を実行するのはかなり一般的です... 3時間で3 / 24、27分で27/24/60です。このタイプのOracleの数学は正確であり、丸めエラーが発生しません。
select 27/24/60 from dual;
... 0.01874999999999などではなく0.01875を返します。
間隔データ型がまだ投稿されていません。
Oracleには、正確なシナリオのデータ型もあります。 Oracleには、INTERVAL YEAR TO MONTHおよびINTERVAL DAY TO SECONDデータ型もあります。
10gR2ドキュメントから。
INTERVAL YEAR TO MONTHはピリオドを格納します 年と月を使用して時間の 日時フィールド。このデータ型は 違いを表すのに役立ちます 2つの日時値の間 年と月の値は 重要。
INTERVAL YEAR [(year_precision)] TO 月
year_precisionは次の数です YEAR日時フィールドの数字。の year_precisionのデフォルト値は2です。
第2データ型の間隔日
INTERVAL DAY TO SECONDはピリオドを保存します 日、時間、 分、秒。このデータ型は 正確な表現に役立つ 2つの日時の違い 値。
次のようにこのデータ型を指定します。
INTERVAL DAY [(day_precision)] TO 第二 [(fractional_seconds_precision)]
where
day_precisionは桁数です DAY日時フィールド。受け入れられた 値は0〜9です。デフォルトは2です。
fractional_seconds_precisionは 小数部の桁数 SECOND datetimeフィールドの一部。 受け入れられる値は0〜9です。 デフォルトは6です。
柔軟性が非常に高い 間隔値を リテラル。 <!> quot; Intervalを参照してください リテラル<!> quot;詳細については 間隔値をリテラルとして指定します。 <!> quot; DatetimeとIntervalも参照してください 例<!> quot;使用例 間隔。
私の経験に基づいて、4つの主な方法があります:
1)日付をエポック整数(1970年1月1日からの秒数)に変換し、整数としてデータベースに保存します。
2)日付をYYYYMMDDHHMMSS整数に変換し、整数としてデータベースに保存します。
3)日付として保存
4)文字列として保存
データベースの機能に依存せず、日付を使用して迅速かつ簡単な算術演算を実行できるため、常に1と2を使い続けています。
最初の文に基づいて、隠された<!> quot; features <!> quot;の1つにつまずいています。 Javaの(つまりバグ):java.util.Date
は不変であるべきでしたが、そうではありません。 (Java 7は、これを新しい日付/時刻APIで修正することを約束します。)ほぼすべてのエンタープライズアプリは、さまざまな temporalパターン、ある時点で日付と時刻の算術演算を行う必要があります。
理想的には、Googleカレンダーで使用される Joda時間を使用できます。これができない場合は、Grails / Railsに似た計算方法を使用した<=>のラッパーと、ラッパーの範囲(つまり、時間の開始と終了を示す順序ペア)で構成されるAPIを推測します期間)で十分です。
現在のプロジェクト(HR計時アプリケーション)では、すべての日付をOracleとJavaの両方で同じタイムゾーンに正規化しようとしています。幸いなことに、ローカライズ要件は軽量です(= 1タイムゾーンで十分です)。永続オブジェクトが1日よりも細かい精度を必要としない場合、深夜のタイムスタンプを使用します。私はさらに進んで、永続オブジェクトが許容できる最も粗い粒度まで余分なミリ秒を捨てることを主張します(処理が簡単になります)。
getTime()の結果を長整数として保存することにより、すべての日付をGMTタイムスタンプとして明確に保存できます(つまり、タイムゾーンや夏時間の問題はありません)。
データベースクエリで日、週、月などの操作が必要な場合、およびクエリのパフォーマンスが最重要である場合、タイムスタンプ(ミリ秒よりも高い粒度に正規化)を列を持つ日付内訳テーブルにリンクできます日、週、月などの値に対応しているため、クエリでコストのかかる日付/時刻関数を使用する必要がありません。
アランは正しい。ジョーダの時間は素晴らしい。 java.util.DateとCalendarは残念です。
タイムスタンプが必要な場合は、時刻とともにoracleの日付タイプを使用し、列に_tmstなどの種類の接尾辞を付けます。データをjavaに読み込むと、データをjoda時間のDateTimeオブジェクトに入れます。タイムゾーンが正しいことを確認するには、タイムゾーンとともにタイムスタンプを保存する特定のデータ型がOracleにあることを考慮してください。または、テーブルに別の列を作成して、タイムゾーンIDを保存できます。タイムゾーンIDの値は、タイムゾーンの標準のフルネームIDである必要があります http://java.sun.com/j2se/1.4.2/docs/api/java/util/TimeZone.html#getTimeZone%28java.lang.String% 29 。 TZ dtaに別の列を使用する場合、データをJavaに読み込むときはDateTimeオブジェクトを使用しますが、.withZoneRetainFieldsを使用してDateTimeオブジェクトにタイムゾーンを設定し、タイムゾーンを設定します。
日付データのみ(タイムスタンプなし)が必要な場合は、データベースの日付タイプを時間なしで使用します。もう一度名前を付けてください。この場合、jodatimeのDateMidnightオブジェクトを使用します。
下の行:データベースの型システムと使用している言語を活用します。それらを学び、あなたの問題に対処するための表現力豊かなAPIと言語構文を持つことの利点を享受してください。
UPDATE:Joda-Timeプロジェクトはメンテナンスモードになりました。そのチームは、Javaに組み込まれている java.time クラスへの移行を推奨しています。
Joda-Time
Joda-Timeは、期間を表す3つのクラスを提供しています:間隔、期間、および期間。
ISO 8601標準では、期間および< href = "https://en.wikipedia.org/wiki/ISO_8601#Time_intervals" rel = "nofollow noreferrer">間隔。 Joda-Timeは、このような文字列を解析および生成します。
タイムゾーンは重要な考慮事項です。データベースは日時値をUTCで保存する必要があります。ただし、ビジネスロジックではタイムゾーンを考慮する必要がある場合があります。 <!> quot; day <!> quot;の始まり。タイムゾーンに依存します。ちなみに、3文字または4文字のコードではなく、適切なタイムゾーン名を使用します。
>S.Lottによる正解では、通常は日付に対して最適なハーフオープンロジックを使用することをお勧めします。 -アルバイト。期間の開始は包括的で、終了は排他的です。 Joda-Timeは、そのメソッドでハーフオープンロジックを使用しています。
DateTimeZone timeZone_NewYork = DateTimeZone.forID( "America/New_York" );
DateTime start = new DateTime( 2014, 9, 29, 15, 16, 17, timeZone_NewYork );
DateTime stop = new DateTime( 2014, 9, 30, 1, 2, 3, timeZone_NewYork );
int daysBetween = Days.daysBetween( start, stop ).getDays();
Period period = new Period( start, stop );
Interval interval = new Interval( start, stop );
Interval intervalWholeDays = new Interval( start.withTimeAtStartOfDay(), stop.plusDays( 1 ).withTimeAtStartOfDay() );
DateTime lateNight29th = new DateTime( 2014, 9, 29, 23, 0, 0, timeZone_NewYork );
boolean containsLateNight29th = interval.contains( lateNight29th );
コンソールにダンプ<!>#8230;
System.out.println( "start: " + start );
System.out.println( "stop: " + stop );
System.out.println( "daysBetween: " + daysBetween );
System.out.println( "period: " + period ); // Uses format: PnYnMnDTnHnMnS
System.out.println( "interval: " + interval );
System.out.println( "intervalWholeDays: " + intervalWholeDays );
System.out.println( "lateNight29th: " + lateNight29th );
System.out.println( "containsLateNight29th: " + containsLateNight29th );
実行時<!>#8230;
start: 2014-09-29T15:16:17.000-04:00
stop: 2014-09-30T01:02:03.000-04:00
daysBetween: 0
period: PT9H45M46S
interval: 2014-09-29T15:16:17.000-04:00/2014-09-30T01:02:03.000-04:00
intervalWholeDays: 2014-09-29T00:00:00.000-04:00/2014-10-01T00:00:00.000-04:00
lateNight29th: 2014-09-29T23:00:00.000-04:00
containsLateNight29th: true
すべての日付をミリ秒単位で保存しています。タイムスタンプ/日付時刻フィールドをまったく使用しません。
だから、私はそれを長く操作しなければなりません。これは、SQLクエリで「前」、「後」、「今」のキーワードを使用しないことを意味します。