Queries
All queries are formed through chain method calls. The last method in the call
chain should be the build()
method, which returns final query.
Select
You can query whole objects Movie.select().build()
that gives you
the following EdgeQL query: select Movie
You also can ask database for exact set of properties:
Movie.select(Movie.c.title, Movie.c.year).build()
that would be rendered to select Movie { title, year }
.
Shapes
Nested shapes can be used to fetch linked objects and their properties. Here we fetch all Movie objects and their directors.
Movie.select(
Movie.c.title,
Movie.c.year,
Movie.c.director(
Movie.c.director.first_name,
Movie.c.director.last_name,
),
Movie.c.actors(
Movie.c.actors.first_name,
Movie.c.actors.last_name,
).order_by(Movie.c.actors.first_name, Movie.c.actors.last_name).limit(5)
).build()
generated query
select Movie {
title,
year,
director: {
first_name,
last_name
},
actors: {
first_name,
last_name
}
order by .first_name then .last_name
limit <int64>$limit_0
}
{'limit_0': 5}
Subqueries
If the query is used as a subquery, then the such query should not end with the build()
method.
Post.select(
Post.c.title,
Post.c.description,
lastest_posts=Post.select().order_by(Post.c.created_at.desc()).limit(3),
).build()
generated query
select Post { title, description, latest_posts := (select Post order by created_at desc limit $limit_0) }
Expressions
Query Builder supports the creation of complex expressions, including the use of parentheses for grouping and order of operations. It intelligently handles parentheses by only including them when necessary, ensuring that the final query is both accurate and readable. This capability allows users to construct intricate queries with ease, providing a powerful tool for data analysis and manipulation.
Person.select(
Person.c.name,
(Person.c.weight / Person.c.height ** 2).label('bmi'),
).build()
generated query
select Person { name, bmi := .weight / .height ^ $select_0 }
{'select_0': 2}
Filtering
To filter the set of selected objects, use a where
chained method,
which accepts either binary or unary expressions.
Villain.select(Villain.c.id, Villain.c.name).where(Villain.c.name == 'Doc Ock').build()
generated query
select Villain { id, name } filter .name = <str>$filter_0
{'filter_0': 'Doc Ock'}
You also may filter nested objects:
Post.select(
Post.c.title,
Post.c.text,
Post.c.comments(
Comment.c.text,
).where(Comment.c.created_at >= created_after),
)
generated query
select Post { title, text, comments: { text } filter .created_at >= <cal::local_datetime>$filter_0 }
Ordering
You could pass any number of binary or unary expressions or even columns to order_by
method:
Movie.select().order_by(
Movie.c.rating.desc(),
Movie.c.year.desc(),
Movie.c.title,
).build()
generated query
select Movie order by .rating desc then .year desc then .title
Pagination
You may build pagination on limit and offset expressions exactly like in SQL.
top_250 = (
Movie.select()
.order_by(Movie.c.rating.desc())
.limit(250)
.offset(0)
.build()
)
generated query
select Movie order by .rating desc offset $offset_0 limit $limit_1
{'limit_1': 250, 'offset_0': 0}
In case you are using select or insert subquery for singular relationship then you need to know:
EdgeDB requires you to specify limit 1
until you have not create unique constraint covering this condition.
When you pass python value without wrapper unsafe_text(limit)
it will be rendered
as context’s variable limit_N
and will pass to EdgeDB dynamically, which will cause an error on EdgeDB side.
Similar to how it works in SQLAlchemy’s unsafe_text
wrapper make this expression hardcoded into final query as is,
without dynamic contexts.
Please note that offset
by design producing not optional execution plan
and you have to avoid to use this keyword and method as far as you can.
Insert
Movie.insert.values(
title='Blade Runner 2049',
year=int16(2017),
director=(
Person.select()
.where(Person.c.id == director_id)
.limit1
),
actors=Person.insert.values(
first_name='Harrison',
last_name='Ford',
),
).build()
generated query
insert Movie {
title := <str>$insert_0,
year := <int16>$insert_1,
director := (select Person filter .id = <uuid>$filter_2 limit 1),
actors := (insert Person { first_name := <str>$insert_3, last_name := <str>$insert_4 })
}
{'insert_0': 'Blade Runner 2049', 'insert_1': 2017, 'filter_2': director_id, 'insert_3': 'Harrison', 'insert_4': 'Ford'}
For convenience, the .limit1
property has been added, which is a shorthand for limit(unsafe_text('1'))
.
Conditional Insert (Upsert)
(
Movie
.insert
.values(
slug='blade_runner_2049',
title='Blade Runner 2049',
usd_raised=int16(1000),
)
.unless_conflict(on=Movie.c.slug, else_=Movie.update.values(usd_raised=int16(1000))
.build()
)
generated query
insert Movie {
title := <str>$insert_0,
slug := <str>$insert_1,
usd_raised := <int16>$insert_2,
}
unless conflict on .slug
else (update Movie set { usd_raised := <int16>$update_3 })
Idempotent Insert
Account.insert.values(username='System').unless_conflict().build()
generated query
insert Account { username := <str>$insert_0 } unless conflict
Select or insert
There are times when rather than an “upsert” you need to select an object or insert it, if it wasn’t there. Consider, for example, the functionality to create a new account or retrieve an existing one:
(
Account
.select(
Account.c.id,
Account.c.username,
Account.c.watchlist(
Account.c.watchlist.title,
),
)
.select_from(
Account
.insert
.values(username='Alice')
.unless_conflict(Account.c.username, Account)
)
.build()
)
generated query
select (
insert Account {
username := <str>$insert_0
} unless conflict on .username else Account
) {
id,
username,
watchlist: { title }
}
{'insert_0': 'Alice'}
Update
Movie.update.values(
budget_usd=int16(185_000_000),
).where(Movie.c.title == 'Blade Runner 2049').build()
generated query
update Movie filter .title = <str>$filter_0 set { budget_usd := <int16>$update_1 }
{'filter_0': 'Blade Runner 2049', 'update_1': 185000000}
Delete
Movie.delete.where(Movie.c.title == 'Blade Runner 2049').build()
generated query
delete Movie filter .title = <str>$filter_0
{'filter_0': 'Blade Runner 2049'}
The limit
, offset
and order_by
methods are supported as well.
This query will delete latest log entry:
Log.delete.order_by(Log.c.created_at.desc()).limit1.build()
generated query
delete Log order by .created_at desc limit 1
Group
Movie.group(Movie.c.title).by(Movie.c.year).build()
generated query
group Movie { title } by .year
More complex example:
decade = (Movie.c.year // 10).label('decade')
Movie.group().using(decade).by(decade).build()
generated query
group Movie using decade := .year // $using_0 by decade
{'using_0': 10}
Using syntax also supports nested expressions:
weight_kg = (Person.c.weight_grams / 1000).label('weight_kg')
height_cm = (Person.c.height_m * 100).label('height_cm')
bmi = (weight_kg / height_cm ** 2).label('bmi')
With
All top-level EdgeQL statements (select
, insert
, update
, and delete
)
can be prefixed with a with
block.
These blocks contain declarations of standalone expressions that can be used in your query.
Query builder provides a special method with_
that allows you to set the values of these declarations.
from edgeql_qb import EdgeDBModel
from edgeql_qb.operators import Alias
from edgeql_qb.types import int16
# please note that you may specify module for model,
# which would be used in every generated `with` statements.
Person = EdgeDBModel('Person', module='imdb')
Movie = EdgeDBModel('Movie', module='imdb')
actors = Person.insert.values(
first_name='Harrison',
last_name='Ford',
).label('actors')
director = Person.select().where(Person.c.id == director_id).limit1.label('director')
title = Alias('title').assign('Blade Runner 2049')
year = Alias('year').assign(int16(2017))
query = Movie.insert.with_(actors, director, title, year).values(
title=title,
year=year,
director=director,
actors=actors,
).build()
generated query
with
module imdb,
actors := (with module imdb insert Person { first_name := <str>$insert_0, last_name := <str>$insert_1 }),
director := (with module imdb select Person filter .id = $filter_2 limit 1),
title := <str>$with_3,
year := <int16>$with_4
insert Movie {
title := title,
year := year,
director := director,
actors := actors
}
{'insert_0': 'Harrison', 'insert_1': 'Ford', 'filter_2': director_id, 'with_3': 'Blade Runner 2049', 'with_4': 2017}