Your first query¶
In this tutorial, you will define a model, register an engine, build a query, and read the results. By the end, you will have a working Semolina query you can adapt for your own semantic views.
Prerequisites: Semolina installed (Installation).
1. Define a model¶
A model maps to a semantic view in your warehouse. Create a file called
demo.py and add this code:
from semolina import (
SemanticView,
Metric,
Dimension,
)
class Sales(SemanticView, view="sales"):
revenue = Metric()
cost = Metric()
country = Dimension()
region = Dimension()
view="sales" is the name of the semantic view in your warehouse.
Metric fields are aggregatable measures (revenue, cost).
Dimension fields are categories for grouping (country, region).
In your warehouse, this model maps to a definition like:
CREATE OR REPLACE SEMANTIC VIEW sales
TABLES (
s AS source_table PRIMARY KEY (id)
)
DIMENSIONS (
s.country AS country,
s.region AS region
)
METRICS (
s.revenue AS SUM(s.revenue),
s.cost AS SUM(s.cost)
)
;
CREATE OR REPLACE VIEW sales
WITH METRICS
LANGUAGE YAML
AS $$
version: 1.1
source: source_table
dimensions:
- name: country
expr: country
- name: region
expr: region
measures:
- name: revenue
expr: SUM(revenue)
- name: cost
expr: SUM(cost)
$$;
2. Register an engine¶
Semolina needs an engine to talk to your warehouse. An engine owns one
connection pool and the dialect for a backend. Build one with
create_engine() and register it before running any queries:
from semolina import register, create_engine
register(
"default", create_engine("default")
) # reads .semolina.toml
from semolina import register, create_engine
register(
"default", create_engine("default")
) # reads .semolina.toml
The same Python code works for both backends. create_engine("default") reads
the [connections.default] section of your .semolina.toml, and the type
field there determines which warehouse to connect to.
See How to choose and configure a backend for full connection details and TOML configuration.
Tip
No warehouse? Use DuckDB locally
Install semolina[duckdb] and the
duckdb-semantic-views
community extension, then create a local database with sample data.
Save this as setup_tutorial.py and run it once:
import duckdb
conn = duckdb.connect("tutorial.db")
conn.execute("INSTALL semantic_views FROM community")
conn.execute("LOAD semantic_views")
conn.execute("""
CREATE TABLE IF NOT EXISTS sales_data (
revenue INTEGER, cost INTEGER,
country VARCHAR, region VARCHAR
)
""")
conn.execute("""
INSERT INTO sales_data VALUES
(1000, 100, 'US', 'West'),
(2000, 200, 'CA', 'West'),
(500, 50, 'US', 'East')
""")
conn.execute("""
CREATE OR REPLACE SEMANTIC VIEW sales AS
TABLES (s AS sales_data)
DIMENSIONS (
s.country AS country,
s.region AS region
)
METRICS (
s.revenue AS SUM(s.revenue),
s.cost AS SUM(s.cost)
)
""")
conn.close()
Then register a DuckDB engine pointing at the file:
from adbc_poolhouse import DuckDBConfig
from semolina import register, create_engine
engine = create_engine(DuckDBConfig(database="tutorial.db"))
register("default", engine)
3. Build and run a query¶
Use Model.query() to start building. Chain .metrics() and .dimensions()
to select the fields you want, then call .execute():
cursor = (
Sales.query()
.metrics(Sales.revenue)
.dimensions(Sales.country)
.execute()
)
Each chained method returns a new query object, so queries are immutable and reusable.
You can also pass metrics and dimensions directly to query():
cursor = Sales.query(
metrics=[Sales.revenue],
dimensions=[Sales.country],
).execute()
4. Read the results¶
.execute() returns a SemolinaCursor. Call .fetchall_rows()
to get Row objects that support both attribute and dict-style access:
rows = cursor.fetchall_rows()
for row in rows:
print(row.country, row.revenue) # attribute access
print(row["country"]) # dict-style access
Because revenue is a metric, the warehouse aggregates it per country, so
the query returns one row per country. You should see output like:
US 1500
CA 2000
Complete example¶
This self-contained demo uses a local DuckDB database. To run against a cloud warehouse, replace the engine registration with your connection (see step 2).
First, run setup_tutorial.py from the tip above to create the database. Then
paste this into demo.py and run python demo.py:
from adbc_poolhouse import DuckDBConfig
from semolina import (
SemanticView,
Metric,
Dimension,
register,
create_engine,
)
# 1. Define model
class Sales(SemanticView, view="sales"):
revenue = Metric()
cost = Metric()
country = Dimension()
region = Dimension()
# 2. Register a DuckDB engine
engine = create_engine(DuckDBConfig(database="tutorial.db"))
register("default", engine)
# 3. Build and execute query
cursor = (
Sales.query()
.metrics(Sales.revenue)
.dimensions(Sales.country)
.execute()
)
# 4. Use results
for row in cursor.fetchall_rows():
print(row.country, row.revenue)
You should see:
US 1500
CA 2000
See also¶
Field types, SemanticView parameters, immutability.
All query methods with examples.
Field operators, named methods, AND/OR/NOT composition.