I would like to create an Ecto query that filters records in a children
table by their age (i.e. "minimum age (months) -> maximum age (months)".
One simple way to do this would be the Ecto date_add
feature:
from c in Child, where: c.birthday >
datetime_add(^Ecto.DateTime.utc, -1, "month")
The issue with this is that not all children will be on the same time zone, and certainly not all on Etc/UTC
. This query would be pretty close, but not spot on (some would be off by a day).
I've been trying to use PostgreSQL's interval
functionality to make this query work. I can get it to work using an SQL client, but I'm getting interpolation issues when trying to interpolate values in a fragment.
This works (a child's time zone is sourced from it's location
association):
query = from ch in Child,
join: loc in assoc(ch, :location),
where: ch.birthday <= fragment("(now() AT TIME ZONE ?)::date - interval '2 months'", loc.time_zone)
Repo.all(query)
Note that I've hard-coded in the '2 months'
interval.
I thought this would work, but does not:
query = from ch in Child,
join: loc in assoc(ch, :location),
where: ch.birthday <= fragment("(now() AT TIME ZONE ?)::date - interval ?", loc.time_zone, ^"2 months")
Repo.all(query)
Note that I'm trying to use Ecto's query interpolation to bring in the '2 months'
value into the query.
The error is as follows:
[debug] QUERY ERROR source="children" db=1.7ms queue=0.1ms
SELECT c0."id", (... other properties) FROM "children" AS c0 INNER JOIN "programs" AS p2 ON p2."id" = c0."program_id" INNER JOIN "locations" AS l1 ON l1."id" = p2."location_id" WHERE (c0."birthday" <= (now() AT TIME ZONE l1."time_zone")::date - interval $1) ["2 months"]
** (Postgrex.Error) ERROR 42601 (syntax_error): syntax error at or near "$1"
(ecto) lib/ecto/adapters/sql.ex:436: Ecto.Adapters.SQL.execute_and_cache/7
(ecto) lib/ecto/repo/queryable.ex:130: Ecto.Repo.Queryable.execute/5
(ecto) lib/ecto/repo/queryable.ex:35: Ecto.Repo.Queryable.all/4
The part of the query that fails (I tried the same query in an SQL client) is:
(now() AT TIME ZONE l1."time_zone")::date - interval $1)
It doesn't like the $1
part right there. Is it impossible to interpolate values into this kind of query?
I tried using single-quotes in an SQL client, but got the same errors. I tried the following:
SELECT c0."id" FROM "children" AS c0 INNER JOIN "programs" AS p2 ON p2."id" = c0."program_id" INNER JOIN "locations" AS l1 ON l1."id" = p2."location_id" WHERE (c0."birthday" <= (now() AT TIME ZONE l1."time_zone")::date - interval $1) ['2 months']
Any help would be appreciated!
I needed to do exactly this a while ago and ended up using the fact that you can multiply intervals with
$1
.So, this should work: