Skip to content

Query a model with PyRel

PyRel builds queries from the model definitions in your local project. The project’s deployment.public_schema setting identifies the Snowflake schema that contains the deployed model outputs. From your model project, import its model object and inspect the profile and deployment schema:

from model.schema import model as m
print(m.config.active_profile)
print(m.config.deployment.public_schema)

Check that the printed schema matches the schema where PyRel deployed the model outputs you want to query. This confirms which configuration PyRel loads, but it doesn’t confirm deployment. If the target is wrong, choose the correct active profile or update deployment.public_schema in your project configuration. If you haven’t deployed the model yet, see Deploy a model.

We recommend keeping queries in a separate Python module from your model definitions. You can also write queries in a Jupyter notebook or an interactive Python session.

Import the model instance, concepts, and relationships from the module in your project that defines them. In this example, that module is model.schema. Use your module path and symbol names if they differ:

from model.schema import Company, Person, model as m

When Model() has no config argument, PyRel loads the project’s default configuration, including its connection and deployment settings. The name argument labels the Python object. It doesn’t select a deployed schema. The deployment target comes from deployment.public_schema in the configuration you checked above.

PyRel represents a query as a Fragment, which is a description of the values to return and the conditions they must satisfy. Calls such as Model.select() and Fragment.select() create or refine a fragment. They don’t run the query. Call Fragment.to_df() to execute the query and return its results as a pandas DataFrame.

Select the properties you want in the result. alias() gives each returned column a stable, readable name:

from model.schema import Person, model as m
people = m.select(
Person.id.alias("person_id"),
Person.name.alias("person_name"),
)

people is a query fragment. It describes which values to return but doesn’t contain the result rows. PyRel retrieves those rows when you materialize the fragment with to_df(). You can also refine a fragment to select values from a relationship. The works_at relationship connects each person to a company:

from model.schema import Company, Person, model as m
person_company = m.where(Person.works_at(Company))
query_fragment = person_company.select(
Person.id.alias("person_id"),
Person.name.alias("person_name"),
Company.id.alias("company_id"),
Company.name.alias("company_name"),
)

The fragment describes one result row for each matching person-company relationship. It selects the IDs and names of both related concepts.

Return each person-company relationship and print the result’s column names:

from model.schema import Company, Person, model as m
query_fragment = m.where(Person.works_at(Company)).select(
Person.id.alias("person_id"),
Person.name.alias("person_name"),
Company.id.alias("company_id"),
Company.name.alias("company_name"),
)
df = query_fragment.to_df()
print(df.columns.tolist())

The output shows the DataFrame schema without inventing rows from your deployed model:

['person_id', 'person_name', 'company_id', 'company_name']

Each row contains one matching person-company relationship.

If materialization fails, first confirm that deployment.public_schema in the configuration points to the intended schema and that the Python script runs from the project containing the model definitions. For connection or access issues, see Install and run PyRel.

Use Model.where() to keep only the rows that match a condition, then select the values to return.

Compare a property to a value supplied by your Python code:

from model.schema import Person, model as m
person_name = input("Person name: ").strip()
query_fragment = m.where(Person.name == person_name).select(
Person.id.alias("person_id"),
Person.name.alias("person_name"),
)
df = query_fragment.to_df()

The result contains people whose name equals the entered value. If no deployed person has that name, the DataFrame has no rows.

Use Variable.in_() to keep values that match any item in a list:

from model.schema import Company, model as m
company_names = [
name.strip()
for name in input("Company names, separated by commas: ").split(",")
if name.strip()
]
if not company_names:
raise ValueError("Enter at least one company name.")
query_fragment = m.where(Company.name.in_(company_names)).select(
Company.id.alias("company_id"),
Company.name.alias("company_name"),
)
df = query_fragment.to_df()

The result contains only companies whose names appear in the entered list. If none of the entered names matches a deployed company, the DataFrame has no rows.

Rank and select top results with rank, asc, desc, and top

Section titled “Rank and select top results with rank, asc, desc, and top”

Aggregates can rank companies by how many people have a works_at relationship with each company. Use aggregates.rank() with aggregates.desc() to rank the largest count first:

from relationalai.semantics.std import aggregates
from model.schema import Company, Person, model as m
people_per_company = (
aggregates.count(Person)
.per(Company)
.where(Person.works_at(Company))
)
query_fragment = m.select(
Company.name.alias("company_name"),
people_per_company.alias("people_count"),
aggregates.rank(aggregates.desc(people_per_company)).alias("people_rank"),
)
df = query_fragment.to_df()

aggregates.rank() adds a rank column. It doesn’t sort the returned DataFrame. Use aggregates.asc() to rank smaller counts first, or sort the DataFrame after materialization.

Use aggregates.top() in where() to keep the top results. Here’s how to rank by the same per-company count:

from relationalai.semantics.std import aggregates
from model.schema import Company, Person, model as m
people_per_company = (
aggregates.count(Person)
.per(Company)
.where(Person.works_at(Company))
)
query_fragment = (
m.select(
Company.name.alias("company_name"),
people_per_company.alias("people_count"),
)
.where(aggregates.top(10, people_per_company))
)
df = query_fragment.to_df()

The query returns the ten companies with the highest counts.

Use distinct() to return unique values or unique combinations of selected values:

from model.schema import Company, Person, model as m
unique_companies = m.select(m.distinct(Company.name))
unique_person_company_pairs = m.where(Person.works_at(Company)).select(
m.distinct(Person.name, Company.name)
)

unique_companies returns each company name once. unique_person_company_pairs returns each person-company name pair once. To deduplicate a multi-column row, include every selected value in the same distinct() call.

You can count unique values by passing a distinct value to aggregates.count():

from relationalai.semantics.std import aggregates
from model.schema import Company, model as m
query_fragment = m.select(
aggregates.count(m.distinct(Company.name)).alias("unique_company_count")
)
df = query_fragment.to_df()

Use per() to group an aggregate by a concept or property. This query counts the people related to each company:

from relationalai.semantics.std import aggregates
from model.schema import Company, Person, model as m
people_per_company = (
aggregates.count(Person)
.per(Company)
.where(Person.works_at(Company))
)
query_fragment = m.select(
Company.name.alias("company_name"),
people_per_company.alias("people_count"),
)
df = query_fragment.to_df()

The DataFrame has one row per company with a matching relationship and a count for that company. The count and rows depend on your deployed model.

Export results to Snowflake tables with into and exec

Section titled “Export results to Snowflake tables with into and exec”
Optional: export a query fragment to a Snowflake table

Use Fragment.into() to set the destination, then Fragment.exec() to run the export. Replace the placeholder with a fully qualified destination table name. The destination schema must allow you to create or replace the table. An existing target is replaced by default. update=True requests an update export where supported. Each code block builds its own query fragment and destination table, so you can run either one independently:

from model.schema import Company, Person, model as m
query_fragment = m.where(Person.works_at(Company)).select(
Person.id.alias("person_id"),
Person.name.alias("person_name"),
Company.id.alias("company_id"),
Company.name.alias("company_name"),
)
out = m.Table("<OUTPUT_DB>.<OUTPUT_SCHEMA>.<OUTPUT_TABLE>")
query_fragment.into(out).exec()
from model.schema import Company, Person, model as m
query_fragment = m.where(Person.works_at(Company)).select(
Person.id.alias("person_id"),
Person.name.alias("person_name"),
Company.id.alias("company_id"),
Company.name.alias("company_name"),
)
out = m.Table("<OUTPUT_DB>.<OUTPUT_SCHEMA>.<OUTPUT_TABLE>")
query_fragment.into(out, update=True).exec()

You can also use a configured short table name with m.Table("MY_NAMED_EXPORT"). See Configure PyRel and the Model.Table() API reference for destination configuration and optional destination schemas.

Choose a guide based on what you want to do next: