【问题标题】:How to select more than 1 record per day?如何每天选择多于 1 条记录?
【发布时间】:2012-11-04 19:23:34
【问题描述】:

这是一个postgresql 问题。

PostgreSQL 8.3.3 on i686-redhat-linux-gnu, compiled by GCC gcc (GCC) 3.4.6 20060404 (Red Hat 3.4.6-9).

表格如下:

date_time           other_column
2012-11-01 00:00:00 ...
2012-11-02 01:00:00 ...
2012-11-02 02:00:00 ...
2012-11-02 03:00:00 ...
2012-11-02 04:00:00 ...
2012-11-03 05:00:00 ...
2012-11-03 06:00:00 ...
2012-11-05 00:00:00 ...
2012-11-07 00:00:00 ...
2012-11-07 00:00:00 ...
...

我想从特定日期范围内选择每天最多 3 条记录

例如,我想从 2012-11-02 到 2012-11-05 最多选择 3 条记录。 expected result 将是:

date_time           other_column
2012-11-02 01:00:00 ...
2012-11-02 02:00:00 ...
2012-11-02 03:00:00 ...
2012-11-03 05:00:00 ...
2012-11-03 06:00:00 ...
2012-11-05 00:00:00 ...

我已经花了几个小时在这上面,但仍然无法弄清楚。请帮我。 :(

更新: 我尝试的当前sql每天只能选择一条记录:

SELECT DISTINCT ON (TO_DATE(SUBSTRING((date_time || '') FROM 1 FOR 10), 'YYYY-MM-DD')) *
FROM myTable
WHERE  date_time >=  '20121101 00:00:00'  
AND  date_time <= '20121130 23:59:59'

【问题讨论】:

  • 请添加您尝试过的相关java代码..
  • 这其实是一个sql问题。
  • 这是每组最大 n 的问题吗?见stackoverflow.com/questions/751399/…。我添加了该标签,请发布当前查询
  • 看来您的意思是每天最多 3 条记录。正确的?另外:最多 3 条记录根据什么标准?选择任意记录?最伟大的?至少?什么?看起来你用得最少,但最好指定一下。
  • (TO_DATE(SUBSTRING((date_time || '') FROM 1 FOR 10), 'YYYY-MM-DD') 似乎是写date_trunc('day', date_time) 的一种特别可怕的方式。见postgresql.org/docs/current/static/functions-datetime.htmlsqlfiddle.com/#!12/0fd51/13

标签: sql postgresql greatest-n-per-group postgresql-8.3


【解决方案1】:

我会使用子选择和左外连接。这应该可以解决问题:

select distinct(date_format(a.date_time,"%Y-%m-%d")) date_time, b.* from table a
left outer join (
  select date_format(date_time,"%Y-%m-%d") dt, * from table limit 3
) b 
on date_format(a.date_time,"%Y-%m-%d") = b.dt; 

【讨论】:

  • @BhavikAmbani 不清楚你在这里想说什么。也就是说,上面的代码显然是错误的,因为它使用"double quotes"(标识符引用)作为'literal strings',每个sqlfiddle.com/#!12/0fd51/4
  • @CraigRinger 这就是我的意思
  • @Tiit 一旦我纠正了明显的错误,这仍然失败;它似乎是使用 MySQL date_format 函数的仅 MySQL 查询。在 PostgreSQL 中,可以使用to_char 而不是date_format,但在这种情况下,您实际上是在将时间戳截断为日期,因此您应该使用date_trunc。报价更正后的您的:sqlfiddle.com/#!12/0fd51/8。它似乎也完全丢弃了时间部分;更正日期截断后请参见此处:sqlfiddle.com/#!12/0fd51/9
  • 子查询外部查询之前被评估。这个查询在 any RDBMS 中完全是无稽之谈。
【解决方案2】:

以下答案均使用date_trunc('day',date_time) 或仅转换为date 将时间戳截断为日期。无需跳过日期格式和字符串。请参阅手册中的Date/time functions

此 SQLFiddle 显示了三个可能的答案:http://sqlfiddle.com/#!12/0fd51/14,所有这些都为输入数据产生相同的结果(但如果date_time 中可能有重复,则不一定是相同的结果)。

要解决您的问题,您可以使用具有限制的相关子查询来生成要过滤的 IN 列表:

SELECT a.date_time, a.other_column
FROM table1 a
WHERE a.date_time IN (
  SELECT b.date_time
  FROM table1 b
  WHERE b.date_time IS NOT NULL
    AND a.date_time::date = b.date_time::date
  ORDER BY b.date_time
  LIMIT 3
)
AND a.date_time::date BETWEEN '2012-11-02' AND '2012-11-05';

这应该是最便携的方法——尽管它不适用于 MySQL(至少从 5.5 开始),因为MySQL doesn't support LIMIT in a subquery used in an IN clause。不过,它适用于 SQLite3 和 PostgreSQL,并且应该适用于大多数其他数据库。

另一个选项是选择您想要的日期范围,使用窗口函数用行号注释范围内的行,然后过滤输出以排除多余的行:

SELECT date_time, other_column
FROM (
  SELECT 
    date_time, 
    other_column, 
    rank() OVER (PARTITION BY date_trunc('day',date_time) ORDER BY date_time) AS n
  FROM Table1
  WHERE date_trunc('day',date_time) BETWEEN '2012-11-02' AND '2012-11-05'
  ORDER BY date_time
) numbered_rows
WHERE n < 4;

如果有可能出现联系,即如果date_time 不是唯一的,则考虑使用rankdense_rank 窗口函数而不是row_number 来获得确定性结果,或者在@ 中添加一个附加子句987654335@ row_number 打破平局。

如果您使用rank,那么如果它不能容纳所有行,它将不包含任何行;如果您使用dense_rank,它将包括所有这些,即使它必须超过每天 3 行的限制才能这样做。

使用窗口规范也可以通过这种方式进行各种其他处理。


这是另一个使用数组聚合和切片的公式,它完全是 PostgreSQL 特有的,但很有趣。

SELECT b.date_time, b.other_column 
FROM (
  SELECT array_agg(a.date_time ORDER BY a.date_time)
  FROM table1 a
  WHERE a.date_time::date BETWEEN '2012-11-02' 
    AND '2012-11-05'
  GROUP BY a.date_time::date
) x(arr) 
INNER JOIN table1 b ON (b.date_time = ANY (arr[1:3]));

【讨论】:

    【解决方案3】:

    我想从特定日期范围内每天选择最多 3 条记录

    SELECT date_time, other_column
    FROM  (
       SELECT *, row_number() OVER (PARTITION BY date_time::date) AS rn
       FROM   tbl
       WHERE  date_time >= '2012-11-01 0:0'
       AND    date_time <  '2012-12-01 0:0'
       ) x
    WHERE  rn < 4;
    

    要点

    • 使用窗口函数row_number()。根据问题,rank()dense_rank() 将是错误的 - 可能会选择 3 条以上的记录且时间戳重复。

    • 由于您没有定义每天需要哪些 行,因此正确的答案是不在窗口函数中包含ORDER BY 子句。为您提供与问题匹配的任意选择。

    • 我从

      更改了您的 WHERE 子句
      WHERE  date_time >= '20121101 00:00:00'  
      AND    date_time <= '20121130 23:59:59'
      

      WHERE  date_time >=  '2012-11-01 0:0'  
      AND    date_time <   '2012-12-01 0:0'
      

      对于像'20121130 23:59:59.123' 这样的极端情况,您的语法会失败。

      @Craig 的建议:

      date_time::date BETWEEN '2012-11-02' AND '2012-11-05'
      

      .. 可以正常工作,但在性能方面是一种反模式。如果在表达式中对数据库列应用强制转换或函数,则不能使用普通索引。

    PostgreSQL 8.3 解决方案

    最佳解决方案Upgrade to a more recent version, preferably to the current version 9.2.

    其他解决方案

    只有几天你可以雇用UNION ALL

    SELECT date_time, other_column
    FROM   tbl t1
    WHERE  date_time >= '2012-11-01 0:0'
    AND    date_time <  '2012-11-02 0:0'
    LIMIT  3
    )
    UNION ALL 
    (
    SELECT date_time, other_column
    FROM   tbl t1
    WHERE  date_time >= '2012-11-02 0:0'
    AND    date_time <  '2012-11-03 0:0'
    LIMIT  3
    )
    ...
    

    括号在这里不是可选的。

    更多天generate_series() 的解决方法 - 就像我发布的 here (including a link to more)

    在我们有窗口函数之前,我可能已经用 plpgsql 函数解决了这个问题:

    CREATE OR REPLACE FUNCTION x.f_foo (date, date, integer
                             , OUT date_time timestamp, OUT other_column text)
      RETURNS SETOF record AS
    $BODY$
    DECLARE
        _last_day date;          -- remember last day
        _ct       integer := 1;  -- count
    BEGIN
    
    FOR date_time, other_column IN
       SELECT t.date_time, t.other_column
       FROM   tbl t
       WHERE  t.date_time >= $1::timestamp
       AND    t.date_time <  ($2 + 1)::timestamp
       ORDER  BY t.date_time::date
    LOOP
       IF date_time::date = _last_day THEN
          _ct := _ct + 1;
       ELSE
          _ct := 1;
       END IF;
    
       IF _ct <= $3 THEN
          RETURN NEXT;
       END IF;
    
       _last_day := date_time::date;
    END LOOP;
    
    END;
    $BODY$ LANGUAGE plpgsql STABLE STRICT;
    
    COMMENT ON FUNCTION f_foo(date3, date, integer) IS 'Return n rows per day
    $1 .. date_from (incl.)
    $2 .. date_to  (incl.)
    $3 .. maximim rows per day';
    

    呼叫:

    SELECT * FROM f_foo('2012-11-01', '2012-11-05', 3);
    

    【讨论】:

    • 重铸与时间范围的好点。我用rankdense_rank 讨论了这些问题;我只是建议他们作为决胜局,如果相当不明确的问题结果需要更多确定性的行选择,而代价是总是得到 3 行。
    • 执行查询时出错。错误:“OVER”处或附近的语法错误postgresql.org/docs/9.1/static/tutorial-window.html 我尝试将其修剪为只运行 SELECT *, row_number() OVER() AS rn FROM mytable WHERE date_time>= '2012-11-01 0:0' AND date_time
    • @Drogba:您似乎忽略了提及您过时的 PostgreSQL 版本。 PostgreSQL 8.4 引入了窗口函数。
    • omg,i686-redhat-linux-gnu 上的 PostgreSQL 8.3.3,由 GCC gcc (GCC) 3.4.6 20060404 (Red Hat 3.4.6-9) 编译。有什么选择吗?
    • @Drogba:我为 8.3 添加了解决方案。下次请记得立即提及您的版本号。
    猜你喜欢
    • 2013-05-05
    • 1970-01-01
    • 2023-03-05
    • 2022-01-24
    • 2012-01-22
    • 1970-01-01
    • 1970-01-01
    • 2020-11-02
    • 1970-01-01
    相关资源
    最近更新 更多