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. 截至该日期的那一周(截至日期)

  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_begmonth_beg是 100 % 冗余的,可以很容易地分别替换为 date_trunc('week', date + 1) - 1date_trunc('month', date)

  • 您的一周似乎从星期日开始(减一),因此+ 1 .. - 1.

  • 在子句中使用的窗口函数的默认框架是. 这正是你所需要的。ORDER BYOVERRANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

  • 使用UNION ALL,不使用UNION

  • 您对time_series(D, W, M) 的不幸选择排序不好,我重命名以使最终ORDER BY更容易。

  • 此查询每天可以处理多行。计数包括一天的所有同龄人。

  • 更多关于DISTINCT ON

每天有不同的用户

要每天只计算每个用户一次,请使用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;

SQL Fiddle适用于所有三种解决方案。

更快dense_rank()

@Clodoaldo提出了一项重大改进:使用窗口函数dense_rank()。这是优化版本的另一个想法。立即排除每日重复项应该更快。性能增益随着每天的行数而增长。

建立在简化和清理的数据模型上 - 没有冗余列 -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;

SQL Fiddle展示了 4 个更快变体的性能。这取决于您的数据分布,这对您来说是最快的。
所有这些都大约是相关子查询版本的 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 回答
-1

尝试这样的查询

SELECT count(distinct user_id), date_format(date, '%Y-%m-%d') as date_period
FROM uniques
GROUP By date_period
于 2013-04-17T04:05:22.817 回答