这是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'
答案 0 :(得分:3)
以下答案均使用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
不唯一,那么请考虑使用rank
或dense_rank
窗口函数而不是row_number
来获得确定性结果,或者在ORDER BY
中为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]));
答案 1 :(得分: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'
..会正常工作,但是关于性能的反模式。如果将强制转换或函数应用于表达式中的数据库列,则不能使用纯索引。
最佳解决方案: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);
答案 2 :(得分:-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;