5

次のようなテーブルに基づいて、費やされた合計時間をカウントする必要があります。

ID | 開始時刻 | 終了時間 |

期間が重複する可能性があります。オーバーパッピング期間を 1 回だけカウントする必要があります。

たとえば、次のような生理がある場合:

*----A----* *------C-----* *----------D----------*
                  * -  - -なれ - -*

合計は次のようになります。(A.end-A.start) + (C.end - B.start) + (D.end - D.start)

このクエリを作成するために使用する必要があるアプローチについて少し混乱しています。助けていただければ幸いです。

4

3 に答える 3

2

わかりました、本番環境で使用する前に、あらゆる方法でこれをテストすることを真剣に主張します。特に、1 つの期間に複数のオーバーラップがある場合にどうなるかをテストします。

このクエリが行うことは、各タイム スパンの期間を計算し、より高い ID を持つ他のタイム スパンとどれだけオーバーラップが存在するかを計算することです。

select
    t1.id,
    t1.start_time,
    t1.end_time,
    t1.end_time - t1.start_time as duration,
    sum(
          if(t2.start_time <  t1.start_time and t2.end_time >  t1.end_time  , t1.end_time - t1.start_time, 0) -- t2 completely around t1
        + if(t2.start_time >= t1.start_time and t2.end_time <= t1.end_time  , t2.end_time - t2.start_time, 0) -- t2 completely within t1
        + if(t2.start_time <  t1.start_time and t2.end_time >  t1.start_time and t2.end_time   < t1.end_time  , t2.end_time - t1.start_time, 0) -- t2 starts before t1 starts and overlaps partially
        + if(t2.start_time <  t1.end_time   and t2.end_time >  t1.end_time   and t2.start_time > t1.start_time, t1.end_time - t2.start_time, 0) -- t2 starts before t1 ends and overlaps partially
    ) as overlap
from
    times t1
    left join times t2 on
        t2.id > t1.id --  t2.id is greater than t1.id
        and (
               (t2.start_time <  t1.start_time and t2.end_time >  t1.end_time  ) -- t2 completely around t1
            or (t2.start_time >= t1.start_time and t2.end_time <= t1.end_time  ) -- t2 completely within t1
            or (t2.start_time <  t1.start_time and t2.end_time >  t1.start_time) -- t2 starts before t1 starts and overlaps
            or (t2.start_time <  t1.end_time   and t2.end_time >  t1.end_time  ) -- t2 starts before t1 ends and overlaps
        )
group by
    t1.id

したがって、最終的に必要なのは次のとおりです。

select
    sum(t.duration) - sum(t.overlap) as filtered_duration
from
    (
        OTHER QUERY HERE
    ) as t

したがって、最終的には次のクエリがあります。

select
    sum(t.duration) - sum(t.overlap) as filtered_duration
from
    (
        select
            t1.id,
            t1.start_time,
            t1.end_time,
            t1.end_time - t1.start_time as duration,
            sum(
                  if(t2.start_time <  t1.start_time and t2.end_time >  t1.end_time  , t1.end_time - t1.start_time, 0) -- t2 completely around t1
                + if(t2.start_time >= t1.start_time and t2.end_time <= t1.end_time  , t2.end_time - t2.start_time, 0) -- t2 completely within t1
                + if(t2.start_time <  t1.start_time and t2.end_time >  t1.start_time and t2.end_time   < t1.end_time  , t2.end_time - t1.start_time, 0) -- t2 starts before t1 starts and overlaps partially
                + if(t2.start_time <  t1.end_time   and t2.end_time >  t1.end_time   and t2.start_time > t1.start_time, t1.end_time - t2.start_time, 0) -- t2 starts before t1 ends and overlaps partially
            ) as overlap
        from
            times t1
            left join times t2 on
                t2.id > t1.id --  t2.id is greater than t1.id
                and (
                       (t2.start_time <  t1.start_time and t2.end_time >  t1.end_time  ) -- t2 completely around t1
                    or (t2.start_time >= t1.start_time and t2.end_time <= t1.end_time  ) -- t2 completely within t1
                    or (t2.start_time <  t1.start_time and t2.end_time >  t1.start_time) -- t2 starts before t1 starts and overlaps
                    or (t2.start_time <  t1.end_time   and t2.end_time >  t1.end_time  ) -- t2 starts before t1 ends and overlaps
                )
        group by
            t1.id
    ) as t
于 2013-04-03T08:42:18.960 に答える
2

結果が正しいことを確認しながら、時間を取得する別の方法を提案したいと思います。しかし、MySQLでこれを完全に行う方法はわかりません。

上記の例を次の時間で再利用します。第 3 レベルのエントリ「F」さえあるかもしれません。

1         3              7           12 13    (15 16)        20
|----A----|              |------C-----| |----------D----------|
                  |-----B-----|              |---E---|
                  5           9              14     17
                                                |F|
  1. 時間順に並べられたすべてのタイムスタンプの結合リストを照会し、各「アクション」のタイプを追加します

    SELECT 1 as onoff, start_time as time FROM table
    UNION
    SELECT -1 as onoff, end_time as time FROM table
    ORDER BY time
    
  2. 開始/ログイン時に 1 ずつ増加し、終了/ログアウト時に 1 ずつ減少する一時カウンターを使用して、ループ (?) によってリストを処理します。

    カウンタがtmp.start=<time>0 から 1 に変化tmp.end=<time>し、temp. 1 から 0 に変化した場合。

    スクリプトは、上記の例に対して次のように実行します。

    QUERY                       TMP TABLE
    onoff | time  | ctr         ID | start | end
    1     | 01:00 | 1           1  | 01:00 |            (record 1 added,   ctr 0->1)
    -1    | 03:00 | 0           1  | 01:00 | 03:00      (record 1 updated, ctr 1->0)
    1     | 05:00 | 1           2  | 05:00 |            (record 2 added,   ctr 0->1)
    1     | 07:00 | 2                                   (nothing to do)
    -1    | 09:00 | 1                                   (nothing to do)
    -1    | 12:00 | 0           2  | 05:00 | 12:00      (record 2 updated, ctr 1->0)
    1     | 13:00 | 1           3  | 13:00 |            (record 3 added,   ctr 0->1)
    1     | 14:00 | 2                                   (nothing to do)
    1     | 15:00 | 3                                   (nothing to do)
    -1    | 16:00 | 2                                   (nothing to do)
    -1    | 17:00 | 1                                   (nothing to do)
    -1    | 20:00 | 0           3  | 13:00 | 20:00      (record 3 updated, ctr 1->0)
    
  3. 最後のステップは非常に簡単です。ユニット内のtimestampdiff()fromstartを取得しend、それが必要/好きであり、さらにフィルタリングまたはグループ化を行います。

    例: データを別の場所で使用するには

    SELECT ID, start, end, timestampdiff(MINUTE, start, end) FROM tmp
    

    または例: 作業時間 / ユーザーごとのログイン時間の合計

    SELECT user_id, SUM(timestampdiff(MINUTE, start, end)) FROM tmp GROUP BY user_id
    

これにより、どのレベルのネストでも正しい期間が得られると確信していますが、MySQLでこれを達成する方法を知っている人はいますか? これも使ってみたいです。

よろしくお願いします

PS: スクリプトは最後のセッションを「閉じる」か、カウンター > 1 で終了した場合はエラーをスローし、カウンターがいつでも < 0 になった場合はエラーをスローする可能性があります。

于 2013-05-03T15:09:26.987 に答える