Articles
Metabase Interview Questions: Models, SQL Parameters, and Dashboard Permissions
Practice Metabase models, basic variables, field filters, alias mapping, dashboard permissions, and Transform migrations with concrete query and access checks.
A dashboard's date filter works in one Metabase question and generates invalid SQL in another. Meanwhile, a restricted viewer can open a saved SQL question containing rows they should never see. These failures involve different contracts: parameter expansion and content permissions.
Metabase interview questions become more useful when you separate the reusable data definition, the SQL that filters it, and the paths through which people can access its results. This guide uses an aliased order query and a two-persona access exercise to make those boundaries testable.
Evidence boundary: Official facts are linked to current Metabase documentation, whose latest pages identify v0.63 at the time of review. Examples and interview answers are original exercises and engineering inference. No candidate reports establish a company-specific interview format. Check your installed version and plan before relying on feature availability or migration behavior.
For a related implementation exercise, use Design an RBAC Relational Schema; the practice table below explains its connection to this topic.
What is a model, and is it a materialized table?
Official fact: Metabase models are curated starting points for questions, based on SQL or query-builder questions and enriched with column metadata. Persistence is a separate capability. Current documentation recommends considering Transforms, describes them as successors to models, and says conversion creates a persistent output table and repoints dependent questions and dashboards. See the models documentation.
That version note changes a good interview answer. Do not describe every model as a materialized table, or assume a model and a transform have identical freshness. Explain the existing deployment first, then distinguish the reusable definition from any configured persistence or scheduled refresh.
Original exercise: analysts use an “Eligible Orders” model that returns order_id, status, total, and created_at. Its business contract is one row per eligible order. Add a column description explaining whether total includes refunds and whether created_at is the order's creation time or payment time.
Engineering inference: Metadata improves discoverability and query construction, but it does not repair duplicate rows or ambiguous eligibility rules. Test the output key and row count independently. If a join introduces multiple rows per order, changing a column's displayed semantic type cannot restore the intended grain.
A model is also not a machine-learning model merely because it shares the name. Start with the business entity, row grain, and intended reuse. That explanation gives both technical and nontechnical interviewers a clear basis for follow-up questions.
If the model contains a calculated timestamp, test that consumers can filter it in the way the dashboard requires. A useful-looking column without the necessary metadata or query support can become a frustrating starting point for self-service analysis.
How do basic SQL variables differ from field filters?
Official fact: Metabase supports SQL parameters in native questions. A basic variable supplies a value, while a field filter connects to a database field and can produce a predicate appropriate to the selected values. The SQL parameter guide describes the parameter types and dashboard connections.
The difference is visible in these original snippets:
WHERE o.status = {{status}}
WHERE {{created_at}}
In the first snippet, the query supplies the column and equality operator. In the second, a field-filter variable supplies the filtering expression. Treating a field filter as a scalar and writing o.created_at = {{created_at}} misunderstands the expansion contract.
Official fact: The field-filter documentation explicitly omits the column and operator from field-filter syntax. It also requires an alias mapping when the referenced table uses an alias.
Engineering inference: Choose a field filter when its database-field mapping and supported widget behavior match the user's task. Use a basic variable when the question requires a value without that mapping, and choose its type deliberately. A text variable called “date” does not automatically become a correctly connected date-range filter.
Do not manually construct user-selected table names or sort expressions as though they were ordinary scalar values. A value parameter and an SQL identifier occupy different syntactic roles. For dynamic query structure, define supported choices explicitly and review the generated query using the actual database dialect.
The interview follow-up is, “Why can't I just quote every variable?” Because the correct expansion depends on the variable type. Adding quotes around a generated predicate or around a value already handled by the parameter mechanism can produce incorrect SQL. Diagnose the type and mapping before changing punctuation.
Build an optional filter without leaving broken SQL
Use this original native query:
SELECT o.order_id, o.status, o.total, o.created_at
FROM orders AS o
WHERE 1 = 1
[[AND o.status = {{status}}]]
[[AND {{created_at}}]]
Configure status as a basic text variable. Configure created_at as a field filter mapped to the database's orders.created_at field, with table-and-field alias o.created_at. The exact timestamp syntax generated for the range depends on the driver and selected filter.
Official fact: Metabase's optional-variable documentation uses double brackets to make a clause optional. The surrounding SQL must remain valid when that clause is removed.
The WHERE 1 = 1 scaffold here keeps the remaining AND clauses syntactically valid. It is not a security rule or a performance optimization. Explain its limited purpose.
| Original test input | Required expansion behavior | What to verify |
|---|---|---|
| Neither filter set | Both optional clauses disappear | All otherwise eligible rows remain; SQL stays valid |
| Status = paid | Equality clause uses the paid value | Cancelled rows are excluded |
| Date range only | Field filter emits a date predicate on o.created_at | Both boundaries use the intended timestamp semantics |
| Both filters set | Both clauses remain | Results satisfy their intersection |
Query alias changed to ord | Mapping changes with the alias | Generated predicate references a table visible in the query |
Suppose the variable remains mapped to orders.created_at, but the native query now uses orders AS o. The generated predicate can refer to a table name that is no longer available in that scope. Changing a dashboard label will not fix that problem; correct the alias mapping.
A second failure occurs when only the value is optional: AND o.status = [[{{status}}]]. If the value disappears while the operator remains, the query is incomplete. Make the complete conditional clause optional, then test the empty-filter case.
Engineering inference: Save the generated SQL or equivalent inspected expansion for the four input combinations. Test with a row exactly at the start boundary, one just before it, one at the end boundary, and a null timestamp. State whether your intended range is closed or half-open rather than letting a chart label hide the choice.
These fixture tests verify query logic. They do not certify every Metabase driver, widget, or time-zone configuration. A final deployment check must run the actual saved question with its configured variables.
How do dashboard filters connect to native questions?
A filter widget needs a compatible target in each card. A dashboard can contain a query-builder question, a basic-variable SQL question, and a field-filter SQL question; those cards need not expose the same filtering capabilities.
Engineering inference: Trace the connection from dashboard widget to card parameter, parameter type, mapped field, alias, and generated predicate. If one card ignores a date range, first check whether it is connected and whether its variable supports that operation. Do not immediately rewrite the model.
Original case: the date widget controls the order list but not the summary card. If the summary intentionally shows lifetime revenue, label it accordingly. If it should show period revenue, connect and test its parameter. A shared dashboard does not imply that every card inherits every filter automatically.
Also check whether the filtered field belongs to a table visible in the native query. A variable mapped to a product field cannot make a missing product table magically appear. Add the appropriate relationship only if it preserves the question's intended grain; otherwise choose a different data definition.
Use expected outputs to make the explanation concrete. With three eligible orders, a paid-status selection should return the two paid orders. A date selection covering only one of those should leave one. If the summary still counts three, inspect its filter connection and query instead of assuming the list is wrong.
Why can a restricted viewer still see a saved SQL result?
Official fact: Metabase distinguishes data permissions from collection permissions. People can belong to multiple groups, and the general permissions model grants the most permissive access across their groups. The default All users group therefore matters. See the permissions introduction.
Official fact: The row and column security documentation describes paid-plan functionality and an important limitation: those controls do not apply to SQL-question results. Saved SQL questions with restricted data need appropriate collection restrictions; public links lack the logged-in identity needed for those controls.
Do not infer that a viewer's restricted query-builder access protects every saved native question. A collection can expose the results of a query created under a broader access path.
Use this original access exercise with synthetic tenants:
| Persona and path | Intended observation | Evidence to collect |
|---|---|---|
| East viewer opens approved dashboard | East-only records | Actual visible rows and all group memberships |
| East viewer opens raw native SQL question | No access if it exposes all tenants | Collection access to that specific saved question |
| Analyst opens approved dashboard | All authorized tenants | Data and collection grants for the analyst |
| East viewer follows a public link | No assumed personalized isolation | Sharing configuration and whether a login identity exists |
Engineering inference: Test each path with the actual intended persona. Do not run everything as an administrator or rely solely on a permissions screenshot. Include download and subscription paths when they are part of the deployment's authorized use.
If a user belongs to a restricted group and a broadly authorized group, inspect the combined effective access. Narrowing one group does not necessarily narrow the user's overall permissions. Keep a fixture account with known membership so the result is reproducible.
For stronger native-SQL isolation requirements, evaluate database-level controls and supported impersonation options for the installed plan. State the enforcement boundary you actually use. A required tenant predicate in application SQL can be part of a design, but an editable dashboard filter is not proof of tenant isolation.
What changes when you convert a model to a transform?
The official migration description identifies a persistent table and dependency repointing. Engineering inference: That makes conversion a data and dependency change, not merely a rename. Build a small acceptance test around freshness, schema, row grain, and downstream filters.
Original case: a live model reflects a newly paid order immediately. A transform output refreshes on a schedule. The dashboard can now be correct relative to the last refresh while stale relative to the source. Define the freshness promise and expose the relevant refresh time.
Before conversion, record the model query, output types, dependent questions, and expected fixture results. After conversion, compare row counts, keys, totals, descriptions, and filters against that baseline. Inspect that dependent questions point to the intended output rather than assuming every consumer behaves identically.
A column-type change can alter aggregation and filter choices. A timestamp that becomes text can break a date interaction even when its displayed values look unchanged. A different key can affect joins and drill-through. Check behavior, not just the dashboard screenshot.
Do not delete the prior definition immediately as a cleanup exercise. Establish a rollback and dependency review appropriate to the deployment. The point is to make the migration's consequences reviewable, especially when analysts reuse the data outside the original dashboard.