Select

The select command retrieves or computes a set of values. We’ve already seen simple queries that select primitive values.

  1. db>
  1. select 'hello world';
  1. {'hello world'}
  1. db>
  1. select [1, 2, 3];
  1. {[1, 2, 3]}
  1. db>
  1. select {1, 2, 3};
  1. {1, 2, 3}

With the help of a with block, we can add filters, ordering, and pagination clauses.

  1. db>
  2. ...
  3. ...
  1. with x := {1, 2, 3, 4, 5}
  2. select x
  3. filter x >= 3;
  1. {3, 4, 5}
  1. db>
  2. ...
  3. ...
  1. with x := {1, 2, 3, 4, 5}
  2. select x
  3. order by x desc;
  1. {5, 4, 3, 2, 1}
  1. db>
  2. ...
  3. ...
  1. with x := {1, 2, 3, 4, 5}
  2. select x
  3. offset 1 limit 3;
  1. {2, 3, 4}

These queries can also be rewritten to use inline aliases, like so:

  1. db>
  2. ...
  1. select x := {1, 2, 3, 4, 5}
  2. filter x >= 3;

Selecting objects

However most queries are selecting objects that live in the database. For demonstration purposes, the queries below assume the following schema.

  1. module default {
  2. abstract type Person {
  3. required property name -> str { constraint exclusive };
  4. }
  5. type Hero extending Person {
  6. property secret_identity -> str;
  7. multi link villains := .<nemesis[is Villain];
  8. }
  9. type Villain extending Person {
  10. link nemesis -> Hero;
  11. }
  12. type Movie {
  13. required property title -> str { constraint exclusive };
  14. required property release_year -> int64;
  15. multi link characters -> Person;
  16. }
  17. }

Let’s start by selecting all Villains objects in the database. In this example, there are only three. Remember, Villain is a reference to the set of all Villain objects.

  1. db>
  1. select Villain;
  1. {
  2. default::Villain {id: ea7bad4c...},
  3. default::Villain {id: 6ddbb04a...},
  4. default::Villain {id: b233ca98...},
  5. }

For the sake of readability, the id values have been truncated.

By default, this only returns the id of each object. If serialized to JSON, this result would look like this:

  1. [
  2. {"id": "ea7bad4c-35d6-11ec-9519-0361f8abd380"},
  3. {"id": "6ddbb04a-3c23-11ec-b81f-7b7516f2a868"},
  4. {"id": "b233ca98-3c23-11ec-b81f-6ba8c4f0084e"},
  5. ]

Shapes

To specify which properties to select, we attach a shape to Hero. A shape can be attached to any object type expression in EdgeQL.

  1. db>
  1. select Villain { id, name };
  1. {
  2. default::Villain { id: ea7bad4c..., name: 'Whiplash' },
  3. default::Villain { id: 6ddbb04a..., name: 'Green Goblin', },
  4. default::Villain { id: b233ca98..., name: 'Doc Ock' },
  5. }

Nested shapes

Nested shapes can be used to fetch linked objects and their properties. Here we fetch all Villain objects and their nemeses.

  1. db>
  2. ...
  3. ...
  4. ...
  1. select Villain {
  2. name,
  3. nemesis: { name }
  4. };
  1. {
  2. default::Villain {
  3. name: 'Green Goblin',
  4. nemesis: default::Hero {name: 'Spider-Man'},
  5. },
  6. ...
  7. }

In the context of EdgeQL, computed links like Hero.villains are treated identically to concrete/non-computed links like Villain.nemesis.

  1. db>
  2. ...
  3. ...
  4. ...
  1. select Hero {
  2. name,
  3. villains: { name }
  4. };
  1. {
  2. default::Hero {
  3. name: 'Spider-Man',
  4. villains: {
  5. default::Villain {name: 'Green Goblin'},
  6. default::Villain {name: 'Doc Ock'},
  7. },
  8. },
  9. ...
  10. }

Filtering

To filter the set of selected objects, use a filter <expr> clause. The <expr> that follows the filter keyword can be any boolean expression.

To reference the name property of the Villain objects being selected, we use Villain.name.

  1. db>
  2. ...
  1. select Villain {id, name}
  2. filter Villain.name = "Doc Ock";
  1. {default::Villain {id: b233ca98..., name: 'Doc Ock'}}

This query contains two occurrences of Villain. The first (outer) is passed as the argument to select and refers to the set of all Villain objects. However the inner occurrence is inside the scope of the select statement and refers to the object being selected.

However, this looks a little clunky, so EdgeQL provides a shorthand: just drop Villain entirely and simply use .name. Since we are selecting a set of Villains, it’s clear from context that .name must refer to a link/property of the Villain type. In other words, we are in the scope of the Villain type.

  1. db>
  2. ...
  1. select Villain {name}
  2. filter .name = "Doc Ock";
  1. {default::Villain {name: 'Doc Ock'}}

Filtering by ID

To filter by id, remember to cast the desired ID to uuid:

  1. db>
  2. ...
  1. select Villain {id, name}
  2. filter .id = <uuid>"b233ca98-3c23-11ec-b81f-6ba8c4f0084e";
  1. {
  2. default::Villain {
  3. id: 'b233ca98-3c23-11ec-b81f-6ba8c4f0084e',
  4. name: 'Doc Ock'
  5. }
  6. }

Nested filters

Filters can be added at every level of shape nesting. The query below applies a filter to both the selected Hero objects and their linked villains.

  1. db>
  2. ...
  3. ...
  4. ...
  5. ...
  6. ...
  1. select Hero {
  2. name,
  3. villains: {
  4. name
  5. } filter .name ilike "%er"
  6. } filter .name ilike "%man";
  1. {
  2. default::Hero {
  3. name: 'Iron Man',
  4. villains: {default::Villain {name: 'Justin Hammer'}},
  5. },
  6. default::Hero {
  7. name: 'Spider-Man',
  8. villains: {
  9. default::Villain {name: 'Shocker'},
  10. default::Villain {name: 'Tinkerer'},
  11. default::Villain {name: 'Kraven the Hunter'},
  12. },
  13. },
  14. }

Note that the scope changes inside nested shapes. When we use .name in the outer filter, it refers to the name of the hero. But when we use .name in the nested villains shape, the scope has changed to Villain.

Ordering

Order the result of a query with an order by clause.

  1. db>
  2. ...
  1. select Villain { name }
  2. order by .name;
  1. {
  2. default::Villain {name: 'Abomination'},
  3. default::Villain {name: 'Doc Ock'},
  4. default::Villain {name: 'Green Goblin'},
  5. default::Villain {name: 'Justin Hammer'},
  6. default::Villain {name: 'Kraven the Hunter'},
  7. default::Villain {name: 'Loki'},
  8. default::Villain {name: 'Shocker'},
  9. default::Villain {name: 'The Vulture'},
  10. default::Villain {name: 'Tinkerer'},
  11. default::Villain {name: 'Zemo'},
  12. }

The expression provided to order by may be any singleton expression, primitive or otherwise.

In EdgeDB all values are orderable. Objects are compared using their id; tuples and arrays are compared element-by-element from left to right. By extension, the generic comparison operators \=, <, >, etc. can be used with any two expressions of the same type.

You can also order by multiple expressions and specify the direction with an asc (default) or desc modifier.

When ordering by multiple expressions, arrays, or tuples, the leftmost expression/element is compared. If these elements are the same, the next element is used to “break the tie”, and so on. If all elements are the same, the order is not well defined.

  1. db>
  2. ...
  3. ...
  4. ...
  1. select Movie { title, release_year }
  2. order by
  3. .release_year desc then
  4. str_trim(.title) desc;
  1. {
  2. default::Movie {title: 'Spider-Man: No Way Home', release_year: 2021},
  3. ...
  4. default::Movie {title: 'Iron Man', release_year: 2008},
  5. }

When ordering by multiple expressions, each expression is separated with the then keyword. For a full reference on ordering, including how empty values are handled, see Reference > Commands > Select.

Pagination

EdgeDB supports limit and offset clauses. These are typically used in conjunction with order by to maintain a consistent ordering across pagination queries.

  1. db>
  2. ...
  3. ...
  4. ...
  1. select Villain { name }
  2. order by .name
  3. offset 3
  4. limit 3;
  1. {
  2. default::Villain {name: 'Hela'},
  3. default::Villain {name: 'Justin Hammer'},
  4. default::Villain {name: 'Kraven the Hunter'},
  5. }

The expressions passed to limit and offset can be any singleton int64 expression. This query fetches all Villains except the last (sorted by name).

  1. db>
  2. ...
  3. ...
  1. select Villain {name}
  2. order by .name
  3. limit count(Villain) - 1;
  1. {
  2. default::Villain {name: 'Abomination'},
  3. default::Villain {name: 'Doc Ock'},
  4. ...
  5. default::Villain {name: 'Winter Soldier'}, # no Zemo
  6. }

Computed fields

Shapes can contain computed fields. These are EdgeQL expressions that are computed on the fly during the execution of the query. As with other clauses, we can use leading dot notation (e.g. .name) to refer to the properties and links of the object type currently in scope.

  1. db>
  2. ...
  3. ...
  4. ...
  1. select Villain {
  2. name,
  3. name_upper := str_upper(.name)
  4. };
  1. {
  2. default::Villain {
  3. id: 4114dd56...,
  4. name: 'Abomination',
  5. name_upper: 'ABOMINATION',
  6. },
  7. ...
  8. }

As with nested filters, the current scope changes inside nested shapes.

  1. db>
  2. ...
  3. ...
  4. ...
  5. ...
  6. ...
  7. ...
  8. ...
  9. ...
  1. select Villain {
  2. id,
  3. name,
  4. name_upper := str_upper(.name),
  5. nemesis: {
  6. secret_identity,
  7. real_name_upper := str_upper(.secret_identity)
  8. }
  9. };
  1. {
  2. default::Villain {
  3. id: 6ddbb04a...,
  4. name: 'Green Goblin',
  5. name_upper: 'GREEN GOBLIN',
  6. nemesis: default::Hero {
  7. secret_identity: 'Peter Parker',
  8. real_name_upper: 'PETER PARKER',
  9. },
  10. },
  11. ...
  12. }

Fetching backlinks is a common use case for computed fields. To demonstrate this, let’s fetch a list of all movies starring a particular Hero.

  1. db>
  2. ...
  3. ...
  4. ...
  1. select Hero {
  2. name,
  3. movies := .<characters[is Movie] { title }
  4. } filter .name = "Iron Man";
  1. {
  2. default::Hero {
  3. name: 'Iron Man',
  4. movies: {
  5. default::Movie {title: 'Iron Man'},
  6. default::Movie {title: 'Iron Man 2'},
  7. default::Movie {title: 'Iron Man 3'},
  8. default::Movie {title: 'Captain America: Civil War'},
  9. default::Movie {title: 'The Avengers'},
  10. },
  11. },
  12. }

The computed backlink villains is a combination of the backlink operator .< and a type intersection [is Villain]. For a full reference on backlink syntax, see EdgeQL > Paths.

Instead of re-declaring backlinks inside every query where they’re needed, it’s common to add them directly into your schema as computed links.

  1. abstract type Person {
  2. required property name -> str {
  3. constraint exclusive;
  4. };
  5. multi link movies := .<characters[is Movie]
  6. }

In the example above, the Person.movies is a multi link. Including these keywords is optional, since EdgeDB can infer this from the assigned expression .<characters[is Movie]. However, it’s a good practice to include the explicit keywords to make the schema more readable and “sanity check” the cardinality.

This simplifies future queries; Person.movies can now be traversed in shapes just like a non-computed link.

  1. select Hero {
  2. name,
  3. movies: { title }
  4. } filter .name = "Iron Man";

Subqueries

There’s no limit to the complexity of computed expressions. EdgeQL is designed to be fully composable; entire queries can be embedded inside each other. Below, we use a subquery to select all movies containing a villain’s nemesis.

  1. db>
  2. ...
  3. ...
  4. ...
  5. ...
  6. ...
  7. ...
  8. ...
  1. select Villain {
  2. name,
  3. nemesis_name := .nemesis.name,
  4. movies_with_nemesis := (
  5. select Movie { title }
  6. filter Villain.nemesis in .characters
  7. )
  8. };
  1. {
  2. default::Villain {
  3. name: 'Loki',
  4. nemesis_name: 'Thor',
  5. movies_with_nemesis: {
  6. default::Movie {title: 'Thor'},
  7. default::Movie {title: 'Thor: The Dark World'},
  8. default::Movie {title: 'Thor: Ragnarok'},
  9. default::Movie {title: 'The Avengers'},
  10. },
  11. },
  12. ...
  13. }

Polymorphic queries

All queries thus far have referenced concrete object types: Hero and Villain. However, both of these types extend the abstract type Person, from which they inherit the name property.

Polymorphic sets

It’s possible to directly query all Person objects; the resulting set with be a mix of Hero and Villain objects (and possibly other subtypes of Person, should they be declared).

  1. db>
  1. select Person { name };
  1. {
  2. default::Villain {name: 'Abomination'},
  3. default::Villain {name: 'Zemo'},
  4. default::Hero {name: 'The Hulk'},
  5. default::Hero {name: 'Iron Man'},
  6. ...
  7. }

You may also encounter such “mixed sets” when querying a link that points to an abstract type (such as Movie.characters) or a union type.

  1. db>
  2. ...
  3. ...
  4. ...
  5. ...
  6. ...
  7. ...
  1. select Movie {
  2. title,
  3. characters: {
  4. name
  5. }
  6. }
  7. filter .title = "Iron Man 2";
  1. {
  2. default::Movie {
  3. title: 'Iron Man 2',
  4. characters: {
  5. default::Villain {name: 'Whiplash'},
  6. default::Villain {name: 'Justin Hammer'},
  7. default::Hero {name: 'Iron Man'},
  8. default::Hero {name: 'Black Widow'},
  9. },
  10. },
  11. }

Polymorphic fields

We can fetch different properties conditional on the subtype of each object by prefixing property/link references with [is <type>]. This is known as a polymorphic query.

  1. db>
  2. ...
  3. ...
  4. ...
  5. ...
  6. ...
  7. ...
  8. ...
  1. select Person {
  2. name,
  3. secret_identity := [is Hero].secret_identity,
  4. number_of_villains := count([is Hero].villains),
  5. nemesis := [is Villain].nemesis {
  6. name
  7. }
  8. };
  1. {
  2. default::Villain {
  3. name: 'Green Goblin',
  4. secret_identity: {},
  5. number_of_villains: 0,
  6. nemesis: default::Hero {name: 'Spider-Man'},
  7. },
  8. default::Hero {
  9. name: 'Spider-Man',
  10. secret_identity: 'Peter Parker',
  11. number_of_villains: 6,
  12. nemesis: {},
  13. },
  14. ...
  15. }

This syntax might look familiar; it’s the type intersection again. In effect, this operator conditionally returns the value of the referenced field only if the object matches a particular type. If the match fails, an empty set is returned.

The line secret_identity := [is Hero].secret_identity is a bit redundant, since the computed property has the same name as the polymorphic field. In these cases, EdgeQL supports a shorthand.

  1. db>
  2. ...
  3. ...
  4. ...
  5. ...
  6. ...
  7. ...
  1. select Person {
  2. name,
  3. [is Hero].secret_identity,
  4. [is Villain].nemesis: {
  5. name
  6. }
  7. };
  1. {
  2. default::Villain {
  3. name: 'Green Goblin',
  4. secret_identity: {},
  5. nemesis: default::Hero {name: 'Spider-Man'},
  6. },
  7. default::Hero {
  8. name: 'Spider-Man',
  9. secret_identity: 'Peter Parker',
  10. nemesis: {},
  11. },
  12. ...
  13. }

Relatedly, it’s possible to filter polymorphic links by subtype. Below, we exclusively fetch the Movie.characters of type Hero.

  1. db>
  2. ...
  3. ...
  4. ...
  5. ...
  6. ...
  1. select Movie {
  2. title,
  3. characters[is Hero]: {
  4. secret_identity
  5. },
  6. };
  1. {
  2. default::Movie {
  3. title: 'Spider-Man: Homecoming',
  4. characters: {default::Hero {secret_identity: 'Peter Parker'}},
  5. },
  6. default::Movie {
  7. title: 'Iron Man',
  8. characters: {default::Hero {secret_identity: 'Tony Stark'}},
  9. },
  10. ...
  11. }

Free objects

To select several values simultaneously, you can “bundle” them into a “free object”. Free objects are a set of key-value pairs that can contain any expression. Here, the term “free” is used to indicate that the object in question is not an instance of a particular object type; instead, it’s constructed ad hoc inside the query.

  1. db>
  2. ...
  3. ...
  4. ...
  5. ...
  6. ...
  1. select {
  2. my_string := "This is a string",
  3. my_number := 42,
  4. several_numbers := {1, 2, 3},
  5. all_heroes := Hero { name }
  6. };
  1. {
  2. {
  3. my_string: 'This is a string',
  4. my_number: 42,
  5. several_numbers: {1, 2, 3},
  6. all_heroes: {
  7. default::Hero {name: 'The Hulk'},
  8. default::Hero {name: 'Iron Man'},
  9. default::Hero {name: 'Spider-Man'},
  10. default::Hero {name: 'Thor'},
  11. default::Hero {name: 'Captain America'},
  12. default::Hero {name: 'Black Widow'},
  13. },
  14. },
  15. }

Note that the result is a singleton but each key corresponds to a set of values, which may have any cardinality.

With block

All top-level EdgeQL statements (select, insert, update, and delete) can be prefixed with a with block. These blocks let you declare standalone expressions that can be used in your query.

  1. db>
  2. ...
  3. ...
  1. with hero_name := "Iron Man"
  2. select Hero { secret_identity }
  3. filter .name = hero_name;
  1. {default::Hero {secret_identity: 'Tony Stark'}}

For full documentation on with, see EdgeQL > With.

See also

Reference > Commands > Select

Cheatsheets > Selecting data