Using Redundant Conditions to Unlock Indexes in MySQL
planetscale.com
planetscale.com
> Redundant conditions are nice because they require no changes to the database [...] This makes them useful for queries that are only sometimes run or where indexes can't be easily added to the main conditions
but where possible I typically prefer adding an index on the expression that the query is using (if your database supports it) since it expresses your intent more clearly. A redundant condition is more likely to get "optimized away" by other developers or changes in the query planner.
Of course if your database can't be changed then maybe you can use this trick if there is already a suitable indexed column.
If you're curious, here's a video I did on it: https://planetscale.com/courses/mysql-for-developers/indexes...
The range scan will miss all items with a created_at from 2023-12-31 00:00:01 to 2023-12-31 23:59:59
Index the thing you're querying on...
> The optimal indexing strategy always depends on the application, but in general, it's best to have indexes on the conditions you are frequently querying against. [...] This makes redundant conditions useful for queries that are only sometimes run or where indexes can't be easily added to the main conditions.
E.g.:
WHERE event_timestamp >= NOW() - ‘30 days’::INTERVAL AND (event_timestamp AT TIME ZONE ‘UTC’)::DATE >= NOW() - ‘31 days’::INTERVAL
(^This particular example probably has a better solution, but I’ve used the same pattern for DATE_TRUNC expressions and seen dramatic improvements.)
Could you expand on why it's not deterministic? I thought timestamptz was 8-byte microseconds since the epoch. Is the problem that the timestamptz uses the server timezone?
https://www.postgresql.org/docs/current/xfunc-volatility.htm...
However, the example you gave works just fine. It's all just UTC in backend and very much deterministic.
postgres=# create table timetest as select t.time, 'hi' as foo from generate_series('2000-01-01'::timestamptz, '2010-01-01'::timestamptz, '5 minutes'::interval) t(time);
SELECT 1052065
postgres=# create index on timetest(time);
CREATE INDEX
postgres=# explain analyze select * from timetest where time > now() - '30 days'::interval;
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ QUERY PLAN │
├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Index Scan using timetest_time_idx on timetest (cost=0.43..4.45 rows=1 width=11) (actual time=0.019..0.019 rows=0 loops=1) │
│ Index Cond: ("time" > (now() - '30 days'::interval)) │
│ Planning Time: 1.163 ms │
│ Execution Time: 0.081 ms │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
If you query frequently with date_trunc, you may want to use an expression index: postgres=# create index on timetest(date_trunc('day', time at time zone 'US/Pacific'));
CREATE INDEX
postgres=# explain analyze select * from timetest where date_trunc('day', time at time zone 'US/Pacific') > now() - '30 days'::interval;
┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ QUERY PLAN │
├───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Index Scan using timetest_date_trunc_idx on timetest (cost=0.43..4.45 rows=1 width=11) (actual time=0.075..0.076 rows=0 loops=1) │
│ Index Cond: (date_trunc('day'::text, timezone('US/Pacific'::text, "time")) > (now() - '30 days'::interval)) │
│ Planning Time: 0.379 ms │
│ Execution Time: 0.124 ms │
└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
Note that to make this work you have to convert "time" to a timestamp (no tz), because date_trunc with timestamptz is not an immutable function and cannot be indexed—I believe this is what you may have been thinking of when you say that timestamptz is non-deterministic.But, because the index-compatible condition is too broad, you still need to have the "original" one to get the correct results. If your new, "redundant", condition gets the exact correct results then you're not doing this pattern, you're just replacing a query that can't use an index with one that can, and in that case sure, it doesn't make sense to keep the old one around.