10

次のタスクのクエリを見つけるのに苦労しています

次のデータがあり、一意の ID ごとに合計ネットワーク日を見つけたい

ID  From        To          NetworkDay
1   03-Sep-12   07-Sep-12   5
1   03-Sep-12   04-Sep-12   2
1   05-Sep-12   06-Sep-12   2
1   06-Sep-12   12-Sep-12   5
1   31-Aug-12   04-Sep-12   3
2   04-Sep-12   06-Sep-12   3
2   11-Sep-12   13-Sep-12   3
2   05-Sep-12   08-Sep-12   3

問題は、日付範囲が重複する可能性があり、次の結果が得られる SQL を思いつかないことです

ID  From        To          NetworkDay
1   31-Aug-12   12-Sep-12   9
2   04-Sep-12   08-Sep-12   4
2   11-Sep-12   13-Sep-12   3

その後

ID  Total Network Day
1   9
2   7

ネットワーク日の計算ができない場合は、2 番目のテーブルにアクセスするだけで十分です。

私の質問が明確であることを願っています

4

3 に答える 3

2

これを行うには、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 |
+------+------------+------------+------+
于 2012-10-08T23:28:40.660 に答える
0
with t_data as (
    select 1 as id,
           to_date('03-sep-12','dd-mon-yy') as start_date,
           to_date('07-sep-12','dd-mon-yy') as end_date from dual
    union all
    select 1,
           to_date('03-sep-12','dd-mon-yy'),
           to_date('04-sep-12','dd-mon-yy') from dual
    union all
    select 1,
           to_date('05-sep-12','dd-mon-yy'),
           to_date('06-sep-12','dd-mon-yy') from dual
    union all
    select 1,
           to_date('06-sep-12','dd-mon-yy'),
           to_date('12-sep-12','dd-mon-yy') from dual
    union all 
    select 1,
           to_date('31-aug-12','dd-mon-yy'),
           to_date('04-sep-12','dd-mon-yy') from dual
    union all 
    select 2,
           to_date('04-sep-12','dd-mon-yy'), 
           to_date('06-sep-12','dd-mon-yy') from dual
    union all 
    select 2, 
           to_date('11-sep-12','dd-mon-yy'), 
           to_date('13-sep-12','dd-mon-yy') from dual
    union all 
    select 2, 
           to_date('05-sep-12','dd-mon-yy'), 
           to_date('08-sep-12','dd-mon-yy') from dual
),
t_holidays as (
    select to_date('01-jan-12','dd-mon-yy') as holiday
      from dual
),
t_data_rn as (
    select rownum as rn, t_data.* from t_data
),
t_model as (
    select distinct id,
           start_date
    from t_data_rn
      model
         partition by (rn, id)
         dimension by (0 as i)
         measures(start_date, end_date)
         rules
         (  start_date[for i 
                      from 1 
                        to end_date[0]-start_date[0]
                 increment 1] = start_date[0] + cv(i),
            end_date[any] = start_date[cv()] + 1
         )
      order by 1,2
),
t_network_days as (
    select t_model.*,
           case when
               mod(to_char(start_date, 'j'), 7) + 1 in (6, 7)
               or t_holidays.holiday is not null               
               then 0 else 1
           end as working_day
      from t_model
      left outer join t_holidays
        on t_holidays.holiday = t_model.start_date
)
select id, 
       sum(working_day) as network_days
  from t_network_days
 group by id;
  • t_data- 初期データ
  • t_holidays- 休日のリストが含まれています
  • t_data_rnrownum-の各行に一意のキー ( ) を追加するだけですt_data
  • t_model-t_data日付範囲を日付のフラットなリストに展開します
  • t_network_dayst_model-曜日 (土日) と休日リストに基づいて、各日付を営業日または週末としてマークします。
  • 最終クエリ - 各グループごとのネットワーク日数を計算します。
于 2012-11-04T10:11:39.393 に答える
0

穴を取り除き、最大間隔のみを考慮して、間隔をマージする SQL を構築してみてはどうでしょうか。次のようになります(テストされていません):

SELECT DISTINCT F.ID, F.From, L.To
 FROM Temp AS F, Temp AS L
 WHERE F.From < L.To AND F.ID = L.ID
   AND NOT EXISTS (SELECT *
                 FROM Temp AS T
                 WHERE T.ID = F.ID
                 AND F.From < T.From AND T.From < L.To
                         AND NOT EXISTS ( SELECT *
                         FROM Temp AS T1
                            WHERE T1.ID = F.ID
                            AND T1.From < T.From
                            AND T.From <= T1.To)
               )
   AND NOT EXISTS (SELECT *
              FROM Temp AS T2
              WHERE T2.ID = F.ID
              AND (
                    (T2.From < F.From AND F.From <= T2.To)
                 OR (T2.From < L.To AND L.To < T2.To)
                   )
             )
于 2012-09-11T14:28:13.720 に答える