Scrydon
Analytics

Query tables with SQL

Run SELECT queries and JOINs across your workspace's managed tables directly from a notebook — column masking always applied, no extra configuration needed.

The scrydon.sql module gives notebooks a native SQL surface over the workspace's governed tables. Connect once and the Data Sources panel lists your tables with their columns — then write SELECT, JOIN, and GROUP BY queries exactly as you would against any database connection.

Every row the notebook sees is already masked: the same column-masking policies that apply in Analytics apply here. There is no separate enforcement to configure and no path that bypasses them.

Connect and explore

Add this setup cell at the top of the notebook:

from scrydon import sql

con = sql.connect()   # workspace tables now appear in Data Sources

sql.connect() discovers your workspace tables and registers them with the DuckDB connection it returns. Marimo lists the connection in the Data Sources panel on the left under a scrydon database, where you can click any table to see its columns or run a SELECT * FROM <table> cell.

By default, connect() loads smaller tables so a native SQL cell returns rows straight away. Larger tables (over ~50,000 rows) appear in the panel but stay empty until you load them with sql.hydrate("table_name") or query them through sql.query(...). The load runs in parallel, so the panel is ready in a couple of seconds even with many tables.

Want the panel instantly with no data reads? Pass sql.connect(load=False) for a discovery-only connection — every table then shows its columns but returns zero rows until you sql.hydrate("table_name") or sql.query(...).

Run a query

Click a table in the panel to drop in a SELECT * cell, or write your own SQL cell:

SELECT supplier_name, risk_score
FROM suppliers
WHERE country = 'DE'
ORDER BY risk_score DESC
LIMIT 100

You can run the same query from a Python cell and get a DataFrame back. sql.query(...) loads exactly the tables your query references (through the governed masked read), so it also works on large tables that connect() didn't pre-load:

df = sql.query("""
    SELECT supplier_name, risk_score
    FROM suppliers
    WHERE country = 'DE'
    ORDER BY risk_score DESC
    LIMIT 100
""")

JOINs and aggregations

SQL across multiple tables works the same way:

SELECT s.supplier_name,
       COUNT(o.id)    AS order_count,
       SUM(o.total)   AS revenue
FROM   suppliers s
JOIN   orders    o ON s.id = o.supplier_id
WHERE  o.placed_at >= '2025-01-01'
GROUP  BY s.supplier_name
ORDER  BY revenue DESC

Only the tables your query references are loaded — others stay as panel entries until you query them.

Masked columns

If your role has a mask on a column, you see the placeholder value in query results — the same behaviour as tables.read() and the Analytics table preview. A one-time notice lists masked columns the first time a table is loaded.

To see which columns are masked for you, call tables.get("table_name") — it returns the mask strategy per column alongside other schema metadata.

Large tables

sql.connect() loads smaller tables automatically; tables over ~50,000 rows appear in the panel but aren't loaded, so a native SQL cell against one returns zero rows until you load it. Query it through sql.query(...) (which loads only what it references) or pre-load it with sql.hydrate("name").

For very large tables, sql.query() asks you to opt in before loading all the rows:

# Acknowledge that you want to load a very large table
df = sql.query("SELECT * FROM big_table", allow_large=True)

You can also pre-load a large table into the connection so it is available in native SQL cells:

sql.hydrate("big_table", allow_large=True)

Refresh the cache

Each table is loaded once per session. If you write to a table earlier in the notebook and then query it, re-run the setup cell or call sql.refresh() to pick up the new rows:

sql.refresh()              # reset the whole session — all tables reload on next query
sql.refresh("suppliers")   # refresh just one table

Read-only

scrydon.sql is read-only. INSERT, UPDATE, DELETE, and CREATE TABLE AS raise an error that points you to the right alternative.

scrydon.sql rejects SQL that would modify or define tables (INSERT, UPDATE, DELETE, CREATE TABLE AS, DROP). It does not restrict DuckDB's own reading functions such as read_csv() or URL-sourced reads — those run with your normal notebook capabilities and remain subject to the workspace egress policy.

# Write rows back to a managed table
tables.write("suppliers", df, mode="upsert")

# Publish a new managed table from a DataFrame
tables.create("supplier_risk_scores", scores_df, classification="internal")

Quick reference

CallWhat it does
sql.connect()Return a DuckDB connection; register workspace tables in Data Sources and load smaller ones (pass load=False to skip loading)
sql.query(sql)Run a SELECT and return a DataFrame; load only the referenced tables on demand (works on large tables too)
sql.hydrate("name")Load one table's rows (for native SQL cells on large tables); pass allow_large=True for very large tables
sql.refresh()Reset the session — all cached data is dropped
sql.refresh("name")Drop the cache for one table only
On this page

On this page