Run SQL
Run Spark SQL on Nexus with parameters, estimates and result pages.
nexus.sql() runs one statement on and waits for the answer. In the app, the same queries run in DataSQL editor. For long scripts or heavy processing, see Spark sessions and Jobs.
Before you start
- The SDK is installed and you can connect (SDK quickstart).
- Your key's Access allows what the statement does. A read-only key can only read.
- The workspace has credits.
Run a query
from klarety import Nexus, RefusedError, ServiceError
with Nexus.connect() as nexus:
try:
result = nexus.sql(
"SELECT customer, SUM(amount) AS total "
"FROM orders GROUP BY customer ORDER BY total DESC",
max_rows=10,
)
except RefusedError as refused:
raise SystemExit(f"Query refused: {refused}")
except ServiceError as failed:
raise SystemExit(f"Nexus could not answer, try again: {failed}")
for row in result.rows:
print(row["customer"], row["total"])
print(f"Read {result.bytes_scanned} bytes")Tables without a name are read from default. Name another schema as schema.table.
What comes back
sql() returns a ResultSet:
| Field | Meaning |
|---|---|
rows | The rows, each a dictionary keyed by column name. len(result) is the number of rows. |
bytes_scanned | How much data the statement read. |
truncated | True when the statement produced more rows than came back. |
result_id | Set when the rest of the result was kept aside for you to read page by page. |
pages | How many pages were kept aside. |
result_truncated | True when even the kept result passed your plan's size, so only its first part is kept. |
Two value types need care:
- A whole number larger than 253 comes back as text, so no JSON reader rounds it. Convert it with
int(). - A
timestamptzvalue comes back in UTC with no offset written. AddZbefore a strict parser reads it.
Pass values with parameters
Never join user input into SQL. Use params: each :name is filled as a string literal, never read as SQL.
result = nexus.sql(
"SELECT * FROM orders WHERE customer = :customer AND amount > CAST(:minimum AS DOUBLE)",
params={"customer": "Acme", "minimum": "100"},
)Every value is a string; CAST numbers and dates in the SQL, as above. A parameter name starts with a letter or underscore and has up to 64 letters, digits or underscores; a statement takes at most 256 parameters.
Control how many rows come back
max_rows caps the rows returned: 1 to 10,000, 1,000 by default, never past your plan's page of rows. If there are more, truncated is True.
Read a large result
When a result is bigger than one answer, Nexus keeps the rest for a day and sets result_id. nexus.result_rows(result) reads every row page by page, without holding it all in memory:
result = nexus.sql("SELECT * FROM events WHERE day = '2026-11-01'")
if result.truncated:
count = 0
for row in nexus.result_rows(result):
count += 1
print(f"{count} rows in total")If result_id is None, result_rows() returns the rows you have, so it is always safe to call.
Estimate before you run
nexus.estimate() reports what a statement would read, without running or charging for it:
estimate = nexus.estimate("SELECT * FROM events")
print(estimate["bytes_scanned"], estimate["allowed_bytes"], estimate["within_budget"])If within_budget is False, it would be refused with OverBudgetError. Add a WHERE on a column the table is organised by, select fewer columns, or aggregate first.
Start Nexus ahead of time
After a quiet period, the first statement can wait while Nexus starts. Before a known burst (for example, at app start), call nexus.warm(); it returns at once and starts Nexus in the background. nexus.spark_status() tells you whether it is off, starting, ready or busy.
Retries and timeouts
You do not need to write retry loops around sql():
- Every statement carries an SDK-chosen ID. If the connection drops, the SDK resends it with the same ID, and Nexus runs it once.
- When every slot your plan allows is busy, or Nexus is starting, the SDK waits and tries again for up to five minutes.
- A statement that has not finished in that time raises
AnswerNotReadyError. Run it again: nothing ran twice.
What can go wrong
| Exception | Cause | What to do |
|---|---|---|
RefusedError | The statement is not allowed for this key, or the SQL is invalid. The message says which. | Fix the statement or use a key with more access. |
OverBudgetError | The statement would read more than your plan allows. | Narrow it, then check with estimate(). |
RateLimitedError | Too many calls per minute. | The SDK already waited; slow your loop down. |
OverBudgetError (busy) | Every slot is taken and the queue is full. | Send it again shortly. |
InsufficientCreditsError | The workspace is out of credits. | Add credits. |
AnswerNotReadyError | No answer within the waiting time. | Ask again. |
Next steps
- Tables and schemas to create and change the tables you query.
- Jobs and batch jobs to change rows with SQL.
- Errors for the full list of exceptions.