I am pretty sure, @kgrittn's interpretation of the question is accurate and I love his elegant use of a row constructors. Even more so after I have tested a couple of alternatives, none of which could match the performance:
Tested with a real life table of 65426 rows; 32107 qualified. PostgreSQL 9.1.4, best of five with EXPLAIN ANALYZE
:
SELECT * FROM tbl
WHERE to_char(data, 'MMDD') BETWEEN '0215' AND '0621';
Total runtime: 251.188 ms
SELECT * FROM tbl
WHERE to_char(data, 'MMDD')::int BETWEEN 215 AND 621;
Total runtime: 250.965 ms
SELECT * FROM tbl
WHERE to_char(data, 'MMDD') COLLATE "C" BETWEEN '0215' AND '0621';
Total runtime: 221.732 ms
String comparison is faster with the "non-locale" C
- more in the manual about collation support.
SELECT * FROM tbl
WHERE EXTRACT(MONTH FROM data)*100 + EXTRACT(DAY FROM data)
BETWEEN 215 AND 621;
Total runtime: 209.965 ms
SELECT * FROM tbl
WHERE EXTRACT(MONTH FROM data) BETWEEN 3 AND 5
OR EXTRACT(MONTH FROM data) = 2 AND EXTRACT(DAY FROM data) >= 15
OR EXTRACT(MONTH FROM data) = 6 AND EXTRACT(DAY FROM data) <= 21;
Total runtime: 160.169 ms
SELECT * FROM tbl
WHERE EXTRACT(MONTH FROM data) BETWEEN 2 AND 6
AND CASE EXTRACT(MONTH FROM data)
WHEN 2 THEN EXTRACT(DAY FROM data) >= 15
WHEN 6 THEN EXTRACT(DAY FROM data) <=21
ELSE TRUE END;
Total runtime: 147.390 ms
SELECT * FROM tbl
WHERE CASE EXTRACT(MONTH FROM data)
WHEN 3 THEN TRUE
WHEN 4 THEN TRUE
WHEN 5 THEN TRUE
WHEN 2 THEN EXTRACT(DAY FROM data) >= 15
WHEN 6 THEN EXTRACT(DAY FROM data) <= 21
ELSE FALSE END;
Total runtime: 131.907 ms
@Kevin's solution with row constructors:
SELECT * FROM tbl
WHERE (EXTRACT(MONTH FROM data), EXTRACT(DAY FROM data))
BETWEEN (2, 15) AND (6, 21);
Total runtime: 125.460 ms
Chapeau.
Faster with functional index
The only way to beat that is with indexes. None of the queries above can use a plain index on data
. However, if read performance is crucial (and for a small cost on write performance) you can resort to a functional index:
CREATE INDEX ON tbl(EXTRACT(MONTH FROM data), EXTRACT(DAY FROM data));
SELECT * FROM tbl
WHERE (EXTRACT(MONTH FROM data), EXTRACT(DAY FROM data))
BETWEEN (2, 15) AND (6, 21);
Total runtime: 85.895 ms
And that's where I can finally beat Kevin's query by a hair: with a single column index instead of the multi-column index needed in his case.
CREATE INDEX ON tbl(
CAST(EXTRACT(MONTH FROM data) * 100 + EXTRACT(DAY FROM data) AS int));
SELECT * FROM tbl
WHERE (EXTRACT(MONTH FROM data) * 100 + EXTRACT(DAY FROM data))::int
BETWEEN 215 AND 621;
Total runtime: 84.215 ms