web-dev-qa-db-ja.com

MySQLは7つの連続した日付を持つ行を選択します

次の列を持つ単純なテーブルを想像してください:item_iddate
そして値:

CREATE TABLE foo (item_id int, date date);

INSERT INTO foo(item_id, date)
VALUES
    ( 1, '2017-02-10' ),
    ( 2, '2017-02-10' ),
    ( 1, '2017-02-11' ),
    ( 1, '2017-02-12' ),
    ( 1, '2017-02-13' ),
    ( 2, '2017-02-13' ),
    ( 1, '2017-02-14' );

選択方法item_idsテーブルに7つの連続した日レコードがありますか?

開始日と終了日は不明です。それは、任意の開始日から最大7日間連続して利用できるはずです。

3
Mehdi Azmoudeh

MySQL 8

MySQL 8はウィンドウ関数を提供します...

_SELECT item_id
FROM (
  SELECT
    item_id,
    date,
    count(coalesce(diff, 1)=1 OR null) OVER (PARTITION BY item_id ORDER BY date) seq
  FROM (
    SELECT
      item_id,
      date,
      date - lag(date) OVER (PARTITION BY item_id ORDER BY date) AS diff
    FROM foo
  ) AS t
) AS t2
GROUP BY item_id
HAVING max(seq) > 7;
_

説明

これは私たちがインナーでやっていることです。

_SELECT
  item_id,
  date,
  date - lag(date) OVER (PARTITION BY item_id ORDER BY date) AS diff
FROM foo

 item_id |    date    | diff 
---------+------------+------
       1 | 2017-02-10 |     
       1 | 2017-02-11 |    1
       1 | 2017-02-12 |    1
       1 | 2017-02-13 |    1
       1 | 2017-02-14 |    1
       2 | 2017-02-10 |     
       2 | 2017-02-13 |    3
(7 rows)
_

ここで違いを返します。ここで必要なのは、日付の差が1であるものを分離することです。ここで差の結果がnullであると仮定するのは、減算する前の日付がないためであるので、1に設定します。 1がない場合、値をnullに設定して、count()がそれをスキップするようにします。

_SELECT
  item_id,
  date,
  count(coalesce(diff, 1)=1 OR null) OVER (PARTITION BY item_id ORDER BY date) seq
FROM (
  SELECT
    item_id,
    date,
    date - lag(date) OVER (PARTITION BY item_id ORDER BY date) AS diff
  FROM foo
) AS t; 

 item_id |    date    | seq 
---------+------------+-----
       1 | 2017-02-10 |   1
       1 | 2017-02-11 |   2
       1 | 2017-02-12 |   3
       1 | 2017-02-13 |   4
       1 | 2017-02-14 |   5
       2 | 2017-02-10 |   1
       2 | 2017-02-13 |   1
(7 rows)
_

この時点からは、_GROUP BY_とHAVINGになります。

これは、MySQL 8がまだリリースされていないため、PostgreSQLでテストされました。 PostgreSQL を使用していない場合は、無料でダウンロードして確認してください。 MySQLに似ていますが、あらゆる点で優れています。

5
Evan Carroll
    select 
    item_id
    from foo 
    where
    (
      select count(distinct date) from foo d2 
       where d2.date>=foo.date and d2.date<=date_add(foo.date, interval 6 day)
       and foo.item_id=d2.item_id -- if IDs need to be the same
    ) =7
    order by item_id

したがって、各行の各日付を取り、6日を追加して7日間にします。その期間に7つの異なる日付がある場合、それらは連続している必要があります。

2
LoztInSpace

素朴なアプローチは、7つの自己結合を行うことです。

select f1.item_id, f1.dt, f7.dt
from foo f1 
join foo f2 
    on date_add(f1.dt, interval 1 day) = f2.dt
    and f1.item_id = f2.item_id
join foo f3
    on date_add(f2.dt, interval 1 day) = f3.dt
    and f2.item_id = f3.item_id
join foo f4 
    on date_add(f3.dt, interval 1 day) = f4.dt
    and f3.item_id = f4.item_id
join foo f5 
    on date_add(f4.dt, interval 1 day) = f5.dt
    and f4.item_id = f5.item_id
join foo f6 
    on date_add(f5.dt, interval 1 day) = f6.dt
    and f5.item_id = f6.item_id
join foo f7 
    on date_add(f6.dt, interval 1 day) = f7.dt
    and f6.item_id = f7.item_id;

少し単純な方法は、連続する間隔を見つけて、どの間隔が7日以上かを確認することです。

select item_id from (
    select lower.item_id, lower.min_dt, min(upper.max_dt) as max_dt  
    from (      
        select item_id, dt as min_dt      
        from foo f1      
        where not exists (         
            select 1 from foo f2
            where f1.item_id = f2.item_id
              and f1.dt = date_add(f2.dt, interval 1 day)
        )  
    ) as lower  
    join (
        select item_id, dt as max_dt      
        from foo f3      
        where not exists (
            select 1 from foo f4          
            where f3.item_id = f4.item_id 
              and f3.dt = date_sub(f4.dt, interval 1 day)     
        )  
    ) as upper
         on lower.item_id = upper.item_id
        and max_dt >= min_dt  
    group by lower.item_id, lower.min_dt
) as x 
where datediff(max_dt, min_dt) >= 7;

誰かがコメントで述べたように、この種の問題はウィンドウ関数のそよ風です。

2
Lennart