SQL data sources and macros

Grafana · Practical · 10 min

Business metrics often live in relational databases, and Grafana can chart them next to system metrics.

Grafana ships PostgreSQL, MySQL and Microsoft SQL Server data sources. Configure them with a read-only database user: dashboard users can run any query the data source allows.

A query can be formatted as a table or a time series. For time series, return a column named time (a timestamp or epoch), one or more numeric value columns, and optionally a text column used as the series name (metric). Sort by time.

Macros insert the dashboard's time range and interval into SQL:

  • $__timeFilter(created_at) becomes a BETWEEN condition for the selected range, so the database can use an index on that column.
  • $__timeGroupAlias(created_at, $__interval) buckets timestamps into intervals and aliases the column as time.
  • $__timeFrom() and $__timeTo() give the range bounds; $__unixEpochFilter() handles integer timestamps.

Dashboard variables work in SQL too, including query variables like SELECT DISTINCT country FROM customers. Multi-value variables are rendered as a comma-separated quoted list for IN ($country).

Keep queries cheap: always filter with $__timeFilter, aggregate in SQL, and point Grafana at a replica rather than the primary production database.

Example

-- Orders per interval by country, as a time series panel
SELECT
  $__timeGroupAlias(created_at, $__interval),
  country AS metric,
  count(*) AS orders
FROM orders
WHERE $__timeFilter(created_at)
  AND country IN ($country)          -- multi-value variable
GROUP BY 1, 2
ORDER BY 1;

-- Expands roughly to (PostgreSQL):
-- SELECT floor(extract(epoch from created_at)/60)*60 AS "time", ...
-- WHERE created_at BETWEEN '2026-10-17T06:00:00Z' AND '2026-10-17T12:00:00Z'
--   AND country IN ('DE','FR')

Remember

  • Time series queries return time, value columns and an optional metric column
  • $__timeFilter and $__timeGroupAlias inject the dashboard range and interval
  • Use a read-only user and preferably a replica

Watch out for

  • Forgetting $__timeFilter, so every refresh scans the whole table.

Check yourself

  1. What does $__timeFilter(created_at) do?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: grafana.com/docs/grafana/latest/datasources/postgres/

Builds on

Unlocks

Nothing builds on this yet.