3

这是一个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'
4

3 回答 3

3

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

此 SQLFiddle 显示了三个可能的答案:http://sqlfiddle.com/#!12/0fd51/14date_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 不支持LIMIT在子查询中使用的子查询IN。不过,它适用于 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不是唯一的,则考虑使用rankor 或dense_rank窗口函数而不是row_number获得确定性结果,或者在 in 中添加一个附加子句ORDER BYrow_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]));
于 2012-11-16T05:41:44.377 回答
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 解决方案

最佳解决方案升级到更新版本,最好升级到当前版本 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()- 就像我在此处发布的内容(包括指向更多的链接)

在我们有窗口函数之前,我可能已经用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);
于 2012-11-16T09:54:05.983 回答
-3

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

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; 
于 2012-11-16T04:27:57.487 回答