9

ユーザーベースでコホート分析を行うことを検討しています。「users」と「sessions」の 2 つのテーブルがあり、ユーザーとセッションの両方に「created_at」フィールドがあります。特定の日に作成され、セッションも作成されたユーザーの数 y = (0..6日前)、彼がその日に戻ったことを示します。

created_at  d2  d3  d4
today       *   *   *
today-1     49  *   *
today-2     45  30  *
today-3     47  48  18
...

この場合、today-3 に作成された 47 人のユーザーが、today-2 に返されました。

単一の MySQL クエリでこれを実行できますか? このようにクエリを個別に実行することもできますが、すべてを 1 つのクエリにまとめると非常に便利です。

SELECT `users`.* FROM `users` INNER JOIN `sessions` ON `sessions`.`user_id` = `users`.`id` WHERE `users`.`os` = 'ios' AND (`sessions`.`updated_at` BETWEEN '2013-01-16 08:00:00' AND '2013-01-17 08:00:00')
4

4 に答える 4

23

これは複雑な問題のようです。難しい問題に思えるかどうかに関係なく、小さな問題から取り組み始めることは決して悪い考えではありません。

たとえば、要件に応じて、先週登録されたすべてのユーザー (ユーザーのみ) を返すクエリから開始できます。つまり、今から 6 日後の日から開始できます。

SELECT *
FROM users
WHERE created_at >= CURDATE() - INTERVAL 6 DAY

次のステップでは、結果を日付別にグループ化し、すべてのグループの行を数えることができます。

SELECT
  created_at,
  COUNT(*) AS user_count
FROM users
WHERE created_at >= CURDATE() - INTERVAL 6 DAY
GROUP BY created_at

created_atdatetimeまたはの場合、グループ化基準としてtimestamp使用します。DATE(created_at)

SELECT
  DATE(created_at) AS created_at,
  COUNT(*) AS user_count
FROM users
WHERE created_at >= CURDATE() - INTERVAL 6 DAY
GROUP BY DATE(created_at)

ただし、出力に絶対日付は必要ないようですが、などの相対日付のみが必要です。その場合、2 つの日付間の日数を返す関数を使用して、(数値) オフセットを生成できます。今日から、それらの値でグループ化します。todaytoday - 1 dayDATEDIFF()

SELECT
  DATEDIFF(CURDATE(), created_at) AS created_at,
  COUNT(*) AS user_count
FROM users
WHERE created_at >= CURDATE() - INTERVAL 6 DAY
GROUP BY DATE(created_at)

created_atには などの「日付」が含まれ01まで続き6ます。todayそれらをなどに変換するのtoday-1は簡単で、最終的なクエリでそれがわかります。ただし、これまでのところ、ユーザーを実際にカウントする必要はなく、ユーザーのリターンをカウントする必要があるため、1 歩後退する必要があるポイントに到達しました (または、おそらく、右に半ステップです)。 . usersそのため、現時点で必要な実際の作業データセットは次のようになります。

SELECT
  id,
  DATEDIFF(CURDATE(), created_at) AS day_offset
FROM users
WHERE created_at >= CURDATE() - INTERVAL 6 DAY

この行セット (派生元の行セット) を結合するためのユーザー ID が必要であり、グループ化基準としてsessions必要です。day_offset

sessions次に、同様の変換をテーブルで実行する必要があります。詳細については説明しません。結果のクエリは、次の 2 つの例外を除いて、最後のクエリと非常に同じになると言えば十分です。

  • idに置き換えられuser_idます。

  • DISTINCT はサブセット全体に適用されます。

DISTINCT の理由は、1 ユーザーおよび 1 日あたり 1 行しか返さないためです。ユーザーが特定の日にセッションをいくつ持っていても、それらを1 つの returnとしてカウントしたいというのが私の理解です。したがって、ここから派生するものは次のsessionsとおりです。

SELECT DISTINCT
  user_id,
  DATEDIFF(CURDATE(), created_at) AS day_offset
FROM sessions
WHERE created_at >= CURDATE() - INTERVAL 6 DAY

あとは、2 つの派生テーブルを結合し、グループ化を適用し、条件付き集計を使用して必要な結果を取得するだけです。

SELECT
  CONCAT('today', IFNULL(CONCAT('-', NULLIF(u.DayOffset, 0)), '')) AS created_at,
  SUM(s.DayOffset = 0) AS d0,
  SUM(s.DayOffset = 1) AS d1,
  SUM(s.DayOffset = 2) AS d2,
  SUM(s.DayOffset = 3) AS d3,
  SUM(s.DayOffset = 4) AS d4,
  SUM(s.DayOffset = 5) AS d5,
  SUM(s.DayOffset = 6) AS d6
FROM (
  SELECT
    id,
    DATEDIFF(CURDATE(), created_at) AS DayOffset
  FROM users
  WHERE created_at >= CURDATE() - INTERVAL 6 DAY
) u
LEFT JOIN (
  SELECT DISTINCT
    user_id,
    DATEDIFF(CURDATE(), created_at) AS DayOffset
  FROM sessions
  WHERE created_at >= CURDATE() - INTERVAL 6 DAY
) s
ON u.id = s.user_id
GROUP BY u.DayOffset
;

私はこれをテスト/デバッグしていないことを認めなければなりませんが、これが必要な場合は、提供されたデータ サンプルを喜んで使用します。:)

于 2013-01-22T18:17:25.947 に答える
2

この回答は、@Newy が望んでいた出力テーブルを反転するため、コホートは列ではなく行になり、相対日付ではなく絶対日付を使用します。

次のようなクエリを探していました。

Date        d0  d1  d2  d3  d4  d5  d6
2016-11-03  3   1   0   0   0   0   0
2016-11-04  4   2   0   1   0   0   *
2016-11-05  7   0   1   1   0   *   *
2016-11-06  7   3   1   1   *   *   *
2016-11-07  13  5   1   *   *   *   *
2016-11-08  4   0   *   *   *   *   *
2016-11-09  1   *   *   *   *   *   *

特定の日にサインアップしたユーザーの数を探していたので、1 日後、2 日後に戻ってきたユーザーの数などを調べていました。2016 年 11 月 7 日に 13 人のユーザーがサインアップしてセッションを行い、その後 5 人でした。これらのユーザーのうち、1 日後に戻ってきたユーザーが 1 人、2 日後に戻ってきたユーザーが 1 人などです。

@Andriy M の大きなクエリの最初のサブクエリを取得し、現在の日付からの相対日数ではなく、ユーザーがサインアップした日付を取得するように変更しました。

SELECT
    id,
    DATE(created_at) AS DayOffset
  FROM users
  WHERE created_at >= CURDATE() - INTERVAL 6 DAY

次に、LEFT JOIN サブクエリを次のように変更しました。

 SELECT DISTINCT
    sessions.user_id,
    DATEDIFF(sessions.created_at, user.created_at) AS DayOffset
    FROM sessions
    LEFT JOIN users ON (users.id = sessions.user_id)
    WHERE sessions.created_at >= CURDATE() - INTERVAL 6 DAY

@Andriy Mの回答のように現在の日付に相対的ではなく、ユーザーがサインアップした日付に相対的なdayoffsetが必要でした。そのため、ユーザーがサインアップした時間を取得するためにユーザーテーブルに結合を残し、その日付の差分を作成しました。

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

SELECT u.DayOffset as Date,
  SUM(s.DayOffset = 0) AS d0,
  SUM(s.DayOffset = 1) AS d1,
  SUM(s.DayOffset = 2) AS d2,
  SUM(s.DayOffset = 3) AS d3,
  SUM(s.DayOffset = 4) AS d4,
  SUM(s.DayOffset = 5) AS d5,
  SUM(s.DayOffset = 6) AS d6
FROM (
 SELECT
    id,
    DATE(created_at) AS DayOffset
  FROM users
  WHERE created_at >= CURDATE() - INTERVAL 6 DAY
) as u
LEFT JOIN (
    SELECT DISTINCT
    sessions.user_id,
    DATEDIFF(sessions.created_at, user.created_at) AS DayOffset
    FROM sessions
    LEFT JOIN users ON (users.id = sessions.user_id)
    WHERE sessions.created_at >= CURDATE() - INTERVAL 6 DAY
) as s
ON s.user = u.id
GROUP BY u.DayOffset
于 2016-11-09T15:35:42.443 に答える