Skip to content

Queries as fallback

The primary way to feed a Plot block is a database view: the database team owns the SQL, the dashboard references the view name, and PlotPress runs SELECT * FROM <view> WHERE .... That’s the path you should use whenever it’s available.

When it isn’t — when you can’t get the view created in time, when you’re prototyping, or when the database doesn’t support views (e.g. an S3 bucket via DuckDB) — PlotPress falls back to a SQL file in the dashboard’s queries/ folder. Treat this as a workaround: it pulls data-shape decisions out of the database and into the dashboard, where they’re harder to reuse and harder to audit.

✅ Acceptable:

  • The database doesn’t expose views (S3, raw Parquet, some MongoDB collections).
  • You’re prototyping and the view will be created next sprint.
  • The query needs a parameter the view can’t express.

❌ Not acceptable as a long-term home for:

  • Business-logic SQL that other dashboards or tools also need. Push it into a view.
  • Joins across many tables that change often. Push them into a view.
  • Anything you’d want to test in a dbt project.
-- queries/monthly_revenue.sql
-- @param year int default 2026
select
date_trunc('month', invoice_date) as month,
sum(amount) as revenue,
currency
from invoices
where extract(year from invoice_date) = :year
group by 1, 3
order by 1;

Two conventions:

  1. Parameters are declared in -- @param headers. Each line gives a name, a type, and a default.
  2. Bind syntax is :name. The backend rewrites these to the driver’s native placeholder ($1 for Postgres, ? for SQLite/MySQL, {name} for ClickHouse).
TypeValidated asBound as
intsafe integerint
floatfinite numberfloat
stringUTF-8 stringstring
dateYYYY-MM-DDdate
timestampRFC 3339timestamp
booltrue/falsebool
enum(a,b,c)one of the listed valuesstring
```plot query=monthly_revenue
Plot.barY(data, { x: "month", y: "revenue" })
```

query= instead of view=. The runtime knows which to look up.

  • No string interpolation. :year is a parameter; ${year} is a typo. Anything that looks like dynamic SQL must be a parameter or a separate query file.
  • No DDL, no DML. Queries are select-only. The backend rejects any non-SELECT at parser level.
  • No multi-statement queries. One statement per file.

These constraints exist to keep the security model small. The set of things a viewer can cause to happen is exactly the set of -- @param permutations across queries/*.sql.