Ecto.Query.with_cte

You're seeing just the macro with_cte, go back to Ecto.Query module for more information.
Link to this macro

with_cte(query, name, list)

View Source (macro)

A common table expression (CTE) also known as WITH expression.

name must be a compile-time literal string that is being used as the table name to join the CTE in the main query or in the recursive CTE.

IMPORTANT! Beware of using CTEs. In raw SQL, CTEs can be used as a mechanism to organize queries, but said mechanism has no purpose in Ecto since Ecto queries are composable by definition. In other words, if you need to break a large query into parts, use all of the functionality in Elixir and in this module to structure your code. Furthermore, breaking a query into CTEs can negatively impact performance, as the database may not optimize efficiently across CTEs. The main use case for CTEs in Ecto is to provide recursive definitions, which we outline in the following section. Non-recursive CTEs can often be written as joins or subqueries, which provide better performance.

Options

  • :as - the CTE query itself or a fragment

Recursive CTEs

Use recursive_ctes/2 to enable recursive mode for CTEs.

In the CTE query itself use the same table name to leverage recursion that has been passed to the name argument. Make sure to write a stop condition to avoid an infinite recursion loop. Generally speaking, you should only use CTEs in Ecto for writing recursive queries.

Expression examples

Products and their category names for breadcrumbs:

category_tree_initial_query =
  Category
  |> where([c], is_nil(c.parent_id))

category_tree_recursion_query =
  Category
  |> join(:inner, [c], ct in "category_tree", on: c.parent_id == ct.id)

category_tree_query =
  category_tree_initial_query
  |> union_all(^category_tree_recursion_query)

Product
|> recursive_ctes(true)
|> with_cte("category_tree", as: ^category_tree_query)
|> join(:left, [p], c in "category_tree", on: c.id == p.category_id)
|> group_by([p], p.id)
|> select([p, c], %{p | category_names: fragment("ARRAY_AGG(?)", c.name)})

It's also possible to pass a raw SQL fragment:

@raw_sql_category_tree """
SELECT * FROM categories WHERE c.parent_id IS NULL
UNION ALL
SELECT * FROM categories AS c, category_tree AS ct WHERE ct.id = c.parent_id
"""

Product
|> recursive_ctes(true)
|> with_cte("category_tree", as: fragment(@raw_sql_category_tree))
|> join(:inner, [p], c in "category_tree", on: c.id == p.category_id)

If you don't have any Ecto schema pointing to the CTE table, you can pass a tuple with the CTE table name as the first element and an Ecto schema as the second element. This will cast the result rows to Ecto structs as long as the Ecto schema maps to the same fields in the CTE table:

{"category_tree", Category}
|> recursive_ctes(true)
|> with_cte("category_tree", as: ^category_tree_query)
|> join(:left, [c], p in assoc(c, :products))
|> group_by([c], c.id)
|> select([c, p], %{c | products_count: count(p.id)})

Keyword syntax is not supported for this feature.

Limitation: CTEs on schemas with source fields

Ecto allows developers to say that a table in their Ecto schema maps to a different column in their database:

field :group_id, :integer, source: :iGroupId

At the moment, using a schema with source fields in CTE may emit invalid queries. If you are running into such scenarios, your best option is to use a fragment as your CTE.