これを行うには、Oracle で Oracle Analytics、つまり「OVER ... PARTITION BY」句を使用できます。PARTITION BY 句は GROUP BY に似ていますが、集計部分はありません。つまり、行をグループ化 (つまり、パーティション化) し、別のグループとして操作を実行できます。各行を操作すると、上の前の行の列にアクセスできます。これが PARTITION BY が提供する機能です。(PARTITION BY は、パフォーマンスのためのテーブルのパーティション分割とは関係ありません。)
では、重複しない日付をどのように出力するのでしょうか? 最初に (ID,DFROM) フィールドに基づいてクエリを並べ替え、次に ID フィールドを使用してパーティション (行グループ) を作成します。次に、次のような式を使用して、前の行の TO 値と現在の行の FROM 値が重複しているかどうかをテストします (疑似コード)。
max(previous.DTO, current.DFROM) as DFROM
この基本式は、重複していない場合は元の DFROM 値を返しますが、重複がある場合は前の TO 値を返します。行は順序付けされているため、最後の行のみを考慮する必要があります。前の行が現在の行と完全に重なっている場合、その行の日付範囲を「ゼロ」にする必要があります。したがって、DTO フィールドに対しても同じことを行い、次を取得します。
max(previous.DTO, current.DFROM) as DFROM, max(previous.DTO, current.DTO) as DTO
調整された DFROM と DTO の値を使用して新しい結果セットを生成したら、それらを集計して DFROM と DTO の範囲間隔をカウントできます。
データベースのほとんどの日付計算は、データのように包括的ではないことに注意してください。したがって、DATEDIFF(dto,dfrom) のようなものには、dto が実際に参照する日が含まれないため、最初に dto を 1 日上に調整する必要があります。
もう Oracle サーバーにアクセスすることはできませんが、Oracle Analytics を使用すればこれが可能であることはわかっています。クエリは次のようになります: (これが機能するようになったら、私の投稿を更新してください。)
SELECT id,
max(dfrom, LAST_VALUE(dto) OVER (PARTITION BY id ORDER BY dfrom) ) as dfrom,
max(dto, LAST_VALUE(dto) OVER (PARTITION BY id ORDER BY dfrom) ) as dto
from (
select id, dfrom, dto+1 as dto from my_sample -- adjust the table so that dto becomes non-inclusive
order by id, dfrom
) sample;
ここでの秘密は、現在の行の前の値を返すLAST_VALUE(dto) OVER (PARTITION BY id ORDER BY dfrom)式です。したがって、このクエリは、重複しない新しい dfrom/dto 値を出力する必要があります。次に、これを実行して (dto-dfrom) サブクエリを実行し、合計を合計するだけです。
MySQL の使用
私はmysqlサーバーにアクセスできたので、そこで動作させました。MySQL には Oracle のような結果分割 (分析) がないため、結果セット変数を使用する必要があります。これは、@var:=xxx 型の式を使用して最後の日付の値を記憶し、それに応じて dfrom/dto を調整することを意味します。同じアルゴリズムが少し長く、より複雑な構文です。また、ID フィールドが変更されるたびに、最後の日付の値を忘れる必要があります。
サンプルテーブルは次のとおりです(同じ値です):
create table sample(id int, dfrom date, dto date, networkDay int);
insert into sample values
(1,'2012-09-03','2012-09-07',5),
(1,'2012-09-03','2012-09-04',2),
(1,'2012-09-05','2012-09-06',2),
(1,'2012-09-06','2012-09-12',5),
(1,'2012-08-31','2012-09-04',3),
(2,'2012-09-04','2012-09-06',3),
(2,'2012-09-11','2012-09-13',3),
(2,'2012-09-05','2012-09-08',3);
クエリでは、上記のようにグループ化されていない結果セットを出力します。変数 @ld は「最後の日付」であり、変数 @lid は「最後の ID」です。@lid が変更されるたびに、@ld を null にリセットします。参考までに、mysql では、:= 演算子は代入が行われる場所であり、= 演算子は単に等号です。
これは 3 レベルのクエリですが、2 レベルに減らすこともできます。より読みやすくするために、追加の外部クエリを使用しました。最も内側のクエリは単純で、dto列が含まれないように調整され、適切な行の順序付けが行われます。中間のクエリは、dfrom/dto 値を調整して、重複しないようにします。外側のクエリは、使用されていないフィールドを単純に削除し、間隔の範囲を計算します。
set @ldt=null, @lid=null;
select id, no_dfrom as dfrom, no_dto as dto, datediff(no_dto, no_dfrom) as days from (
select if(@lid=id,@ldt,@ldt:=null) as last, dfrom, dto, if(@ldt>=dfrom,@ldt,dfrom) as no_dfrom, if(@ldt>=dto,@ldt,dto) as no_dto, @ldt:=if(@ldt>=dto,@ldt,dto), @lid:=id as id,
datediff(dto, dfrom) as overlapped_days
from (select id, dfrom, dto + INTERVAL 1 DAY as dto from sample order by id, dfrom) as sample
) as nonoverlapped
order by id, dfrom;
上記のクエリは結果を返します (ここでは dfrom/dto が重複していないことに注意してください)。
+------+------------+------------+------+
| id | dfrom | dto | days |
+------+------------+------------+------+
| 1 | 2012-08-31 | 2012-09-05 | 5 |
| 1 | 2012-09-05 | 2012-09-08 | 3 |
| 1 | 2012-09-08 | 2012-09-08 | 0 |
| 1 | 2012-09-08 | 2012-09-08 | 0 |
| 1 | 2012-09-08 | 2012-09-13 | 5 |
| 2 | 2012-09-04 | 2012-09-07 | 3 |
| 2 | 2012-09-07 | 2012-09-09 | 2 |
| 2 | 2012-09-11 | 2012-09-14 | 3 |
+------+------------+------------+------+