Is there any way to make it faster?
Oh yes, much faster. (Updated 2021.)
Basic assessment
If you need this often and fast, and writes to the table are either few or predictable (like: new rows always have current time), a materialized view would be fastest, like @Roger suggested. But you still need a query to implement it. And the queries I am going to suggest are so fast that you might skip the MV ...
In related cases there is often a look-up table with candidate values allowing for a much faster query:
Assumptions for this case:
- Postgres 9.4 or later.
creation_date
is data type timestamp
(works for date
or timestamptz
too).
- Actual range of timestamps is unknown.
- There is a btree index on
acs_objects(creation_date)
.
Emulate loose index scan with rCTE
If you neither have a look-up table nor a derived table with candidate values, there is still a very fast alternative. Basically, you need to emulate an "index skip scan", a.k. a."loose index scan". This query works in any case:
WITH RECURSIVE cte AS (
SELECT date_trunc('year', max(creation_date)) AS y
FROM acs_objects
UNION ALL
SELECT (SELECT date_trunc('year', max(creation_date))
FROM acs_objects
WHERE creation_date < cte.y)
FROM cte
WHERE cte.y IS NOT NULL
)
SELECT to_char(y, 'YYYY') AS year
FROM cte
WHERE cte.y IS NOT NULL;
Probably fastest: top-down, and truncate each timestamp to the start of the year, then find the latest of the earlier rows; repeat.
Details for this technique:
Based on generate_series()
valex's idea can be implemented more efficiently with generate_series()
producing timestamp
values based on the actual range of existing years:
SELECT to_char(y, 'YYYY') AS year
FROM (
SELECT generate_series(date_trunc('year', min(creation_date))
, max(creation_date)
, interval '1 year')
FROM acs_objects
) t(y)
WHERE EXISTS (
SELECT FROM acs_objects
WHERE creation_date >= y
AND creation_date < y + interval '1 year'
);
db<>fiddle here demonstrating both.
Old slfiddle
If there are few gaps in the range of years, this may be faster, yet. But either should only take a few milliseconds or less, regardless of the table size.
Related: