Искам да избера най-много 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'
.. би работил правилно, но е анти-модел по отношение на производителността. Ако приложите cast или функция към колоната на вашата база данни в израза, обикновените индекси не могат да се използват.
Решение за 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);