Ecto.Query.API
Исходный кодПеречисляет все функции, разрешённые в API запросов.
- Операторы сравнения:
==,!=,<=,>=,<,> - Арифметические операторы:
+,-,*,/ - Логические операторы:
and,or,not - Оператор включения:
in/2 - Операторы подзапросов:
any,allиexists - Функции поиска:
like/2иilike/2 - Функции проверки на null:
is_nil/1 - Агрегатные функции:
count/0,count/1,avg/1,sum/1,min/1,max/1 - Интервалы даты и времени:
datetime_add/3,date_add/3,from_now/2,ago/2 - Внутри select:
struct/2,map/2,merge/2,selected_as/2и литералы (карты, кортежи, списки и т. д.) - Общие:
fragment/1,field/2,type/2,as/1,parent_as/1,selected_as/1
Обратите внимание: функции этого модуля существуют только для документации, и вы никогда не должны вызывать их напрямую. Кроме того, можно определять собственные макросы и использовать их в запросах Ecto (см. документацию по fragment/1).
Интервалы
Ecto поддерживает следующие значения для параметра interval: "year", "month", "week", "day", "hour", "minute", "second", "millisecond" и "microsecond".
Функции Date/Time, такие как datetime_add/3, date_add/3, from_now/2, ago/2, принимают interval в качестве аргумента.
API оконных функций
Ecto также поддерживает многие оконные функции, доступные в базах данных SQL. Дополнительную информацию см. в разделе Ecto.Query.WindowAPI.
Об арифметических операторах
Реализация этих операторов в Ecto представляет собой лишь тонкую обёртку над адаптерами. Поэтому, если ваш адаптер позволяет использовать их определённым образом (например, складывать дату и интервал в PostgreSQL), это будет работать и в запросах Ecto.
Краткое описание
- left * right
Бинарная операция
*.- left + right
Бинарная операция
+.- left - right
Бинарная операция
-.- left / right
Бинарная операция
/.- left != right
Бинарная операция
!=.- left < right
Бинарная операция
<.- left <= right
Бинарная операция
<=.- left == right
Бинарная операция
==.- left > right
Бинарная операция
>.- left >= right
Бинарная операция
>=.- ago(count, interval)
Вычитает указанный интервал из текущего времени UTC.
- all(subquery)
Проверяет, соответствуют ли все значения, возвращённые указанным подзапросом, условию операции сравнения.
- left and right
Бинарная операция
and.- any(subquery)
Проверяет, соответствуют ли одно или несколько значений, возвращённых указанным подзапросом, условию операции сравнения.
- as(binding)
Ссылается на привязку с именем в виде атома.
- avg(value)
Вычисляет среднее значение для указанной записи.
- coalesce(value, expr)
Возвращает первое значение, которое не равно null, или null, если оба значения равны null.
- constant(value)
Позволяет вставить динамическую строку или число во фрагмент
- count()
Подсчитывает записи в таблице.
- count(value)
Подсчитывает указанную запись.
- count(value, atom)
Подсчитывает различные значения в указанной записи.
- date_add(date, count, interval)
Добавляет указанный интервал к дате.
- datetime_add(datetime, count, interval)
Добавляет указанный интервал к дате и времени.
- exists(subquery)
Возвращает true, если указанный подзапрос возвращает одну или несколько строк.
- field(source, field)
Позволяет динамически обращаться к полю.
- filter(value, filter)
Применяет указанное выражение как предложение FILTER к агрегатной функции. В настоящее время поддерживается только Postgres.
- fragment(fragments)
Передаёт фрагменты непосредственно в базу данных.
- from_now(count, interval)
Добавляет указанный интервал к текущему времени UTC.
- identifier(binary)
Позволяет вставить динамический идентификатор во фрагмент
- ilike(string, search)
Выполняет поиск
searchвstringбез учёта регистра.- left in right
Проверяет, содержится ли значение слева среди значений справа.
- is_nil(value)
Проверяет, равно ли указанное значение nil.
- json_extract_path(json_field, path)
Возвращает значение из
json_field, на которое указываетpath.- like(string, search)
Выполняет поиск
searchвstring.- map(source, fields)
Используется в
selectдля указания полей, которые следует вернуть в виде карты.- max(value)
Вычисляет максимальное значение для указанной записи.
- merge(left_map, right_map)
Объединяет карты, добавляя карту справа поверх карты слева.
- min(value)
Вычисляет минимальное значение для указанной записи.
- not value
Унарная операция
not.- left or right
Бинарная операция
or.- parent_as(binding)
Ссылается на привязку с именем в виде атома в родительском запросе.
- selected_as(name)
Ссылается на псевдоним выбранного значения.
- selected_as(selected_value, name)
Создаёт псевдоним для указанного выбранного значения.
- splice(list)
Позволяет вставить аргумент-список во фрагмент.
- struct(source, fields)
Используется в
selectдля указания полей структуры, которые следует вернуть.- sum(value)
Вычисляет сумму для указанной записи.
- type(interpolated_value, type)
Приводит указанное значение к заданному типу на уровне базы данных.
- values(values, types)
Создаёт список значений/таблицу-константу.
Функции
Функции
left * rightИсточник
Бинарная операция *.
left + rightИсточник
Бинарная операция +.
left - rightИсточник
Бинарная операция -.
left / rightИсточник
Бинарная операция /.
left != rightИсточник
Бинарная операция !=.
left < rightИсточник
Бинарная операция <.
left <= rightИсточник
Бинарная операция <=.
left == rightИсточник
Бинарная операция ==.
left > rightИсточник
Бинарная операция >.
left >= rightИсточник
Бинарная операция >=.
ago(count, interval)Источник
Вычитает заданный интервал из текущего времени в UTC.
Текущее время в UTC получается из Elixir, а не из базы данных.
Поддерживаемые значения interval см. в разделе Интервалы.
Примеры
from p in Post, where: p.published_at > ago(3, "month")
all(subquery)Источник
Проверяет, соответствуют ли все значения, возвращённые указанным подзапросом, условию операции сравнения.
from p in Post, where: p.visits >= all( from(p in Post, select: avg(p.visits), group_by: [p.category_id]) )
Чтобы запись о публикации соответствовала условию в приведённом выше примере, её должны посещать не реже, чем среднюю публикацию во всех категориях.
from p in Post, where: p.visits == all( from(p in Post, select: max(p.visits)) )
Приведённый выше пример выбирает все публикации с наибольшим количеством посещений.
В качестве аргумента и any, и all должен передаваться подзапрос, а сами функции должны использоваться в правой части операции сравнения. Обе функции можно использовать с любым оператором сравнения: ==, !=, >, >=, <, <=.
left and rightИсточник
Бинарная операция and.
any(subquery)Источник
Проверяет, соответствует ли условию операции сравнения хотя бы одно из значений, возвращённых указанным подзапросом.
from p in Product, where: p.id == any( from(li in LineItem, select: [li.product_id], where: li.created_at > ^since and li.qty >= 10) )
В приведённом выше примере товар соответствует условию, если после указанной даты была создана позиция заказа, в которой покупатель приобрёл не менее 10 единиц товара.
В качестве аргумента и any, и all должен передаваться подзапрос, а сами функции должны использоваться в правой части операции сравнения. Обе функции можно использовать с любым оператором сравнения: ==, !=, >, >=, <, <=.
as(binding)Источник
Ссылка на привязку с именем, заданным атомом.
Дополнительные сведения см. в разделе Именованные привязки.
avg(value)Источник
Вычисляет среднее значение для заданной записи.
from p in Payment, select: avg(p.value)
coalesce(value, expr)Источник
Возвращает первое значение, не равное null, или null, если оба значения равны null.
В SQL функция COALESCE принимает любое количество аргументов, но в Ecto — только два, поэтому для достижения того же результата её необходимо объединять в цепочку вызовов.
from p in Payment, select: p.value |> coalesce(p.backup_value) |> coalesce(0)
constant(value)Источник
Позволяет вставить динамическую строку или число во фрагмент:
limit = 10
"posts" |> select([p], p.title) |> limit(fragment("?", constant(^limit)))
Приведённый выше пример вставит значение limit непосредственно в запрос, а не будет обрабатывать его как параметр запроса. Будет сформирован запрос вида SELECT p0.title FROM "posts" AS p0 LIMIT 1 вместо SELECT p0.title FROM "posts" AS p0 LIMIT $1.
Обратите внимание: каждое отдельное значение limit приведёт к созданию отдельного запроса, который будет независимо подготовлен и кэширован.
count()Источник
Подсчитывает количество записей в таблице.
from p in Post, select: count()
count(value)Источник
Подсчитывает заданную запись.
from p in Post, select: count(p.id)
count(value, atom)Источник
Подсчитывает уникальные значения в заданной записи.
from p in Post, select: count(p.id, :distinct)
date_add(date, count, interval)Источник
Добавляет заданный интервал к дате.
Дополнительные сведения см. в разделе datetime_add/3.
Поддерживаемые значения interval см. в разделе Интервалы.
datetime_add(datetime, count, interval)Источник
Добавляет заданный интервал к дате и времени.
Первый аргумент — это datetime, второй — количество интервалов, которое может быть как положительным, так и отрицательным, а также значение интервала:
# Get all items published since the last month
from p in Post, where: p.published_at >
datetime_add(^NaiveDateTime.utc_now(), -1, "month")
В примере выше мы использовали datetime_add/3, чтобы вычесть один месяц из текущей даты и времени и сравнить результат с p.published_at. Для выполнения операций с датами можно использовать date_add/3.
Поддерживаемые значения interval см. в разделе Интервалы.
exists(subquery)Источник
Возвращает true, если указанный подзапрос возвращает одну или более строк.
from p in Post,
as: :post,
where:
exists(
from(
c in Comment,
where: parent_as(:post).id == c.post_id and c.replies_count > 5,
select: 1
)
)
Эту функцию лучше всего использовать вместе с parent_as/1, чтобы связать подзапрос с родительским запросом и проверить условие для связанных строк в другой таблице. В приведённом выше примере запрос возвращает публикации, у которых есть хотя бы один комментарий более чем с 5 ответами.
field(source, field)Источник
Позволяет динамически обращаться к полю.
Имя источника может быть привязкой (p в from p in Post) или именованной привязкой с использованием as/1 или parent_as/1. Именованная привязка может быть литеральным атомом или интерполяцией.
Имя поля можно указать как атом или строку. В запросе без схемы оба типа имён ведут себя одинаково. Однако при обращении к полю схемы их поведение различается.
При обращении к полю схемы с помощью атома наследуются все свойства схемы. Например, перед формированием итогового запроса имя поля будет заменено на значение :source, а его поведение при работе с типами будет определяться типом, указанным в схеме.
Обращение к полю схемы с помощью строки эквивалентно обходу всего вышеперечисленного и прямому доступу к полю источника (то есть базовой таблицы). Это означает, что имя не будет заменено на значение :source, а поведение при работе с типами будет определяться драйвером (например, Postgrex или MyXQL).
Рассмотрим следующую схему и запрос:
defmodule Car do
use Ecto.Schema
schema "cars" do
field :doors, source: :num_doors
field :tires, source: :num_tires
end
end
def at_least_four(doors_or_tires) do
from c in Car,
where: field(c, ^doors_or_tires) >= 4
end
def at_least_four(query, doors_or_tires) do
from q in query,
where: field(as(:car), ^doors_or_tires) >= 4
end
def at_least_four(query, binding, doors_or_tires) do
from q in query,
where: field(as(^binding), ^doors_or_tires) >= 4
end
В приведённом выше примере at_least_four(:doors) и at_least_four("num_doors") — допустимые способы получить набор автомобилей как минимум с 4 дверями.
Строковые имена могут быть особенно полезны, когда приложение динамически создаёт множество запросов без схемы во время выполнения и нужно избежать создания большого количества атомов.
filter(value, filter)Источник
Применяет заданное выражение как предложение FILTER к агрегатной функции. В настоящее время поддерживается только PostgreSQL.
from p in Payment, select: filter(avg(p.value), p.value > 0 and p.value < 100) from p in Payment, select: avg(p.value) |> filter(p.value < 0)
fragment(fragments)Источник
Отправляет фрагменты напрямую в базу данных.
Синтаксис запросов Ecto не позволяет выразить все возможные запросы к базе данных. Если это необходимо, можно использовать фрагменты, чтобы отправить в базу данных любое выражение:
def unpublished_by_title(title) do
from p in Post,
where: is_nil(p.published_at) and
fragment("lower(?)", p.title) == ^title
end
Каждый символ ? будет интерпретироваться как место для параметра, который необходимо передать в качестве дополнительного аргумента функции fragment. Если в составе фрагмента требуется буквальный символ ?, его можно экранировать с помощью \\? (одно экранирование для строк, другое — для фрагмента).
В примере выше мы используем процедуру lower в базе данных, чтобы преобразовать столбец title к нижнему регистру.
Важно помнить, что при использовании фрагментов Ecto не может выполнять преобразование типов. Поэтому может потребоваться явно привести параметры с помощью type/2:
fragment("lower(?)", p.title) == type(^title, :string)
Идентификаторы и константы
Иногда необходимо интерполировать идентификатор или постоянное значение во фрагмент, а не параметр запроса. Это может понадобиться, если база данных не позволяет параметризовать определённые предложения. Например:
collation = "es_ES"
fragment("? COLLATE ?", ^name, ^collation)
limit = "10"
"posts" |> select([p], p.title) |> limit(fragment("?", ^limit))
Первый пример выше не сработает, так как collation необходимо заключить в кавычки как идентификатор. Второй пример не будет работать в базах данных, которые не позволяют передавать параметры запроса в составе limit.
Это можно исправить, указав Ecto, что эти значения следует обрабатывать не как параметры запроса:
fragment("? COLLATE ?", ^name, identifier(^collation))
"posts" |> select([p], p.title) |> limit(fragment("?", ^constant(limit))
Ecto включит эти значения непосредственно в запрос, выполняя необходимое заключение в кавычки и экранирование.
Кэширование запросов
Поскольку идентификаторы и константы включаются непосредственно в запрос, каждое отдельное значение создаёт отдельный запрос с собственным кэшем.
Разворачивание списков
Иногда требуется интерполировать во фрагмент переменное количество аргументов. Например, при переопределении поведения where по умолчанию в PostgreSQL:
from p in Post, where: fragment("? in (?, ?)", p.id, val1, val2)
Приведённый выше пример будет работать, только если количество аргументов известно заранее. Если оно может изменяться, этот пример не сработает.
Это можно исправить, указав Ecto развернуть аргумент-список во фрагменте:
from p in Post, where: fragment("? in (?)", p.id, splice(^val_list))
Так Ecto узнает, что значения списка нужно развернуть в отдельные аргументы фрагмента. Например:
from p in Post, where: fragment("? in (?)", p.id, splice(^[1, 2, 3]))
будет развёрнуто в
from p in Post, where: fragment("? in (?,?,?)", p.id, ^1, ^2, ^3)
Определение пользовательских функций с помощью макросов и фрагментов
Можно добавить пользовательскую функцию запроса Ecto с помощью макросов. Например, чтобы предоставить доступ к функции coalesce из SQL, можно определить следующий макрос:
defmodule CustomFunctions do
defmacro coalesce(left, right) do
quote do
fragment("coalesce(?, ?)", unquote(left), unquote(right))
end
end
end
Чтобы сделать coalesce/2 доступной, просто импортируйте модуль, в котором она определена.
import CustomFunctions
Единственный недостаток заключается в том, что при просмотре запроса Elixir она будет отображаться как фрагмент. В остальном она должна быть эквивалентна встроенной функции запроса Ecto.
Фрагменты в формате keyword
Для поддержки баз данных, в которых используются запросы не на основе строк, например MongoDB, фрагментам также можно передавать ключевые слова:
from p in Post,
where: fragment(title: ["$eq": ^some_value]) from_now(count, interval)Источник
Добавляет заданный интервал к текущему времени в UTC.
Текущее время в UTC получается из Elixir, а не из базы данных.
Поддерживаемые значения interval см. в разделе Интервалы.
Примеры
from a in Account, where: a.expires_at < from_now(3, "month")
identifier(binary)Источник
Позволяет вставить динамический идентификатор во фрагмент:
collation = "es_ES"
select("posts", [p], fragment("? COLLATE ?", p.title, identifier(^collation)))
Приведённый выше пример вставит значение collation непосредственно в запрос, а не будет обрабатывать его как параметр запроса. Будет сформирован запрос вида SELECT p0.title COLLATE "es_ES" FROM "posts" AS p0 вместо SELECT p0.title COLLATE $1 FROM "posts" AS p0.
Обратите внимание: каждое отдельное значение collation приведёт к созданию отдельного запроса, который будет независимо подготовлен и кэширован.
ilike(string, search)Источник
Выполняет поиск search в string без учёта регистра.
from p in Post, where: ilike(p.body, "Chapter%")
Преобразуется в запрос SQL ILIKE. Эта операция доступна только в PostgreSQL.
left in rightИсточник
Проверяет, содержится ли значение слева в значении справа.
from p in Post, where: p.id in [1, 2, 3]
В качестве правой части можно указать литеральный список, интерполированный список, любую структуру, реализующую протокол Enumerable, или даже столбец базы данных с типом array:
from p in Post, where: "elixir" in p.tags
Кроме того, в качестве правой части можно указать подзапрос, который должен возвращать один столбец:
from c in Comment, where: c.post_id in subquery( from(p in Post, where: p.created_at > ^since, select: p.id) )
is_nil(value)Источник
Проверяет, равно ли заданное значение nil.
from p in Post, where: is_nil(p.published_at)
Чтобы проверить, что заданное значение не равно nil, используйте:
from p in Post, where: not is_nil(p.published_at)
json_extract_path(json_field, path)Источник
Возвращает значение из json_field, на которое указывает path.
from(post in Post, select: json_extract_path(post.meta, ["author", "name"]))
Путь может быть динамическим:
path = ["author", "name"] from(post in Post, select: json_extract_path(post.meta, ^path))
Также поле может быть динамическим вместе с путём:
path = ["author", "name"] from(post in Post, select: json_extract_path(field(post, :meta), ^path))
Запрос также можно переписать следующим образом:
from(post in Post, select: post.meta["author"]["name"])
Элементами пути могут быть целые числа, указывающие на значения в массивах JSON:
from(post in Post, select: post.meta["tags"][0]["name"])
В некоторых адаптерах элементами пути могут быть ссылки на поля источников запроса.
from(post in Post, select: post.meta[p.title]) from(p in Post, join: u in User, on: p.user_id == u.id, select: p.meta[u.name])
Любой элемент пути может быть динамическим:
field = "name" from(post in Post, select: post.meta["author"][^field]) source_field = :source_column from(post in Post, select: post.meta["author"][field(p, ^source_field)])
Предупреждение: индексы в PostgreSQL
PostgreSQL поддерживает индексирование столбцов jsonb с помощью индексов GIN. При сравнении значения поля jsonb со строкой или целым числом Ecto использует оператор включения @>, оптимизированный для таких случаев. Можно также использовать более эффективный вариант индекса GIN jsonb_path_ops. Дополнительные сведения см. в документации PostgreSQL о индексировании JSON.
Предупреждение: возвращаемые типы
Данные из столбца JSON возвращаются без дополнительного декодирования. Это означает, что значения JSON "null" отличаются от SQL-значения "null". Например, приведённая ниже операция Repo.all возвращает пустой список, поскольку p.meta["author"] возвращает JSON null и поэтому is_nil не выполняется:
Repo.insert!(%Post{meta: %{author: nil}})
Repo.all(from(post in Post, where: is_nil(p.meta["author"])))
Аналогично, другие типы, например даты и время, возвращаются в виде строк. Поэтому условия вроде post.meta["published_at"] > from_now(-1, "day") могут возвращать неверные результаты или завершаться с ошибкой, поскольку база данных пытается сравнить несовместимые типы. Однако можно использовать type/2, чтобы принудительно задать типы на уровне базы данных.
like(string, search)Источник
Выполняет поиск search в string.
from p in Post, where: like(p.body, "Chapter%")
Преобразуется в запрос SQL LIKE, поэтому поведение зависит от базы данных. В частности, PostgreSQL выполняет поиск с учётом регистра, тогда как большинство других баз данных — без учёта регистра. Чтобы выполнить поиск like в PostgreSQL, см. ilike/2.
Будьте особенно осторожны, разрешая использовать данные, введённые пользователями, в запросах LIKE, поскольку это позволяет выполнять инъекции LIKE.
map(source, fields)Источник
Используется в select для указания полей, которые следует вернуть в виде карты.
Например, если не нужно возвращать все поля или структуру, можно использовать map/2 для достижения обеих целей:
from p in Post, select: map(p, [:title, :body])
map/2 также можно использовать для динамического выбора полей:
fields = [:title, :body] from p in Post, select: map(p, ^fields)
Если один и тот же источник выбран несколько раз с помощью map, поля объединяются, чтобы избежать получения нескольких копий из базы данных. Иными словами, приведённое ниже выражение:
from(city in City, preload: :country,
select: {map(city, [:country_id]), map(city, [:name])})
будет преобразовано в:
from(city in City, preload: :country,
select: {map(city, [:country_id, :name]), map(city, [:country_id, :name])})
Для предварительной загрузки выбираемые поля можно указать из родительского запроса:
from(city in City, preload: :country,
select: map(city, [:country_id, :name, country: [:id, :population]]))
Также можно выбрать структуру из одного источника, но только часть полей одной из связанных с ним ассоциаций:
from(city in City, preload: :country,
select: %{city | country: map(country: [:id, :population])})
ВАЖНО: При фильтрации полей ассоциаций ОБЯЗАТЕЛЬНО включайте внешние ключи, используемые в связи, иначе Ecto не сможет найти связанные записи.
max(value)Источник
Вычисляет максимальное значение для заданной записи.
from p in Payment, select: max(p.value)
merge(left_map, right_map)Источник
Объединяет правую карту с левой, перезаписывая совпадающие значения.
Если левая карта является структурой, Ecto проверит, существуют ли в ней все поля из правой карты, прежде чем выполнить объединение.
from(city in City, select: merge(city, %{virtual_field: "some_value"}))
Эта функция в основном используется функцией Ecto.Query.select_merge/3 для объединения разных предложений select.
min(value)Источник
Вычисляет минимальное значение для заданной записи.
from p in Payment, select: min(p.value)
not valueИсточник
Унарная операция not.
Используется для отрицания значений в :where. Также используется для проверки утверждения, противоположного in/2, is_nil/1 и exists/1. Например:
from p in Post, where: p.id not in [1, 2, 3]
from p in Post, where: not is_nil(p.title)
# Retrieve all the posts that doesn't have comments.
from p in Post,
as: :post,
where:
not exists(
from(
c in Comment,
where: parent_as(:post).id == c.post_id
)
) left or rightИсточник
Бинарная операция or.
parent_as(binding)Источник
Ссылка на именованную атомарную привязку в родительском запросе.
Доступно только внутри подзапросов.
Дополнительные сведения см. в разделе Именованные привязки.
selected_as(name)Источник
Ссылка на псевдоним выбранного значения.
Можно использовать для обращения к псевдонимам, созданным с помощью selected_as/2. Если псевдоним не был создан с помощью selected_as/2, будет вызвана ошибка.
В каждой базе данных есть собственные правила, определяющие, в каких предложениях можно ссылаться на эти псевдонимы. Если возникает ошибка с сообщением о неизвестном столбце, скорее всего, псевдоним используется в месте, где это не разрешено. Чтобы правильно обращаться к псевдониму, ознакомьтесь с документацией соответствующей базы данных.
selected_as(selected_value, name)Источник
Создаёт псевдоним для выбранного значения.
При работе с вычисляемыми значениями псевдоним помогает упростить запрос. В противном случае при обращении к значению вне предложений select пришлось бы копировать всё выражение.
Это удобно, например, если вычисляемое значение нужно использовать в Ecto.Query.group_by/3 или Ecto.Query.order_by/3:
from p in Post,
select: %{
posted: selected_as(p.posted, :date),
sum_visits: p.visits |> coalesce(0) |> sum() |> selected_as(:sum_visits)
},
group_by: selected_as(:date),
order_by: selected_as(:sum_visits)
Имя псевдонима должно быть атомом, и использовать его можно только в самом внешнем выражении select, иначе будет вызвана ошибка. Обратите внимание, что имя псевдонима не обязано совпадать с ключом, когда select возвращает карту, структуру или список ключевых слов.
Рекомендуется использовать это вместе с selected_as/1, чтобы ссылаться только на определённые псевдонимы.
Подзапросы и CTE
Подзапросы и CTE автоматически задают псевдонимы выбранным полям. Например, можно написать:
# Subquery
s = from p in Post, select: %{visits: coalesce(p.visits, 0)}
from(s in subquery(s), select: s.visits)
# CTE
cte_query = from p in Post, select: %{visits: coalesce(p.visits, 0)}
Post |> with_cte("cte", as: ^cte_query) |> join(:inner, [p], c in "cte") |> select([p, c], c.visits)
Однако можно также использовать selected_as, чтобы переопределить имена по умолчанию:
# Subquery
s = from p in Post, select: %{visits: coalesce(p.visits, 0) |> selected_as(:num_visits)}
from(s in subquery(s), select: s.num_visits)
# CTE
cte_query = from p in Post, select: %{visits: coalesce(p.visits, 0) |> selected_as(:num_visits)}
Post |> with_cte("cte", as: ^cte_query) |> join(:inner, [p], c in "cte") |> select([p, c], c.num_visits)
Имя, заданное функции selected_as/2, также можно использовать в selected_as/1, как и в обычных запросах.
splice(list)Источник
Позволяет развернуть аргумент-список во фрагменте.
Динамические списки можно развернуть в запросе с помощью интерполяции:
from p in Post, where: fragment("? in (?)", p.id, splice(^[1, 2, 3]))
Обратите внимание, что каждый элемент списка будет обработан как отдельный параметр запроса. Во время выполнения приведённый выше пример будет преобразован следующим образом:
from p in Post, where: fragment("? in (?,?,?)", p.id, ^1, ^2, ^3)
Можно также разворачивать списки времени компиляции. Это позволяет объединять параметры запроса с литералами и такими конструкциями, как привязки запроса.
sep = " "
from p in Post, select: fragment("concat(?)", splice([p.count, ^sep, "count"]))
Приведённый выше пример будет преобразован в:
sep = " "
from p in Post, select: fragment("concat(?,?,?)", p.count, ^sep, "count")
Это особенно полезно, если вы хотите создавать повторно используемые макросы для добавления вариативных функций базы данных в запросы. Например, можно создать макрос для функции PostgreSQL concat_ws следующим образом:
defmacro concat_ws(sep, args) do
quote do
fragment("concat_ws(?,?)", unquote(sep), splice(unquote(args)))
end
end
Затем его можно вызвать из приложения со списками аргументов любого размера:
from p in Post, select: concat_ws(":", [p.author, ^year, p.title])
from s in Sequences, select: concat_ws(".", ["public", s.relname])
Внутри разворачиваемых во время компиляции списков можно вкладывать другие операции разворачивания и модификаторы фрагментов, такие как identifier/1 и constant/1.
from p in Post, where: fragment("? in (?)", p.id, splice([constant(^1), splice(^[2, 3])]))
Результат будет преобразован в:
from p in Post, where: fragment(? in (?,?,?), p.id, constant(^1), ^2, ^3)
struct(source, fields)Источник
Используется в select, чтобы указать, какие поля структуры следует вернуть.
Например, если вам не нужно возвращать все поля в составе структуры, можно отфильтровать их, включив только определённые поля с помощью struct/2:
from p in Post, select: struct(p, [:title, :body])
struct/2 также можно использовать для динамического выбора полей:
fields = [:title, :body] from p in Post, select: struct(p, ^fields)
Для удобства select позволяет разработчикам извлекать поля без явного вызова struct/2:
from p in Post, select: [:title, :body]
Или даже динамически:
fields = [:title, :body] from p in Post, select: ^fields
Для предварительной загрузки выбираемые поля можно указать из родительского запроса:
from(city in City, preload: :country,
select: struct(city, [:country_id, :name, country: [:id, :population]]))
Если один и тот же источник выбирается несколько раз с помощью struct, поля объединяются, чтобы избежать получения нескольких копий из базы данных. Иными словами, выражение ниже:
from(city in City, preload: :country,
select: {struct(city, [:country_id]), struct(city, [:name])})
разворачивается в:
from(city in City, preload: :country,
select: {struct(city, [:country_id, :name]), struct(city, [:country_id, :name])})
ВАЖНО: При фильтрации полей ассоциаций НЕОБХОДИМО включить внешние ключи, используемые в связи, иначе Ecto не сможет найти связанные записи.
sum(value)Источник
Вычисляет сумму для заданной записи.
from p in Payment, select: sum(p.value)
type(interpolated_value, type)Источник
Приводит заданное значение к указанному типу на уровне базы данных.
В большинстве случаев Ecto может корректно приводить интерполированные значения благодаря механизму проверки типов. Однако в некоторых ситуациях может потребоваться указать Ecto, что параметр имеет определённый тип:
type(^title, :string)
Также можно указать, что тип должен совпадать с типом столбца:
type(^title, p.title)
Или использовать параметризованный тип, который предварительно должен быть инициализирован с помощью Ecto.ParameterizedType.init/2:
@my_enum Ecto.ParameterizedType.init(Ecto.Enum, values: [:foo, :bar, :baz]) type(^title, ^@my_enum)
Ecto гарантирует, что ^title будет приведено к заданному типу, и обеспечит соблюдение этого типа на уровне базы данных. Если значение возвращается в select, Ecto также обеспечит соблюдение соответствующего типа на всём протяжении обработки.
При выполнении арифметических операций type/2 можно использовать для приведения всех параметров операции к одному типу:
from p in Post, select: type(p.visits + ^a_float + ^a_integer, :decimal)
Внутри select type/2 также можно использовать для приведения типов фрагментов:
type(fragment("NOW"), :naive_datetime)
Или для указания типов полей в запросах без схемы:
from p in "posts", select: type(p.cost, :decimal)
Или для указания типов результатов агрегирования:
from p in Post, select: type(avg(p.cost), :integer) from p in Post, select: type(filter(avg(p.cost), p.cost > 0), :integer)
Или для указания типов результатов выражений сравнения:
from p in Post, select: type(coalesce(p.cost, 0), :integer)
Или для указания типов полей родительского запроса с помощью parent_as/1:
child = from c in Comment, where: type(parent_as(:posts).id, :string) == c.text from Post, as: :posts, inner_lateral_join: c in subquery(child), select: c.text
type и fragment
type/2 предназначен для работы с типами Ecto. Поэтому с его помощью можно выполнить type(expr, :string), но нельзя type(expr, :text), поскольку :text не является настоящим типом Ecto. Если нужно выполнять приведение исключительно на уровне базы данных, можно использовать fragment. Например, в PostgreSQL можно написать fragment("?::text", p.column).
values(values, types)Источник
Создаёт список значений/таблицу-константу.
Список значений можно использовать в качестве источника запроса как в Ecto.Query.from/2, так и в Ecto.Query.join/5.
Первый аргумент — это список карт, представляющих значения таблицы-константы. Если список пуст или поля во всех картах не совпадают в точности, возникает ошибка.
Второй аргумент — это либо карта типов, либо схема Ecto, содержащая все поля из первого аргумента.
Для каждого поля необходимо указать тип, иначе возникнет ошибка. Можно использовать любой тип, допустимый в схеме.
Запросы, использующие список значений, не кэшируются Ecto.
Пример выборки с типами карты
values = [%{id: 1, text: "abc"}, %{id: 2, text: "xyz"}]
types = %{id: :integer, text: :string}
query =
from v1 in values(values, types),
join: v2 in values(values, types),
on: v1.id == v2.id
Repo.all(query)
Пример выборки с типами схемы
values = [%{id: 1, text: "abc"}, %{id: 2, text: "xyz"}]
types = ValuesSchema
query =
from v1 in values(values, types),
join: v2 in values(values, types),
on: v1.id == v2.id
Repo.all(query)
Пример удаления
values = [%{id: 1, text: "abc"}, %{id: 2, text: "xyz"}]
types = %{id: :integer, text: :string}
query =
from p in Post,
join: v in values(values, types),
on: p.id == v.id,
where: p.counter == ^0
Repo.delete_all(query)
Пример обновления
values = [%{id: 1, text: "abc"}, %{id: 2, text: "xyz"}]
types = %{id: :integer, text: :string}
query =
from p in Post,
join: v in values(values, types),
on: p.id == v.id,
update: [set: [text: v.text]]
Repo.update_all(query, [])
© 2014 Chris McCord
Licensed under the MIT License.
https://ecto.hexdocs.pm/Ecto.Query.API.html