Pro ยท Lesson 5 of 9

SQL queries in Metabase

Native questions use variables, field filters, and optional clauses so a dashboard can drive SQL safely.

Sign in to save your place.

Picture it this way

A SQL question is a letter with blanks. {{status}} is a blank the reader fills in. A field filter is a smarter blank: Metabase knows it is a real column, so it can offer a date picker or a dropdown. Double brackets around a line mean 'leave this line out if the blank is empty'.

Native questions and variables

Use the query builder when a reader should change the steps. Use a native SQL question when the logic needs a join, a window, or a warehouse function the builder does not express. Save it in a collection like any other question. It can still sit on a dashboard.

A variable is written as {{variable}}. In the variable sidebar, set the type. Text, number, and date variables are substituted as values, not as raw SQL fragments, so a reader cannot end the statement and start a new one by typing into the box. That is the point of the widget.

SELECT country, SUM(amount) AS revenue
FROM orders
WHERE status = {{status}}
  AND ordered_at >= {{start_date}}
GROUP BY country
ORDER BY revenue DESC

Field filters and optional lines

A field filter connects the widget to a real column. You write the column once and let Metabase build the predicate, including "is empty" and date ranges. The SQL names the field, and the widget decides the operator.

SELECT id, country, amount
FROM orders
WHERE {{created_at}}
  [[AND country = {{country}}]]

The line inside [[ ]] is omitted when Country is empty. Without those brackets, an empty widget becomes a filter for blank country, or the query fails. Use optional clauses for filters people are allowed to skip.

Snippets are reusable SQL fragments saved in Metabase. Use them for a join you repeat. Do not hide a permission check inside a snippet and assume it is security. Permissions are the admin lesson. A snippet is only text.

Do not concatenate SQL in the warehouse call

If you need a dynamic identifier such as a schema name, that is not a value variable. Keep identifiers in the saved SQL. Let widgets pass values only.

Three kinds of blank

Variables in double curly brackets turn a SQL query into a reusable filter.

  1. Text variable{{city}} becomes a typed value
  2. Field filter{{created}} maps to a column
  3. Optional clause[[ ... ]] disappears when empty

Step 1 of 3A text or number variable is replaced with whatever the reader types into the filter box.

SELECT * FROM orders WHERE city = {{city}}

Practice

Save a native question with one required text variable and one optional clause. Run it with the optional filter empty, then filled. Both runs should succeed, and only the second should restrict that column.

Knowledge check

What does {{status}} pass into the query?

What do [[ and ]] do?

When should you prefer a field filter?

Official sources

Checked against vendor documentation in October 2026. Screens and plan names still change, so confirm the control in your own instance.