Docs /SQL Notebooks /Notebooks
SQL NotebooksNotebooks

SQL notebooks

A tab made of cells instead of one editor. Each SQL cell runs on its own and its result becomes a table the cells below can query by name, so a long analysis becomes a chain of small, checkable steps instead of a 200-line WITH.

[1] SQL · vehicle_data
1234
SELECT make, COUNT(*) AS vehicle_count
FROM `ace-analytics.warehouse.vehicles`
WHERE first_registered >= '2020-01-01'
GROUP BY make
[2] SQL · make_summary
12345
-- Aggregate by manufacturer using results from the vehicle_data cell
SELECT make, SUM(vehicle_count) AS total_vehicles
FROM vehicle_data
GROUP BY make
ORDER BY total_vehicles DESC
✓0 B | $0.00· reads the result of vehicle_data
make⏷total_vehicles⏷
1FORD4252980
2VAUXHALL2130011
3HONDA616242
4MERCEDES-BENZ305002
5RENAULT248352
5 rows

Creating one

  • ⌘ ⇧ E, or the caret next to New query in the sidebar → New notebook.
  • ⌘ K → New SQL notebook.
  • From a query tab: the editor’s ⋯ menu → Convert to notebook. A multi-statement script becomes one cell per statement.

A new notebook has one empty SQL cell called query1.

Kinds of cells

The toolbar has Run All and three add buttons: + SQL, + Text, + Chart. The same buttons appear when you hover below the last cell.

KindWhat it is
SQLAn editor with the same completions, diagnostics and cost footer as a query tab, and a results grid underneath
TextMarkdown; click to edit, click away to render
ChartA bar, line, pie, scatter or heatmap over another cell’s result: pick Source Cell, Type, X Axis, Y Axis and an optional Group By

Every SQL and chart cell has a name, shown in its header. Click it to change it. Names start with a letter and contain only letters, digits and underscores; duplicates are refused. Cells are auto-named query1, chart1, note1, …

Cell header buttons: run, collapse, focus (opens the cell full-width; close it with the button at the top), move up, move down, a ⋯ menu with Copy to clipboard and Duplicate, and delete (with Undo). Cells can also be dragged by their handle to reorder.

Running cells

ActionHow
Run this cellThe ▶ in the cell header, or ⌘Enter in its editor
Run this cell and everything that depends on itShift-click ▶, or ⌘⇧Enter
Run every SQL cell, top to bottomRun All; stops at the first cell that fails

Each cell runs as its own BigQuery job, with its own dry-run and its own byte limit. If any cell is over its limit, Run All asks before starting.

Referencing other cells

Write the upstream cell’s name where a table would go:

[3] SQL · dangerous_defects
1234
SELECT d.make, d.model, COUNT(*) AS defects
FROM make_summary AS m
JOIN `ace-analytics.warehouse.defects` AS d ON d.make = m.make
GROUP BY d.make, d.model

A later cell reads the upstream cell’s stored result; nothing is re-run.

  • The upstream cell has run in this session, or its result is still available from a previous one (results are kept about 24 hours)

  • The upstream cell is a SELECT. CREATE, INSERT, MERGE, DECLARE and other statements don’t produce a result table

  • The reference is directly after FROM or JOIN

If you reference a cell that hasn’t run, the feedback bar says so before you waste a job: Cell “make_summary” has not been executed yet. Run it first. Upstream cells are never run for you.

Completions know cell names: after FROM, cells are offered like tables, and once a cell has run its result columns are offered too.

Stale cells

Edit, re-run, rename, move or delete a cell and every cell that reads from it gets a Stale marker in its header, with the reason on hover. Its old results stay visible; click the marker to re-run.

Variables

Three kinds, all flowing downward from the cell that defines them.

Typed parameters are the same -- querylab:param directive lines query tabs use (query parameters). A cell that contains only declarations is skipped when run; it exists only to be read.

[1] SQL · params
12
-- querylab:param start_date DATE = '2025-09-01'
-- querylab:param min_tests INT64 = 100
[2] SQL · filtered
12345
SELECT make, COUNT(*) AS tests
FROM `ace-analytics.warehouse.mot_tests`
WHERE completed_date >= @start_date
GROUP BY make
HAVING tests >= @min_tests

Each run and estimate of a cell sends the parameters it reads as typed BigQuery query parameters. A cell lower in the notebook can redeclare a name to change the value for the cells below it; the same name declared twice in one cell is an error.

DECLARE variables are ordinary BigQuery scripting variables. A cell that uses one without declaring it gets the declaration prepended at run time:

[1] SQL · params
12
DECLARE start_date DATE DEFAULT '2025-09-01';
DECLARE min_tests INT64 DEFAULT 100;
[2] SQL · filtered
12345
SELECT make, COUNT(*) AS tests
FROM `ace-analytics.warehouse.mot_tests`
WHERE completed_date >= start_date
GROUP BY make
HAVING tests >= min_tests

Template variables are plain assignments in a cell of their own. A cell that contains nothing else is skipped when run; it exists only to be read.

[1] SQL · config
12
table_name = 'mot_tests';
result = 'FAILED';
[2] SQL · failed
1234
SELECT make, COUNT(*) AS failed
FROM `ace-analytics.warehouse.{{ table_name }}`
WHERE test_result = @result
GROUP BY make

{{ name }} is a text substitution, so it works for identifiers: tables, columns, project IDs. @name from an assignment is a query parameter that is always STRING; the editor offers Convert to typed parameter on an assignment used that way, which turns it into a -- querylab:param line. A typed STRING parameter feeds {{ name }} too. A missing @name fails before the job is sent, naming the parameter to declare.

Cost and billing per cell

The tune icon in the toolbar opens Notebook Configuration; the one in a cell header opens Cell Configuration. Both have the same fields:

cell → tune icon
Use notebook defaults
Run on Default (from Settings)
Max GB 10 GB
Max Price 0.06 $
TABLESAMPLE 1 %
Reduce costs by sampling tables (0-100)
Cell settings win over notebook settings, which win over Settings → BigQuery.

TABLESAMPLE appears as a comment in the cell’s SQL; edit it there or in the settings. Setting it at notebook level applies it to every cell that hasn’t set its own. See TABLESAMPLE.

Getting the SQL out

The toolbar’s ⋮ menu:

  • Copy SQL as CTEs gives one statement: every SELECT cell but the last becomes name AS (…) in a WITH, the last is the body. DECLARE and DDL cells go in front as-is.
  • Copy SQL as temp tables gives a script: CREATE TEMP TABLE name AS (…); per cell, the last SELECT unchanged, DROP TABLE IF EXISTS lines at the end.
  • Copy to clipboard copies the cells as JSON. Paste into any notebook (⌘V with the notebook focused) to append them; a single cell’s ⋯ → Copy to clipboard does the same for one cell.

Both SQL exports show a toast with Open in new tab, which drops the generated SQL into a fresh query tab. Template-variable cells are left out; the variables aren’t substituted, so fill them in before running.

A cell’s results grid has the usual export options; the BigQuery and Cloud Storage ones add a new SQL cell with the CREATE TABLE or EXPORT DATA statement instead of opening a tab.

Share Notebook creates an encrypted link to the whole notebook: cells, text, chart configs. Billing projects and destination tables are stripped. See Sharing.