6

訪問データの表があります。

uid (INT) | created_at (DATETIME)

ユーザーがアプリに何日続けてアクセスしたかを知りたい。たとえば、次のようになります。

SELECT DISTINCT DATE(created_at) AS d FROM visits WHERE uid = 123

戻ります:

     d      
------------
 2012-04-28
 2012-04-29
 2012-04-30
 2012-05-03
 2012-05-04

5つのレコードと2つの間隔があります-3日(4月28日から30日)と2日(5月3日から4日)。

私の質問は、ユーザーがアプリに連続してアクセスした最大日数(例では3日)を見つける方法です。SQLドキュメントで適切な関数を見つけようとしましたが、成功しませんでした。私は何かが足りないのですか?


UPD: 皆さん、答えてくれてありがとう!実際、私はvertica分析データベース(http://vertica.com/)を使用していますが、これは非常にまれなソリューションであり、経験のある人はごくわずかです。SQL-99標準をサポートしていますが。

さて、ほとんどのソリューションはわずかな変更で機能します。最後に、独自のバージョンのクエリを作成しました。

-- returns starts of the vitit series 
SELECT t1.d as s FROM testing t1
LEFT JOIN testing t2 ON DATE(t2.d) = DATE(TIMESTAMPADD('day', -1, t1.d))
WHERE t2.d is null GROUP BY t1.d

          s          
---------------------
 2012-04-28 01:00:00
 2012-05-03 01:00:00

-- returns end of the vitit series 
SELECT t1.d as f FROM testing t1
LEFT JOIN testing t2 ON DATE(t2.d) = DATE(TIMESTAMPADD('day', 1, t1.d))
WHERE t2.d is null GROUP BY t1.d

          f          
---------------------
 2012-04-30 01:00:00
 2012-05-04 01:00:00

したがって、今必要なのは、たとえば行インデックスなど、何らかの方法でそれらを結合することだけです。

SELECT s, f, DATEDIFF(day, s, f) + 1 as seq FROM (
    SELECT t1.d as s, ROW_NUMBER() OVER () as o1 FROM testing t1
    LEFT JOIN testing t2 ON DATE(t2.d) = DATE(TIMESTAMPADD('day', -1, t1.d))
    WHERE t2.d is null GROUP BY t1.d
) tbl1 LEFT JOIN (
    SELECT t1.d as f, ROW_NUMBER() OVER () as o2 FROM testing t1
    LEFT JOIN testing t2 ON DATE(t2.d) = DATE(TIMESTAMPADD('day', 1, t1.d))
    WHERE t2.d is null GROUP BY t1.d
) tbl2 ON o1 = o2 

サンプル出力:

          s          |          f          | seq 
---------------------+---------------------+-----
 2012-04-28 01:00:00 | 2012-04-30 01:00:00 |   3
 2012-05-03 01:00:00 | 2012-05-04 01:00:00 |   2
4

10 に答える 10

7

最短の別のアプローチは、自己参加を行います。

with grouped_result as
(
    select 
       sr.d,
       sum((fr.d is null)::int) over(order by sr.d) as group_number
    from tbl sr
    left join tbl fr on sr.d = fr.d + interval '1 day'
)
select d, group_number, count(d) over m as consecutive_days
from grouped_result
window m as (partition by group_number)

出力:

          d          | group_number | consecutive_days 
---------------------+--------------+------------------
 2012-04-28 08:00:00 |            1 |                3
 2012-04-29 08:00:00 |            1 |                3
 2012-04-30 08:00:00 |            1 |                3
 2012-05-03 08:00:00 |            2 |                2
 2012-05-04 08:00:00 |            2 |                2
(5 rows)

ライブテスト:http ://www.sqlfiddle.com/#!1/93789/1

sr = 2行目、fr = 1行目(または前の行?ツ</a>)。基本的に、バックトラッキングを実行しています。これは、サポートされていないデータベースのシミュレートされたラグですLAG(PostgresはLAGをサポートしていますが、ウィンドウ処理はネストされたウィンドウ処理をサポートしていないため、ソリューションは非常に長くなります)。したがって、このクエリでは、ハイブリッドアプローチを使用し、結合を介してLAGをシミュレートし、それに対してSUMウィンドウ処理を使用します。これにより、グループ番号が生成されます。

アップデート

最後のクエリを入力するのを忘れました。上記のクエリは、グループ番号付けの基盤を示しています。これを次のようにモーフィングする必要があります。

with grouped_result as
(
    select 
       sr.d,
       sum((fr.d is null)::int) over(order by sr.d) as group_number
    from tbl sr
    left join tbl fr on sr.d = fr.d + interval '1 day'
)
select min(d) as starting_date, max(d) as end_date, count(d) as consecutive_days
from grouped_result
group by group_number
-- order by consecutive_days desc limit 1


STARTING_DATE                END_DATE                     CONSECUTIVE_DAYS
April, 28 2012 08:00:00-0700 April, 30 2012 08:00:00-0700 3
May, 03 2012 08:00:00-0700   May, 04 2012 08:00:00-0700   2

アップデート

ウィンドウ関数を使用する他のソリューションが長くなった理由はわかっています。グループの番号付けとグループのカウントのロジックを説明しようとすると、長くなりました。MySqlアプローチのように追いかけた場合、そのウィンドウ関数は短くなる可能性があります。そうは言っても、これが私の古いウィンドウ関数のアプローチですが、今はもっと良くなっています:

with headers as
(
    select 
      d,lag(d) over m is null or d - lag(d) over m  <> interval '1 day' as header
    from tbl
    window m as (order by d)
)      
,sequence_group as
(
    select d, sum(header::int) over (order by d) as group_number
    from headers  
)
select min(d) as starting_date,max(d) as ending_date,count(d) as consecutive_days
from sequence_group
group by group_number
-- order by consecutive_days desc limit 1

ライブテスト:http ://www.sqlfiddle.com/#!1/93789/21

于 2012-05-04T13:24:51.493 に答える
2

すべて非常に良い答えですが、Verticaに固有の分析機能を利用した別のアプローチを示すことで貢献する必要があると思います(結局のところ、それはあなたが支払ったものの一部です)。そして、私は最後のクエリが短いことを約束します。

まず、conditional_true_event()を使用してクエリを実行します。Verticaのドキュメントから:

イベントウィンドウ番号を0から開始して各行に割り当て、ブール引数式の結果がtrueと評価されたときに番号を1ずつ増やします。

クエリの例は次のようになります。

select uid, created_at, 
       conditional_true_event( created_at - lag(created_at) > '1 day' ) 
       over (partition by uid order by created_at) as seq_id
from visits;

そして出力:

uid  created_at           seq_id  
---  -------------------  ------  
123  2012-04-28 00:00:00  0       
123  2012-04-29 00:00:00  0       
123  2012-04-30 00:00:00  0       
123  2012-05-03 00:00:00  1       
123  2012-05-04 00:00:00  1       
123  2012-06-04 00:00:00  2       
123  2012-06-04 00:00:00  2     

これで、最終的なクエリが簡単になります。

select uid, seq_id, count(1) num_days, min(created_at) s, max(created_at) f
from
(
    select uid, created_at, 
       conditional_true_event( created_at - lag(created_at) > '1 day' ) 
       over (partition by uid order by created_at) as seq_id
    from visits
) as seq
group by uid, seq_id;

最終出力:

uid  seq_id  num_days  s                    f                    
---  ------  --------  -------------------  -------------------  
123  0       3         2012-04-28 00:00:00  2012-04-30 00:00:00  
123  1       2         2012-05-03 00:00:00  2012-05-04 00:00:00  
123  2       2         2012-06-04 00:00:00  2012-06-04 00:00:00  

最後の注意点: num_days実際には、内部クエリの行数です。元のテーブルに2'2012-04-28'回の訪問がある場合(つまり、重複している場合)、それを回避することをお勧めします。

于 2012-12-20T20:48:50.533 に答える
2

MySQLではこれを行うことができます:

SET @nextDate = CURRENT_DATE;
SET @RowNum = 1;

SELECT MAX(RowNumber) AS ConecutiveVisits
FROM    (   SELECT  @RowNum := IF(@NextDate = Created_At, @RowNum + 1, 1) AS RowNumber,
                    Created_At,
                    @NextDate := DATE_ADD(Created_At, INTERVAL 1 DAY) AS NextDate
            FROM    Visits
            ORDER BY Created_At
        ) Visits

ここでの例:

http://sqlfiddle.com/#!2/6e035/8

しかし、これが最善の方法であると100%確信しているわけではありません。

Postgresqlの場合:

 ;WITH RECURSIVE VisitsCTE AS
 (  SELECT  Created_At, 1 AS ConsecutiveDays
    FROM    Visits
    UNION ALL
    SELECT  v.Created_At, ConsecutiveDays + 1
    FROM    Visits v
            INNER JOIN VisitsCTE cte
                ON 1 + cte.Created_At = v.Created_At
)
SELECT  MAX(ConsecutiveDays) AS ConsecutiveDays
FROM    VisitsCTE

ここでの例:

http://sqlfiddle.com/#!1/16c90/9

于 2012-05-04T11:58:07.600 に答える
2

Postgresqlには、MSSQLで利用できる一般的なテーブル式に似たものがあることを私は知っています。私はPostgresqlにあまり詳しくありませんが、以下のコードはMSSQLで機能し、必要なことを実行します。

create table #tempdates (
    mydate date
)

insert into #tempdates(mydate) values('2012-04-28')
insert into #tempdates(mydate) values('2012-04-29')
insert into #tempdates(mydate) values('2012-04-30')
insert into #tempdates(mydate) values('2012-05-03')
insert into #tempdates(mydate) values('2012-05-04');

with maxdays (s, e, c)
as
(
    select mydate, mydate, 1
    from #tempdates
    union all
    select m.s, mydate, m.c + 1
    from #tempdates t
    inner join maxdays m on DATEADD(day, -1, t.mydate)=m.e
)
select MIN(o.s),o.e,max(o.c)
from (
  select m1.s,max(m1.e) e,max(m1.c) c
  from maxdays m1
  group by m1.s
) o
group by o.e

drop table #tempdates

そして、これがSQLフィドルです:http ://sqlfiddle.com/#!3 / 42b38 / 2

于 2012-05-04T11:59:38.167 に答える
1

この質問にはすでにいくつかの答えがあります。ただし、SQLステートメントはすべて複雑すぎるようです。これは、基本的なSQL、行を列挙する方法、およびいくつかの日付演算を使用して実現できます。

重要な観察は、あなたがたくさんの日を持っていて、整数の並列シーケンスを持っているなら、違いは日がシーケンスにあるときの一定の日付であるということです。

次のクエリは、この観察結果を使用して元の質問に回答します。

select uid, min(d) as startdate, count(*) as numdaysinseq
from 
(
   select uid, d, adddate(d, interval -offset day) as groupstart
   from 
   (
     select uid, d, row_number() over (partition by uid order by date) as offset
     from 
     (
       SELECT DISTINCT uid, DATE(created_at) AS d
       FROM visits
     ) t
   ) t
) t

残念ながら、mysqlには機能がありませんrow_number()。ただし、変数には回避策があります(他のほとんどのデータベースにはこの機能があります)。

于 2012-05-05T03:44:28.663 に答える
1

PostgreSQL 8.4以降の場合、ウィンドウ関数をJOIN使用する短くてクリーンな方法があります。これがこれまでに投稿された最速のソリューション
になると思います。

WITH x AS (
    SELECT created_at AS d
         , lag(created_at) OVER (ORDER BY created_at) = (created_at - 1) AS nu
    FROM   visits
    WHERE  uid = 1
    )
   , y AS (
    SELECT d, count(NULLIF(nu, TRUE)) OVER (ORDER BY d) AS seq
    FROM   x
    )
SELECT count(*) AS max_days, min(d) AS seq_from,  max(d) AS seq_to
FROM   y
GROUP  BY seq
ORDER  BY 1 DESC
LIMIT  1;

戻り値:

max_days | seq_from   | seq_to
---------+------------+-----------
3        | 2012-04-28 | 2012-04-30

created_atそれがとであるdateと仮定しuniqueます。

  1. CTE x:ユーザーが毎日訪問する場合は、昨日もここにいたかどうかを確認してください。「昨日」を計算するには、次を使用しますcreated_at - 1。最初の行は特殊なケースであり、ここではNULLを生成します。

  2. seqCTE yの場合:毎日の「これまでのところ昨日がない日」()の実行カウントを計算します。NULL値はカウントされないためcount(NULLIF(nu, TRUE))、高速で最短の方法であり、特殊なケースもカバーします。

  3. 最後に、日数をグループ化seqし、日数を数えます。そこにいる間、私はシーケンスの最初と最後の日を追加しました。 ORDER BYシーケンスの長さ、および最も長いものを選択します。

于 2012-05-04T16:08:24.890 に答える
1

以下はOracleに対応している必要があり、再帰ロジックは必要ありません。

;WITH
  visit_dates (
    visit_id,
    date_id,
    group_id
  )
AS
(
  SELECT
    ROW_NUMBER() OVER (ORDER BY TRUNC(created_at)),
    TRUNC(SYSDATE) - TRUNC(created_at),
    TRUNC(SYSDATE) - TRUNC(created_at) - ROW_NUMBER() OVER (ORDER BY TRUNC(created_at))
  FROM
    visits
  GROUP BY
    TRUNC(created_at)
)
,
  group_duration (
    group_id,
    duration
  )
AS
(
  SELECT
    group_id,
    MAX(date_id) - MIN(date_id) + 1  AS duration
  FROM
    visit_dates
  GROUP BY
    group_id
)
SELECT
  MAX(duration)  AS max_duration
FROM
  group_duration
于 2012-05-04T13:01:20.300 に答える
1

Postgresql:

with headers as
(
    select 
        d,
        lag(d) over m is null or d - lag(d) over m  <> interval '1 day' as header

    from tbl
    window m as (order by d)
)      
,sequence_group as
(
    select d, sum(header::int) over m as group_number 
    from headers
    window m as (order by d)
)
,consecutive_list as
(
    select d, group_number, count(d) over m as consecutive_count
    from sequence_group 
    window m as (partition by group_number)
)
select * from consecutive_list

分割統治法:3つのステップ

最初のステップ、ヘッダーを見つける:

with headers as
(
    select 
        d,
        lag(d) over m is null or d - lag(d) over m  <> interval '1 day' as header

    from tbl
    window m as (order by d)
)
select * from headers

出力:

          d          | header 
---------------------+--------
 2012-04-28 08:00:00 | t
 2012-04-29 08:00:00 | f
 2012-04-30 08:00:00 | f
 2012-05-03 08:00:00 | t
 2012-05-04 08:00:00 | f
(5 rows)

2番目のステップ、グループ化を指定します。

with headers as
(
    select 
        d,
        lag(d) over m is null or d - lag(d) over m  <> interval '1 day' as header

    from tbl
    window m as (order by d)
)      
,sequence_group as
(
    select d, sum(header::int) over m as group_number 
    from headers
    window m as (order by d)
)
select * from sequence_group

出力:

          d          | group_number 
---------------------+--------------
 2012-04-28 08:00:00 |            1
 2012-04-29 08:00:00 |            1
 2012-04-30 08:00:00 |            1
 2012-05-03 08:00:00 |            2
 2012-05-04 08:00:00 |            2
(5 rows)

3番目のステップ、最大日数をカウントします。

with headers as
(
    select 
        d,
        lag(d) over m is null or d - lag(d) over m  <> interval '1 day' as header

    from tbl
    window m as (order by d)
)      
,sequence_group as
(
    select d, sum(header::int) over m as group_number 
    from headers
    window m as (order by d)
)
,consecutive_list as
(
select d, group_number, count(d) over m as consecutive_count
from sequence_group 
window m as (partition by group_number)
)
select * from consecutive_list

出力:

          d          | group_number | consecutive_count 
---------------------+--------------+-----------------
 2012-04-28 08:00:00 |            1 |               3
 2012-04-29 08:00:00 |            1 |               3
 2012-04-30 08:00:00 |            1 |               3
 2012-05-03 08:00:00 |            2 |               2
 2012-05-04 08:00:00 |            2 |               2
(5 rows)
于 2012-05-04T13:07:17.460 に答える
1

これは、最短のMySQL用であり、最小変数(1つの変数のみ)を使用します。

select 
   min(d) as starting_date, max(d) as ending_date, 
   count(d) as consecutive_days
from
(
  select 
     sr.d,
     IF(fr.d is null,@group_number := @group_number + 1,@group_number) 
        as group_number
  from tbl sr
  left join tbl fr on sr.d = adddate(fr.d,interval 1 day)
  cross join (select @group_number := 0) as grp
) as x
group by group_number

出力:

STARTING_DATE                  ENDING_DATE                  CONSECUTIVE_DAYS
April, 28 2012 08:00:00-0700   April, 30 2012 08:00:00-0700 3
May, 03 2012 08:00:00-0700     May, 04 2012 08:00:00-0700   2

ライブテスト:http ://www.sqlfiddle.com/#!2/65169/1

于 2012-05-04T14:30:04.737 に答える
1

OPのVerticaデータベースに対するクエリアプローチを見て、2つの結合を同時に実行してみました。

これらのPostgresqlおよびSQLServerクエリバージョンは両方ともVerticaで機能します

Postgresqlバージョン:

select 
  min(gr.d) as start_date,
  max(gr.d) as end_date,
  date_part('day', max(gr.d) - min(gr.d))+1 as consecutive_days
from 
(
  select 
  cr.d, (row_number() over() - 1) / 2 as pair_number
  from tbl cr   
  left join tbl pr on pr.d = cr.d - interval '1 day'
  left join tbl nr on nr.d = cr.d + interval '1 day'
  where pr.d is null <> nr.d is null
) as gr
group by pair_number
order by start_date

についてpr.d is null <> nr.d is null。つまり、前の行がnullまたは次の行がnullのいずれかですが、両方をnullにすることはできません。したがって、非連続の日付の前の行と次の行はnullであるため、これは基本的に非連続の日付を削除します(これは基本的にヘッダーとフッターのみのすべての日付を提供します)。これはXOR演算とも呼ばれます

連続した日付のみが残っている場合は、row_numberを介してそれらをペアリングできます。

(row_number() over() - 1) / 2 as pair_number

row_number()1から始めて、1で減算する必要があります(代わりに1で加算することもできます)。次に、2で除算します。これにより、ペアの日付が互いに隣接します

ライブテスト:http ://www.sqlfiddle.com/#!1 / fc440 / 7


これはSQLServerのバージョンです。

select 
  min(gr.d) as start_date,
  max(gr.d) as end_date,
  datediff(day, min(gr.d),max(gr.d)) +1 as consecutive_days
from 
(
  select 
     cr.d, (row_number() over(order by cr.d) - 1) / 2 as pair_number
  from tbl cr   
  left join tbl pr on pr.d = dateadd(day,-1,cr.d)
  left join tbl nr on nr.d = dateadd(day,+1,cr.d)
  where         
       case when pr.d is null then 1 else 0 end
    <> case when nr.d is null then 1 else 0 end
) as gr
group by pair_number
order by start_date

日付関数の人為的な違いを除いて、上記と同じロジック。また、SQL ServerにはそのORDER BY句が必要ですが、 OVERPostgresqlOVERは空のままにしておくことができます。

SQL Serverにはファーストクラスのブール値がないため、ブール値を直接比較することはできません。

pr.d is null <> nr.d is null

これはSQLServerで行う必要があります。

   case when pr.d is null then 1 else 0 end
<> case when nr.d is null then 1 else 0 end

ライブテスト:http ://www.sqlfiddle.com/#!3/65df2/17

于 2012-05-05T14:52:50.853 に答える