Spec-Zone.ru › Phoenix

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, [])

Пакет Hex Предпросмотр Hex Просмотреть llms.txt Скачать версию ePub

Создано с помощью ExDoc (v0.40.2) для языка программирования Elixir

© 2014 Chris McCord
Licensed under the MIT License.
https://ecto.hexdocs.pm/Ecto.Query.API.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API