# Query with SQL The SQL editor runs queries on the Nexus engine, which speaks Spark SQL. Every example here uses a small sample table you create first, so you can follow along in an empty workspace. ## Before you start [#before-you-start] - Anyone can read the tables they have access to. - Creating a table needs the right to change structure; adding rows needs the right to change data. Owners and admins have both. See [Data access](/docs/data/data-access). - How much one query may read and how many rows come back depend on your plan. See your own limits in , or with `nx.limits()` in code. ## Run a query [#run-a-query] ### Open the SQL editor [#open-the-sql-editor] Go to . The panel beside the editor lists your **Tables** by **Schema**. ### Write one statement [#write-one-statement] Type a statement, or click a table or column name to insert it. The panel also offers **Preview rows**, **Count rows** and **Changes**. ### Run it [#run-it] Select **Run**, or press ⌘ Enter (Ctrl Enter on Windows and Linux). ### Read the result [#read-the-result] Rows show under the editor, with how much the query read (**Reads**, then a size). Select **Next page** for the rest of a long result. Before every run, the editor checks what the query would read. If that is more than your plan allows, nothing runs and you see **Would read**, the size, and how much is **allowed**. Narrow the query and run it again. ```python title="first_query.py" from klarety import Nexus with Nexus.connect() as nx: result = nx.sql("SELECT current_date() AS today") print(result.rows) print(result.bytes_scanned) ``` `nx.sql()` waits while the statement runs; a resend after a dropped connection never runs it twice. From the command line: `klarety sql "SELECT current_date()"`. ## Create a sample table [#create-a-sample-table] The editor and the API run one statement at a time. Run each block below on its own. A name without a schema, such as `demo_orders`, goes in the `default` schema. ```sql CREATE TABLE demo_orders ( order_id BIGINT NOT NULL, customer STRING, country STRING, status STRING, amount DECIMAL(10, 2), ordered_at TIMESTAMP ) ``` ```sql INSERT INTO demo_orders VALUES (1, 'Ana', 'CO', 'paid', 120.50, TIMESTAMP '2026-10-01 09:15:00'), (2, 'Bruno', 'BR', 'paid', 80.00, TIMESTAMP '2026-10-02 14:40:00'), (3, 'Carla', 'CO', 'refunded', 45.25, TIMESTAMP '2026-10-03 11:05:00'), (4, 'Diego', 'MX', 'paid', 210.00, TIMESTAMP '2026-10-03 16:30:00'), (5, 'Ana', 'CO', 'paid', 60.00, TIMESTAMP '2026-10-05 08:00:00'), (6, 'Elena', 'MX', 'paid', 99.90, TIMESTAMP '2026-10-06 19:20:00') ``` Nexus tables are Apache Iceberg tables, so a plain `CREATE TABLE` is enough. Leave out `USING`, or write `USING iceberg`. ## Example queries [#example-queries] Revenue and order count per country: ```sql SELECT country, COUNT(*) AS orders, SUM(amount) AS revenue FROM demo_orders WHERE status = 'paid' GROUP BY country ORDER BY revenue DESC ``` Each customer's running total, with a window function: ```sql SELECT customer, ordered_at, amount, SUM(amount) OVER (PARTITION BY customer ORDER BY ordered_at) AS running_total FROM demo_orders ORDER BY customer, ordered_at ``` Orders per day: ```sql SELECT date_trunc('DAY', ordered_at) AS day, COUNT(*) AS orders FROM demo_orders GROUP BY 1 ORDER BY 1 ``` Change a row: ```sql UPDATE demo_orders SET status = 'refunded' WHERE order_id = 6 ``` When done, delete the sample in **Nexus Lakehouse** with **More**, **Delete table**, or `nx.tables.delete("demo_orders")` in code. It can be restored for a while after (see [Undo and restore](/docs/data/undo-and-restore)). ## Check what a query reads before you run it [#check-what-a-query-reads-before-you-run-it] Each plan limits how much one query may read. Checking first costs nothing and runs nothing. The SQL editor does this on every run. ```python title="estimate.py" from klarety import Nexus with Nexus.connect() as nx: plan = nx.estimate("SELECT * FROM demo_orders") print(plan["bytes_scanned"], plan["allowed_bytes"], plan["within_budget"]) ``` A refusal from `estimate()` is the same a run would get, so it also safely checks that a statement is allowed. ## Pass values as parameters [#pass-values-as-parameters] Never paste a value a person typed into the SQL text. Name it `:name` and pass it in `params`. Each value is bound as a string literal, never read as SQL. Cast it where you mean a number or a date: ```python title="parameters.py" from klarety import Nexus with Nexus.connect() as nx: result = nx.sql( """ SELECT order_id, customer, amount FROM demo_orders WHERE country = :country AND amount >= CAST(:minimum AS DECIMAL(10, 2)) ORDER BY amount DESC """, params={"country": "CO", "minimum": "50"}, ) for row in result.rows: print(row) ``` Parameter names start with a letter or underscore. Parameters are available from code, not in the SQL editor. Parameters are a developer feature that keeps typed values from changing a query. Switch to the developer view to see how. ## How many rows come back [#how-many-rows-come-back] A query returns one page of rows at a time. Page and result sizes depend on your plan. If the whole result is larger than your plan allows, you see **Only the first part of this result is kept**. Narrow the query or add a `LIMIT`. `max_rows` asks for 1 to 10,000 rows, and at most your plan's page comes back at once. When there are more, `truncated` is true and `nx.result_rows()` reads every row, page by page: ```python title="all_rows.py" from klarety import Nexus with Nexus.connect() as nx: result = nx.sql("SELECT * FROM demo_orders", max_rows=1000) if result.truncated: for row in nx.result_rows(result): print(row) else: for row in result.rows: print(row) ``` The rest of a large result is kept for a day. `result_truncated` is true when even the kept result passed your plan's size: narrow the query. Two value notes: a whole number larger than 2^53 comes back as text, so `int()` it; a `TIMESTAMP` comes back in UTC with no offset written. ## What can go wrong [#what-can-go-wrong] Send one statement per run. A second statement after a semicolon is refused. Filter on fewer rows, select fewer columns, or add a `LIMIT`. In code this raises `OverBudgetError`. You may not have access to a table it names, or your access does not allow the change. In code this raises `RefusedError`. Ask an owner or admin, see [Data access](/docs/data/data-access). The engine starts when first needed, so the first query after a quiet period takes longer. In code, `nx.warm()` starts it ahead of time and returns at once. Your plan allows a number of queries running at the same time. In code, `StatementsBusyError` and `RateLimitedError` mean nothing ran: wait a moment and send it again. ## Where to go next [#where-to-go-next] - [Work with tables](/docs/data/tables): browse and edit rows without SQL. - [Add data](/docs/data/add-data): load a file into a table. - [SQL from code](/docs/developers/sql): the full SDK reference for queries.