3

次の情報を含むテーブルがあります

 |date | user_id | week_beg | month_beg|

テスト値を含むテーブルを作成する SQL:

CREATE TABLE uniques
(
  date DATE,
  user_id INT,
  week_beg DATE,
  month_beg DATE
)
INSERT INTO uniques VALUES ('2013-01-01', 1, '2012-12-30', '2013-01-01')
INSERT INTO uniques VALUES ('2013-01-03', 3, '2012-12-30', '2013-01-01')
INSERT INTO uniques VALUES ('2013-01-06', 4, '2013-01-06', '2013-01-01')
INSERT INTO uniques VALUES ('2013-01-07', 4, '2013-01-06', '2013-01-01') 

入力テーブル:

 | date       | user_id     | week_beg   | month_beg  |    
 | 2013-01-01 | 1           | 2012-12-30 | 2013-01-01 |    
 | 2013-01-03 | 3           | 2012-12-30 | 2013-01-01 |    
 | 2013-01-06 | 4           | 2013-01-06 | 2013-01-01 |    
 | 2013-01-07 | 4           | 2013-01-06 | 2013-01-01 |  

出力テーブル:

 | date       | time_series | cnt        |                 
 | 2013-01-01 | D           | 1          |                 
 | 2013-01-01 | W           | 1          |                 
 | 2013-01-01 | M           | 1          |                 
 | 2013-01-03 | D           | 1          |                 
 | 2013-01-03 | W           | 2          |                 
 | 2013-01-03 | M           | 2          |                 
 | 2013-01-06 | D           | 1          |                 
 | 2013-01-06 | W           | 1          |                 
 | 2013-01-06 | M           | 3          |                 
 | 2013-01-07 | D           | 1          |                 
 | 2013-01-07 | W           | 1          |                 
 | 2013-01-07 | M           | 3          |

日付の個別の user_id の数を計算したい:

  1. その日に

  2. その日までのその週 (Week to date)

  3. その日までの月(Month to date)

1は簡単に計算できます。2 と 3 については、次のようなクエリを使用しようとしています。

SELECT
  date,
  'W' AS "time_series",
  (COUNT DISTINCT user_id) COUNT (user_id) OVER (PARTITION BY week_beg) AS "cnt"
  FROM user_subtitles

SELECT
  date,
  'M' AS "time_series",
  (COUNT DISTINCT user_id) COUNT (user_id) OVER (PARTITION BY month_beg) AS "cnt"
  FROM user_subtitles

Postgres では DISTINCT 計算にウィンドウ関数を使用できないため、このアプローチは機能しません。

GROUP BY アプローチも試しましたが、週/月全体の数値が得られるため機能しません。

この問題にアプローチする最良の方法は何ですか?

4

4 に答える 4

3

すべての行を数えます

SELECT date, '1_D' AS time_series,  count(DISTINCT user_id) AS cnt
FROM   uniques
GROUP  BY 1

UNION  ALL
SELECT DISTINCT ON (1)
       date, '2_W', count(*) OVER (PARTITION BY week_beg ORDER BY date)
FROM   uniques

UNION  ALL
SELECT DISTINCT ON (1)
       date, '3_M', count(*) OVER (PARTITION BY month_beg ORDER BY date)
FROM   uniques
ORDER  BY 1, time_series
  • week_begと列は 100% 冗長であり、それぞれmonth_begに簡単に置き換えることができます 。date_trunc('week', date + 1) - 1date_trunc('month', date)

  • あなたの週は日曜日に始まるようです (1 つずれています)。したがって、+ 1 .. - 1.

  • 句 uses を使用したウィンドウ関数のデフォルト フレームは です。それはまさにあなたが必要とするものです。ORDER BYOVERRANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

  • UNION ALLではなく、使用してくださいUNION

  • (D、W、M)の残念な選択time_seriesはうまくソートされません。最終的なものをORDER BY簡単にするために名前を変更しました。

  • このクエリは、1 日に複数の行を処理できます。カウントには、1 日のすべてのピアが含まれます。

  • についての詳細DISTINCT ON:

1 日あたり DISTINCT ユーザー

すべてのユーザーを 1 日 1 回だけカウントするには、CTEDISTINCT ON次のように使用します。

WITH x AS (SELECT DISTINCT ON (1,2) date, user_id FROM uniques)
SELECT date, '1_D' AS time_series,  count(user_id) AS cnt
FROM   x
GROUP  BY 1

UNION ALL
SELECT DISTINCT ON (1)
       date, '2_W'
      ,count(*) OVER (PARTITION BY (date_trunc('week', date + 1)::date - 1)
                      ORDER BY date)
FROM   x

UNION ALL
SELECT DISTINCT ON (1)
       date, '3_M'
      ,count(*) OVER (PARTITION BY date_trunc('month', date) ORDER BY date)
FROM   x
ORDER BY 1, 2

動的な期間にわたる DISTINCT ユーザー

いつでも相関サブクエリに頼ることができます。大きなテーブルでは遅くなる傾向があります!
前のクエリに基づいて構築します。

WITH du AS (SELECT date, user_id FROM uniques GROUP BY 1,2)
    ,d  AS (
    SELECT date
          ,(date_trunc('week', date + 1)::date - 1) AS week_beg
          ,date_trunc('month', date)::date AS month_beg
    FROM   uniques
    GROUP  BY 1
    )
SELECT date, '1_D' AS time_series,  count(user_id) AS cnt
FROM   du
GROUP  BY 1

UNION ALL
SELECT date, '2_W', (SELECT count(DISTINCT user_id) FROM du
                     WHERE  du.date BETWEEN d.week_beg AND d.date )
FROM   d
GROUP  BY date, week_beg

UNION ALL
SELECT date, '3_M', (SELECT count(DISTINCT user_id) FROM du
                     WHERE  du.date BETWEEN d.month_beg AND d.date)
FROM   d
GROUP  BY date, month_beg
ORDER  BY 1,2;

3 つのソリューションすべてのSQL Fiddle 。

より速くdense_rank()

@Clodoaldoは大幅な改善を思い付きました: window functiondense_rank()を使用します。最適化されたバージョンの別のアイデアを次に示します。毎日の重複をすぐに除外すると、さらに高速になるはずです。パフォーマンスの向上は、1 日あたりの行数に応じて大きくなります。

単純化されサニタイズされたデータ モデルに基づいて構築する - 冗長な列を使用せずに、day代わりに列名としてdate

date標準 SQL の予約語であり、PostgreSQL の基本型名であり、識別子として使用しないでください。

CREATE TABLE uniques(
   day date     -- instead of "date"
  ,user_id int
);

改善されたクエリ:

WITH du AS (
   SELECT DISTINCT ON (1, 2)
          day, user_id 
         ,date_trunc('week',  day + 1)::date - 1 AS week_beg
         ,date_trunc('month', day)::date         AS month_beg
   FROM   uniques
   )
SELECT day, count(user_id) AS d, max(w) AS w, max(m) AS m
FROM  (
    SELECT user_id, day
          ,dense_rank() OVER(PARTITION BY week_beg  ORDER BY user_id) AS w
          ,dense_rank() OVER(PARTITION BY month_beg ORDER BY user_id) AS m
    FROM   du
    ) s
GROUP  BY day
ORDER  BY day;

4 つの高速バリアントのパフォーマンスを示すSQL Fiddle 。それは、あなたにとって最速のデータ分布に依存します。
それらはすべて、相関サブクエリ バージョンの約 10 倍の速さです (相関サブクエリにとっては悪くありません)。

于 2013-04-17T05:20:15.963 に答える
2

相関サブクエリなし。SQL フィドル

with u as (
    select
        "date", user_id,
        date_trunc('week', "date" + 1)::date - 1 week_beg,
        date_trunc('month', "date")::date month_beg
    from uniques
)
select
    "date", count(distinct user_id) D,
    max(week_dr) W, max(month_dr) M
from (
    select
        user_id, "date",
        dense_rank() over(partition by week_beg order by user_id) week_dr,
        dense_rank() over(partition by month_beg order by user_id) month_dr
    from u
    ) s
group by "date"
order by "date"
于 2013-04-17T13:49:54.487 に答える
0

試す

SELECT
  * 
FROM 
(
  SELECT dates, count(user_id), 'D' as timesereis FROM users_data GROUP BY dates
  UNION
  SELECT max(dates), count(user_id), 'W' FROM users_data GROUP BY date_part('year',dates)+date_part('week',dates)
  UNION
  SELECT max(dates), count(user_id), 'M' FROM users_data GROUP BY date_part('year',dates)+date_part('week',dates)
) tEMP order by dates, timesereis

SQLFIDDLE

于 2013-04-17T04:08:37.030 に答える