2

2 日以上 (参考として 7 日を使用しましょう) の間にデータベース テーブルにログインした一意のユーザーの数を返すための SQL を誰かが手伝ってくれないかと思っていました。

私のログ テーブルには、各行にタイムスタンプ (ts) と user_id が含まれており、その時点でのそのユーザーのアクティビティを表しています。

次のクエリは、このログから Daily Active Users または DAU を返します。

SELECT FLOOR(ts / 86400) AS day, COUNT(DISTINCT user_id) AS dau
FROM log
GROUP BY day ORDER BY day ASC

ここで、この 1 つのクエリに 1 週​​間のアクティブ ユーザー数、つまり 7 日間に記録された合計ユニーク ユーザー数を追加 (または少なくとも可能な限り効率的な方法で取得) したいとします。ただし、重複しない週に時間を分割したくありません。必要なのは、その日とその前の 6 日間に見られた個別の user_ids を毎日カウントすることです。

例えば:

day users wau
1   1,2   2
4   1,3   3
7   3,4,5 5
8   5     4    (user_id 2 lost from count)
15  2     2    (user_ids 1,3,4 lost from count)

提供できるヘルプに感謝します。さらに明確にする必要がある場合は、コメントでお気軽にお問い合わせください。

4

3 に答える 3

5

「週平均ユーザー」数(仕様の私の理解によると、「毎日、その日と過去6日間に見られた個別のuser_idの数」)を取得するには、以下の行に沿ってクエリを実行します。使用することができます。(クエリは「1日平均ユーザー」数も返します。

SELECT d.day
     , COUNT(DISTINCT u.user_id) AS wau
     , COUNT(DISTINCT IF(u.day=d.day,u.user_id,NULL)) AS dau
  FROM ( SELECT FLOOR(k.ts/86400) AS `day`
           FROM `log` k
          GROUP BY `day`
       ) d
  JOIN ( SELECT FLOOR(l.ts/86400) AS `day`
              , l.user_id
           FROM `log` l
          GROUP BY `day`, l.user_id
       ) u
    ON u.day <= d.day
   AND u.day > d.day - 7
 GROUP BY d.day
 ORDER BY d.day

(私はまだこれのテストを実行していませんが、後で実行し、修正が必要な場合はこのステートメントを更新します。)

このクエリは、特定の日のユーザーのリスト(行ソースからu)をログテーブルの一連の日(d行ソースから)に結合します。結合述語(ON句)に表示される文字通りの「7」に注意してください。これにより、ユーザーリストが過去6日間に「一致」します。

これを拡張して、たとえばSELECTリストに別の式を追加することにより、過去3日間の個別のユーザー数を取得することもできます。

     , COUNT(DISTINCT IF(u.day<=d.day AND u.day>d.day-3,u.user_id,NULL)) AS 3day

そのリテラル「7」は、より広い範囲を取得するために増やすことができます。そして、上記の式のリテラル3は、任意の日数を取得するように変更できます...前日の行(からd)が。からの各行に十分に結合されていることを確認する必要がありuます。

パフォーマンスに関する注意:インラインビュー(またはMySQLでは派生テーブルと呼ばれる)が原因で、これらのインラインビューの結果セットを中間のMyISAMテーブルに具体化する必要があるため、このクエリはそれほど高速ではない場合があります。

u最適ではない可能性があるため、エイリアスされたインラインビュー。ログテーブルに直接参加する方が速い場合があります。特定の日のユーザーの一意のリストを取得するという観点から考えていました。これは、インラインビューでのクエリによって得られたものです。何が起こっているのかを概念化するのは私にとって簡単でした。そして、同じユーザーが何百人も1日入力した場合、他の日に参加する前に、インラインビューで多数の重複が削除されると考えていました。返される日数を制限するWHERE句はudインラインビュー内に追加するのが最適です。(dインラインビューには、6日前に追加する必要があります。)


DATE(ts)別の注意点として、ts列がTIMESTAMPデータ型の場合、式を使用して日付部分を抽出する傾向があります。ただし、整数ではなく、結果セットにDATEデータ型が返されます。これは、指定した結果セットとは異なります。)

SELECT d.day
     , COUNT(DISTINCT u.user_id) AS wau
     , COUNT(DISTINCT IF(u.day=d.day,u.user_id,NULL)) AS dau
  FROM ( SELECT DATE(k.ts) AS `day`
           FROM `log` k
          GROUP BY `day`
       ) d
  JOIN ( SELECT DATE(l.ts) AS `day`
              , l.user_id
           FROM `log` l
          GROUP BY `day`, l.user_id
       ) u
    ON u.day <= d.day
   AND u.day > DATE_ADD(d.day, INTERVAL -7 DAY)
 GROUP BY d.day
 ORDER BY d.day

于 2012-12-14T19:39:52.560 に答える
2

これは、UNIX タイムスタンプではなく、日付、日時、またはタイムスタンプ フィールド タイプを使用してデータベース内の時刻値を表す必要がある理由を示すもう 1 つの優れた例です。整数のタイムスタンプ値には期間の固有の概念がなく、期間に基づいてクエリを実行する必要があるため、常に、誰かが実際にフィールドに対してクエリを実行する必要があります。その過程で、フィールドのインデックスを利用できなくなります。

いずれにせよ、これはかなり複雑なクエリです。私が提案しているものよりも良い方法があるかもしれませんが、少なくとも私が提案していることは理にかなっていることを願っています. このアプローチでは、テーブルをそれ自体に結合することによってデカルト結合を実行します。次に、条件を使用してレコード数を制限ONし、2 番目のログ テーブルの日付が最初のログ テーブルの日付から 7 日間以内に収まるようにします。最後に、集計とグループ化を行います。クエリは次のようになります。

SELECT DATE(FROM_UNIXTIME(log1.ts)) as `day`, COUNT(DISTINCT log2.user_id) as `dau`
FROM log AS log1
INNER JOIN log AS log2
ON DATE(FROM_UNIXTIME(log2.ts)) <= DATE(FROM_UNIXTIME(log1.ts))
AND DATE(FROM_UNIXTIME(log2.ts)) >= DATE_SUB(DATE(FROM_UNIXTIME(log1.ts)), INTERVAL 7 DAY)
GROUP BY `day`
ORDER BY `day` ASC

警告ですが。かなりの数のログ エントリがある場合、結果セット内のレコード数に何らかの係数を掛ける予定であり、インデックスを使用しないため、このクエリの実行には長い時間がかかります。

最善の策は、実際にテーブルに新しい日付形式の列を作成し、更新を実行して値を入力することです。そのフィールドにインデックスがあることを確認してください。次に、クエリは次のようになります。

SELECT log1.date_field as `day`, COUNT(DISTINCT log2.date_field) as `dau`
FROM log AS log1
INNER JOIN log AS log2 
ON log2.date_field <= log1.date_field
AND log2.date_field >= DATE_SUB(log1.date_field, INTERVAL 7 DAY)
GROUP BY `day`
ORDER BY `day` ASC

その後、今後のすべてのログ エントリにこのフィールドを設定できます。

于 2012-12-14T19:48:42.160 に答える