2つの日付間の日数を簡単に算出する
こんにちは、Ted:
私の会社のデータベースでは、日付を一般的なCYYMMDD形式で7桁のパック10進数の値として格納しています。
2018年のうちに、2つの日付の間の日数を報告するSQLクエリーを書いたのですが、2019年の日付を使用し始めたとたんに正確な計算が行われなくなりました。2つの日付間の差分を算出する正しい方法を教えてもらえないでしょうか。
-Becki
Beckiさんのおっしゃるような「正しい方法」が存在するのかどうかは分かりませんが、必要とされる計算を行う方法をご紹介することは可能です。
SQLには、そのような問題に対処するいくつかの便利な組み込み関数があります。
DAYS関数は、日付の序数値を返します。ある日付の序数値を、もう一方の日付の序数値から差し引けば、経過日数を得ることができます。
DAYSには日付を指定する必要があるため、7桁の数値を日付データ型に変換する必要があります。
そのためには、TIMESTAMP_FORMAT関数またはそのエイリアス、TO_DATEを使用します。
そして、TIMESTAMP_FORMATには文字引数を指定する必要があるため、CHARを使用して、数値を文字テキストに変換します。
まず、テスト データを使用してみましょう。
declare global temporary table DateDiffs
(Key dec(3), DueDate dec(7), ShipDate dec(7));
insert into session.DateDiffs values
( 1 , 1181231 , 1190101),
( 2 , 1190101 , 1190101),
( 3 , 1190101 , 1181231),
( 4 , 1180506 , 1190506),
( 5 , 1190506 , 1180506),
( 6 , 1190201 , 1190301),
( 7 , 1200201 , 1200301);
以下は、CHAR、TIMESTAMP_FORMAT、およびDAYS関数を使用したクエリーです。
select dd.*,
days(timestamp_format(char(ShipDate+19000000),'YYYYMMDD')) -
days(timestamp_format(char(DueDate +19000000),'YYYYMMDD'))
from session.DateDiffs as dd
キー | 期限 | 出荷日 | 遅延日数 |
---|---|---|---|
1 | 1181231 | 1190101 | 1 |
2 | 1190101 | 1190101 | 0 |
3 | 1190101 | 1181231 | -1 |
4 | 1180506 | 1190506 | 365 |
5 | 1190506 | 1180506 | -365 |
6 | 1190201 | 1190301 | 28 |
7 | 1200201 | 1200301 | 29 |
正しく計算されているでしょうか。間違いはないと思われます。
私が作成したFMTDATE関数を使用すると、このクエリーを少しシンプルにすることができます。
select dd.*,
days(date(fmtdate(ShipDate,'CYMD', 'ISO-'))) -
days(date(fmtdate(DueDate, 'CYMD', 'ISO-')))
from session.DateDiffs as dd;
ただし、私があなたの立場だったら、もう一歩足を踏み出してみることでしょう。
私だったら、日付計算を実行する関数を書きます。
それらの複雑なネスト状の組み込み関数をすべて取り除いてくれるものです。
関数の作成は難しくありません。まず、2つの日付の間の日数を返すRPGサブプロシージャーを書くことから始めます。
**free
ctl-opt actgrp(*new);
dcl-s FromDate packed(7) inz(1190101);
dcl-s ThruDate packed(7) inz(1190201);
dcl-s Days packed(7);
*inlr = *on;
Days = Days_after (ThruDate: FromDate);
return;
dcl-proc Days_after;
dcl-pi Days_after packed(7);
ThruDate packed(7) const;
FromDate packed(7) const;
end-pi Days_after;
monitor;
return %diff( %date(ThruDate: *cymd):
%date(FromDate: *cymd): *days);
on-error;
// fixme
endmon;
end-proc;
重要なのはDAYS_AFTERサブプロシージャーです。
それよりの前のメインのコードは、テストのためだけのものです。
後で削除すればよいでしょう。
このサブプロシージャーが適切な結果をもたらすことを確認したら、SQLで必要となるパラメーター リストを追加します。
**free
ctl-opt actgrp(*new);
dcl-s FromDate packed(7) inz(1190101);
dcl-s ThruDate packed(7) inz(1190201);
dcl-s Days packed(7);
dcl-s ThruDateNull int ( 5);
dcl-s FromDateNull int ( 5);
dcl-s ReturnValNull int ( 5);
dcl-s ReturnState char ( 5);
dcl-s FunctionName varchar ( 517);
dcl-s SpecificName varchar ( 128);
dcl-s MessageText varchar (1000);
*inlr = *on;
Days_after (ThruDate: FromDate: Days:
ThruDateNull: FromDateNull: ReturnValNull:
ReturnState:
FunctionName: SpecificName: MessageText);
return;
dcl-proc Days_after;
dcl-pi Days_after;
ThruDate packed ( 7);
FromDate packed ( 7);
ReturnValue packed ( 7);
ThruDateNull int ( 5);
FromDateNull int ( 5);
ReturnValNull int ( 5);
ReturnState char ( 5);
FunctionName varchar ( 517);
SpecificName varchar ( 128);
MessageText varchar (1000);
end-pi Days_after;
ReturnState = *zeros;
clear MessageText;
monitor;
ReturnValue = %diff( %date(ThruDate: *cymd):
%date(FromDate: *cymd): *days);
on-error;
ReturnState = '85555';
MessageText = 'A decimal value could not be converted to a date.';
endmon;
end-proc;
SQLパラメーター スタイルは、エラーがあった場合に簡単にSQL状態を設定する方法を提供します。
ON-ERRORの下に追加したエラー処理コードに注目してください。
ここではSQL状態を02000より大きい値に設定し、メッセージ テキストを指定しています。
サブプロシージャーが新たなパラメーター リストで正常に動作したら、メインの計算コードを削除して、このコードをモジュールに変換します。
**free
ctl-opt nomain;
dcl-proc Days_after export;
dcl-pi Days_after;
ThruDate packed ( 7);
FromDate packed ( 7);
ReturnValue packed ( 7);
ThruDateNull int ( 5);
FromDateNull int ( 5);
ReturnValNull int ( 5);
ReturnState char ( 5);
FunctionName varchar ( 517);
SpecificName varchar ( 128);
MessageText varchar (1000);
end-pi Days_after;
ReturnState = *zeros;
ReturnValNull = *zeros;
clear MessageText;
monitor;
ReturnValue = %diff( %date(ThruDate: *cymd):
%date(FromDate: *cymd): *days);
on-error;
ReturnState = '85555';
MessageText = 'A decimal value could not be converted to a date.';
endmon;
end-proc;
モジュールおよびサービス プログラムを作成します。
CRTRPGMOD MODULE(xxx/DAYS_AFTER)
SRCFILE(xxx/QRPGLESRC)
SRCMBR(DAYS_AFTER)
CRTSRVPGM SRVPGM(xxx/DAYS_AFTER)
MODULE(xxx/DAYS_AFTER)
EXPORT(*ALL)
サービス プログラムでサブプロシージャーを実行する関数を作成します。
create or replace function xxx.days_after
(ThruDate dec(7), FromDate dec(7))
returns dec(7)
language rpgle
parameter style sql
deterministic
returns null on null input
external name 'XXX/DAYS_AFTER(DAYS_AFTER)'
これで、作成した便利な関数を使用できるようになります。
select dd.*, days_after(ShipDate, DueDate) as DaysLate
from session.DateDiffs as dd
いかがでしょうか。
RPGサブプロシージャーを書くことができるのであれば(できるのですが)、SQL関数を書くことができるというわけです。