Search
ctrl/
Ask AI
Light
Dark
System

Select

The full power of the EdgeQL select statement is available as a top-level e.select function. All queries on this page assume the Netflix schema described on the Objects page.

Any scalar expression be passed into e.select, though it’s often unnecessary, since expressions are runable without being wrapped by e.select.

Copy
e.select(e.str('Hello world'));
// select 1234;

e.select(e.op(e.int64(2), '+', e.int64(2)));
// select 2 + 2;

As in EdgeQL, selecting an set of objects without a shape will return their id property only. This is reflected in the TypeScript type of the result.

Copy
const query = e.select(e.Movie);
// select Movie;

const result = await query.run(client);
// {id:string}[]

To specify a shape, pass a function as the second argument. This function should return an object that specifies which properties to include in the result. This roughly corresponds to a shape in EdgeQL.

Copy
const query = e.select(e.Movie, ()=>({
  id: true,
  title: true,
  release_year: true,
}));
/*
  select Movie {
    id,
    title,
    release_year
  }
*/

Note that the type of the query result is properly inferred from the shape. This is true for all queries on this page.

Copy
const result = await query.run(client);
/* {
  id: string;
  title: string;
  release_year: number | null;
}[] */

As you can see, the type of release_year is number | null since it’s an optional property, whereas id and title are required.

Passing a boolean value (as opposed to a true literal), which will make the property optional. Passing false will exclude that property.

Copy
e.select(e.Movie, movie => ({
  id: true,
  title: Math.random() > 0.5,
  release_year: false,
}));

const result = await query.run(client);
// {id: string; title: string | undefined; release_year: never}[]

For convenience, the query builder provides a shorthand for selecting all properties of a given object.

Copy
e.select(e.Movie, movie => ({
  ...e.Movie['*']
}));

const result = await query.run(client);
// {id: string; title: string; release_year: number | null}[]

This * property is just a strongly-typed, plain object:

Copy
e.Movie['*'];
// => {id: true, title: true, release_year: true}

To select a particular object, use the filter_single key. This tells the query builder to expect a singleton result.

Copy
e.select(e.Movie, (movie) => ({
  id: true,
  title: true,
  release_year: true,

  filter_single: {id: '2053a8b4-49b1-437a-84c8-e1b0291ccd9f'},
}));

This also works if an object type has a composite exclusive constraint:

Copy
/*
  type Movie {
    ...
    constraint exclusive on (.title, .release_year);
  }
*/

e.select(e.Movie, (movie) => ({
  title: true,
  filter_single: {title: 'The Avengers', release_year: 2012},
}));

You can also pass an arbitrary boolean expression to filter_single if you prefer.

Copy
const query = e.select(e.Movie, (movie) => ({
  id: true,
  title: true,
  release_year: true,
  filter_single: e.op(movie.id, '=', '2053a8b4-49b1-437a-84c8-e1b0291ccd9f'),
}));

const result = await query.run(client);
// {id: string; title: string; release_year: number | null}
Copy
const query = e.params({ ids: e.array(e.uuid) }, ({ ids }) =>
  e.select(e.Movie, (movie) => ({
    id: true,
    title: true,
    release_year: true,
    filter: e.op(movie.id, 'in', e.array_unpack(ids)),
  }))

const result = await query.run(client, {
  ids: [
    '2053a8b4-49b1-437a-84c8-e1b0291ccd9f',
    '2053a8b4-49b1-437a-84c8-af5d3f383484',
  ],
})
// {id: string; title: string; release_year: number | null}[]

As in EdgeQL, shapes can be nested to fetch deeply related objects.

Copy
const query = e.select(e.Movie, () => ({
  id: true,
  title: true,
  actors: {
    name: true
  }
}));

const result = await query.run(client);
/* {
  id: string;
  title: string;
  actors: { name: string }[]
}[] */

You can use e.shape to define a “portable shape” that can be defined independently and used in multiple queries.

Copy
const baseShape = e.shape(e.Movie, (m) => ({
  title: true,
  num_actors: e.count(m)
}));

const query = e.select(e.Movie, m => ({
  ...baseShape(m),
  release_year: true,
  filter_single: {title: 'The Avengers'}
}))

Note that the result of e.shape is a function. When you use the shape in your final queries, be sure to pass in the scope variable (e.g. m in the example above). This is required for the query builder to correctly resolve the query.

In EdgeQL, a select statement introduces a new scope; within the clauses of a select statement, you can refer to fields of the elements being selected using leading dot notation.

Copy
select Movie { id, title }
filter .title = "The Avengers";

Here, .title is shorthand for the title property of the selected Movie elements. All properties/links on the Movie type can be referenced using this shorthand anywhere in the select expression. In other words, the select expression is scoped to the Movie type.

To represent this scoping in the query builder, we use function scoping. This is a powerful pattern that makes it painless to represent filters, ordering, computed fields, and other expressions. Let’s see it in action.

To add a filtering clause, just include a filter key in the returned params object. This should correspond to a boolean expression.

Copy
e.select(e.Movie, movie => ({
  id: true,
  title: true,
  filter: e.op(movie.title, 'ilike', "The Matrix%")
}));
/*
  select Movie {
    id,
    title
  } filter .title ilike "The Matrix%"
*/

Since filter is a reserved keyword in EdgeDB, there is minimal danger of conflicting with a property or link named filter. All shapes can contain filter clauses, even nested ones.

If you have many conditions you want to test for, your filter can start to get difficult to read.

Copy
e.select(e.Movie, movie => ({
  id: true,
  title: true,
  filter: e.op(
    e.op(
      e.op(movie.title, 'ilike', "The Matrix%"),
      'and',
      e.op(movie.release_year, '=', 1999)
    ),
    'or',
    e.op(movie.title, '=', 'Iron Man')
  )
}));

To improve readability, we recommend breaking these operations out into named variables and composing them.

Copy
e.select(e.Movie, movie => {
  const isAMatrixMovie = e.op(movie.title, 'ilike', "The Matrix%");
  const wasReleased1999 = e.op(movie.release_year, '=', 1999);
  const isIronMan = e.op(movie.title, '=', 'Iron Man');
  return {
    id: true,
    title: true,
    filter: e.op(
      e.op(
        isAMatrixMovie,
        'and',
        wasReleased1999
      ),
      'or',
      isIronMan
    )
  }
});

You can combine compound conditions as much or as little as makes sense for your application.

Copy
e.select(e.Movie, movie => {
  const isAMatrixMovie = e.op(movie.title, 'ilike', "The Matrix%");
  const wasReleased1999 = e.op(movie.release_year, '=', 1999);
  const isAMatrixMovieReleased1999 = e.op(
    isAMatrixMovie,
    'and',
    wasReleased1999
  );
  const isIronMan = e.op(movie.title, '=', 'Iron Man');
  return {
    id: true,
    title: true,
    filter: e.op(
      isAMatrixMovieReleased1999
      'or',
      isIronMan
    )
  }
});

If you need to string together several conditions with or, e.any may be a better choice. Be sure to wrap your conditions in e.set since e.any takes a set.

Copy
e.select(e.Movie, movie => ({
  id: true,
  title: true,
  filter: e.any(
    e.set(
      e.op(movie.title, "=", "Iron Man"),
      e.op(movie.title, "ilike", "guardians%"),
      e.op(movie.title, "ilike", "captain%")
    )
  ),
}));

Similarly to e.any, e.all can replace multiple conditions strung together with and.

Copy
e.select(e.Movie, movie => ({
  id: true,
  title: true,
  filter: e.all(
    e.set(
      e.op(movie.title, "ilike", "captain%"),
      e.op(movie.title, "ilike", "%america%"),
      e.op(movie.title, "ilike", "%:%")
    )
  ),
}));

The conditions passed to e.any or e.all can be composed just like before.

Copy
e.select(e.Movie, movie => {
  const isIronMan = e.op(movie.title, "=", "Iron Man");
  const startsWithGuardians = e.op(movie.title, "ilike", "guardians%");
  const startsWithCaptain = e.op(movie.title, "ilike", "captain%");
  return {
    id: true,
    title: true,
    filter: e.any(
      e.set(
        isIronMan,
        startsWithGuardians,
        startsWithCaptain
      )
    ),
  }
});

As with filter, you can pass a value with the special order_by key. To simply order by a property:

Copy
e.select(e.Movie, movie => ({
  order_by: movie.title,
}));

Unlike filter, order_by is not a reserved word in EdgeDB. Using order_by as a link or property name will create a naming conflict and likely cause bugs.

The order_by key can correspond to an arbitrary expression.

Copy
// order by length of title
e.select(e.Movie, movie => ({
  order_by: e.len(movie.title),
}));
/*
  select Movie
  order by len(.title)
*/

// order by number of actors
e.select(e.Movie, movie => ({
  order_by: e.count(movie.actors),
}));
/*
  select Movie
  order by count(.actors)
*/

You can customize the sort direction and empty-handling behavior by passing an object into order_by.

Copy
e.select(e.Movie, movie => ({
  order_by: {
    expression: movie.title,
    direction: e.DESC,
    empty: e.EMPTY_FIRST,
  },
}));
/*
  select Movie
  order by .title desc empty first
*/

Order direction

e.DESC e.ASC

Empty handling

e.EMPTY_FIRST e.EMPTY_LAST

Pass an array of objects to do multiple ordering.

Copy
e.select(e.Movie, movie => ({
  title: true,
  order_by: [
    {
      expression: movie.title,
      direction: e.DESC,
    },
    {
      expression: e.count(movie.actors),
      direction: e.ASC,
      empty: e.EMPTY_LAST,
    },
  ],
}));

To add a computed field, just add it to the returned shape alongside the other elements. All reflected functions are typesafe, so the output type

Copy
const query = e.select(e.Movie, movie => ({
  title: true,
  uppercase_title: e.str_upper(movie.title),
  title_length: e.len(movie.title),
}));

const result = await query.run(client);
/* =>
  [
    {
      title:"Iron Man",
      uppercase_title: "IRON MAN",
      title_length: 8
    },
    ...
  ]
*/
// {name: string; uppercase_title: string, title_length: number}[]

Computed fields can “override” an actual link/property as long as the type signatures agree.

Copy
e.select(e.Movie, movie => ({
  title: e.str_upper(movie.title), // this works
  release_year: e.str("2012"), // TypeError

  // you can override links too
  actors: e.Person,
}));

EdgeQL supports polymorphic queries using the [is type] prefix.

Copy
select Content {
  title,
  [is Movie].release_year,
  [is TVShow].num_seasons
}

In the query builder, this is represented with the e.is function.

Copy
e.select(e.Content, content => ({
  title: true,
  ...e.is(e.Movie, { release_year: true }),
  ...e.is(e.TVShow, { num_seasons: true }),
}));

const result = await query.run(client);
/* {
  title: string;
  release_year: number | null;
  num_seasons: number | null;
}[] */

The release_year and num_seasons properties are nullable to reflect the fact that they will only occur in certain objects.

In EdgeQL it is not valid to select the id property in a polymorphic field. So for convenience when using the ['*'] all properties shorthand with e.is, the id property will be filtered out of the polymorphic shape object.

Sometimes you need to “detach” a set reference from the current scope. (Read the reference docs for details.) You can achieve this in the query builder with the top-level e.detached function.

Copy
const query = e.select(e.Person, (outer) => ({
  name: true,
  castmates: e.select(e.detached(e.Person), (inner) => ({
    name: true,
    filter: e.op(outer.acted_in, 'in', inner.acted_in)
  })),
}));
/*
  with outer := Person
  select Person {
    name,
    castmates := (
      select detached Person { name }
      filter .acted_in in Person.acted_in
    )
  }
*/

Select a free object by passing an object into e.select

Copy
e.select({
  name: e.str("Name"),
  number: e.int64(1234),
  movies: e.Movie,
});
/* select {
  name := "Name",
  number := 1234,
  movies := Movie
} */