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 aBETWEENcondition 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 astime.$__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.