Parameters
Parameters let one query serve many inputs. Write $1, $2, ... where a value would go, store defaults
with the query, and override any of them at execution time. Parameter values never become part of the SQL
text: they are bound separately, so a value can never change what the query does.
SELECT variable, value FROM device($1) AS d
WHERE value > $2
ORDER BY time DESC LIMIT 50
Here $1 picks which of your devices to query and $2 is the threshold. Execute it with different values
each time without touching the query.
Where you can use them
- In place of values: comparisons (
value > $1),INlists,BETWEENbounds, andLIKE/ILIKEpatterns. - As the device or entity id:
device($1)andentity($1)let one stored query target different resources per execution. The id goes through the same ownership check as a literal id; a device you do not own fails with404.
Supplying values
Parameters travel as an array of { key, value } pairs, and all values are strings. The server converts
each one based on where it is used (number, timestamp, boolean, text):
{
"params": [
{ "key": "$1", "value": "DEVICE_ID" },
{ "key": "$2", "value": "30" }
]
}
- Stored defaults must cover every placeholder the query uses.
- At execution time you can override any subset: values you send win, stored defaults fill the rest.
Paginating with parameters
LIMIT and OFFSET take fixed numbers in the query text, and a stored query has fixed text, so a classic
skip pattern (LIMIT 100 OFFSET 200) would need one stored query per page. Page through a parameter
cursor instead: keep the LIMIT fixed and pass the last timestamp you saw as $1.
SELECT variable, value, time FROM device('DEVICE_ID') AS d
WHERE time < $1
ORDER BY time DESC LIMIT 100
{ "params": [{ "key": "$1", "value": "2026-07-17T10:00:00Z" }] }
The cursor form is also the faster one: a deep OFFSET makes the database scan and discard every skipped
row on each page, while the cursor jumps straight to the right position. Prefer it for anything beyond a few
pages. Fleet queries (device_data_by_tag) paginate over devices with the after_device body field
instead; see Executing Queries.
Rules
- Only
$nplaceholders, numbered1..Nwith no gaps. Named (:name) and anonymous (?) styles are rejected. - Dates accept ISO format (
2026-01-01or2026-01-01T00:00:00Z); booleans accepttrue/false/1/0. - An invalid value returns a
400naming the placeholder (for example,Invalid value for parameter $1); the value itself is never echoed back.