# 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. Source: https://docs.querylab.io/notebooks/ **[1] SQL · vehicle_data** ```sql SELECT make, COUNT(*) AS vehicle_count FROM `ace-analytics.warehouse.vehicles` WHERE first_registered >= '2020-01-01' GROUP BY make ``` **[2] SQL · make_summary · 0 B | $0.00** ```sql -- 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 ``` reads the result of vehicle_data ## 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. | Kind | What it is | | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | **SQL** | An editor with the same completions, diagnostics and cost footer as a query tab, and a results grid underneath | | **Text** | Markdown; click to edit, click away to render | | **Chart** | A 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 | Action | How | | ----------------------------------------------- | ----------------------------------------------------------------------- | | Run this cell | The ▶ in the cell header, or `⌘ Enter` in its editor | | Run this cell and everything that depends on it | Shift-click ▶, or `⌘ ⇧ Enter` | | Run every SQL cell, top to bottom | **Run 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](https://docs.querylab.io/writing-sql/cost-limits/). 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** ```sql 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](https://docs.querylab.io/writing-sql/query-parameters/)). A cell that contains only declarations is skipped when run; it exists only to be read. **[1] SQL · params** ```sql -- querylab:param start_date DATE = '2025-09-01' -- querylab:param min_tests INT64 = 100 ``` **[2] SQL · filtered** ```sql 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** ```sql DECLARE start_date DATE DEFAULT '2025-09-01'; DECLARE min_tests INT64 DEFAULT 100; ``` **[2] SQL · filtered** ```sql 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** ```sql table_name = 'mot_tests'; result = 'FAILED'; ``` **[2] SQL · failed** ```sql 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 (selected) - Run on: Default (from Settings) - Max GB: 10 GB - Max Price: 0.06 $ - TABLESAMPLE: 1 %. Reduce costs by sampling tables (above 0, up to 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](https://docs.querylab.io/writing-sql/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](https://docs.querylab.io/results/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](https://docs.querylab.io/sharing/).