Databricks DE ยท Topic 10.2

SQL Editor, Parameters, CTEs, Snippets & Caching

The everyday Databricks SQL toolkit โ€” write & share queries, reuse them with typed :parameters, parameterize names with IDENTIFIER(), structure with CTEs, standardize with snippets, and make BI instant with three caching layers.

:param (injection-safe)IDENTIFIER()CTEssnippets3 cache layers

The one-liner

:named parameters make queries reusable & injection-safe (typed); IDENTIFIER() parameterizes object names; CTEs structure complex SQL; snippets standardize fragments; and three cache layers (UI / result / disk) return repeated BI queries in milliseconds.
๐Ÿ“ Analogy: the editor is your word processor for data; parameters are mail-merge fields (one template, many values, safely filled); CTEs are named paragraphs; snippets are text-expander shortcuts; caching is the "recently opened" shelf.

Parameter playground โ€” type values, watch the safe query build

โ–ธ :param is typed & bound โ€” not string-concatenated, so injection can't execute

How it works โ€” deep dive

1 ยท The SQL editor โ€” where queries live

Mechanism: the editor runs statements on a selected SQL Warehouse (10.1). Saved queries are first-class, permissioned (UC GRANT) objects reused by dashboards, alerts, and jobs. Trade-off: the editor is free; the warehouse under it drives cost/latency.

-- A saved query targets a UC object with the 3-level namespace.
SELECT region, count(*) AS orders
FROM prod.sales.orders            -- catalog.schema.table (Unity Catalog)
WHERE order_date >= current_date() - INTERVAL 7 DAYS
GROUP BY region ORDER BY orders DESC;

2 ยท Named parameter markers โ€” :param (typed & injection-safe)

Mechanism: the mandatory : separates parameter names from columns; values are bound as typed literals, never spliced into SQL text. Why: that binding is what makes them injection-proof. Trade-off: they replace the legacy mustache {{ }} of the old editor โ€” old queries must be migrated.

-- :named markers are typed & bound โ€” safe + reusable across every range.
SELECT region, sum(amount) AS revenue
FROM prod.sales.orders
WHERE order_date BETWEEN :start_date AND :end_date   -- bound as typed DATEs
  AND region = :region                               -- bound as STRING
GROUP BY region;

3 ยท IDENTIFIER() โ€” parameterize object names safely

Mechanism: a plain :param binds a value, not a name โ€” you can't put :table after FROM. IDENTIFIER(:x) interprets the bound string as a catalog/schema/table/column name. Trade-off: the object must exist and UC grants still apply โ€” it doesn't bypass permissions.

-- Dynamic, injection-safe table & column selection.
SELECT IDENTIFIER(:metric_col) AS metric          -- column name as a parameter
FROM IDENTIFIER(:catalog || '.' || :schema || '.orders')  -- table name as a parameter
WHERE order_date >= :since;

4 ยท CTEs โ€” WITH for readable multi-step SQL

Mechanism: WITH name AS (subquery) names an intermediate result you reference (and chain) later. Trade-off (gotcha): a CTE is not guaranteed to materialize โ€” referenced many times, it may be recomputed; persist a temp view/table for heavy reuse.

-- Chained CTEs read top-down; each name is a clean, reusable step.
WITH recent AS (
  SELECT * FROM prod.sales.orders
  WHERE order_date BETWEEN :start_date AND :end_date
),
by_region AS (
  SELECT region, sum(amount) AS revenue, count(*) AS orders
  FROM recent GROUP BY region
)
SELECT region, revenue, revenue / orders AS avg_order_value
FROM by_region ORDER BY revenue DESC;

5 ยท Query snippets โ€” team-standard SQL fragments

Mechanism: save a reusable fragment with a trigger keyword; typing it expands the snippet (placeholders allowed). Why: everyone defines common logic ("active customer") identically. Trade-off: snippets aren't governed โ€” for logic that must stay consistent, prefer a view or SQL UDF in UC.

-- Snippet "active_cust" expands to a standardized predicate so every
-- query defines "active" identically:
SELECT customer_id, region
FROM prod.sales.customers
WHERE status = 'ACTIVE'
  AND last_seen >= current_date() - INTERVAL 90 DAYS;

6 ยท Query caching โ€” three layers that make BI instant

Mechanism: three layers, each auto-invalidated on write. Why: a re-run of the same query skips compute (result cache); the disk cache speeds different queries over the same hot data.

LayerScope & lifetimeStores
UI cacheper-user, in the DBSQL UI, up to 7 daysrecent results + visualizations
Result cache โ€” localper-warehouse, in-memory, until cluster stop/restartexact results of a prior run
Result cache โ€” remoteserverless, survives restarts, shared workspace-wide, 24 hresults as workspace system data
Disk cacheper-node local SSD, auto-detects data changeshot data files, optimized format
-- Force a cold run for benchmarking; re-enable afterward.
SET use_cached_result = false;   -- bypass the result cache for this session
SELECT region, sum(amount) FROM prod.sales.orders GROUP BY region;
SET use_cached_result = true;    -- restore caching

Cache simulator โ€” run the same query, change the data, watch the cache

โ–ธ identical query on unchanged data = cache hit; a write invalidates it

Why it matters

๐Ÿ“Š Real-world use case: a dashboard uses :start_date/:end_date so one query serves every range; a CTE builds the aggregate; the result cache makes re-opening instant; a shared snippet standardizes the "active customer" filter team-wide.

Uses, edge cases & limitations

Common gotchas