PostgreSQL: функции¶
Функции, которые выполняет только PostgreSQL, из hare.dialects.postgresql.functions; они передаются в
annotate(), filter() и order_by(), как функции базы данных для
всех баз. Полнотекстовый поиск — это hare.search.
from hare.dialects.postgresql.functions import StringAgg, Subpath, TransactionNow
from hare.query.functions import ArrayAgg, ArrayItem
Функции¶
| Функция | SQL |
|---|---|
STDistance(field, point) |
ST_DISTANCE(...) |
STDWithin(field, point, radius_m) |
ST_DWITHIN(...) — radius_m — конечное число не меньше нуля или выражение, иначе ValidationError |
ArrayAgg(field, distinct=False, _filter=None, order_by=()) |
ARRAY_AGG(...) — каждый элемент раскодируется через поле; общий агрегат hare из hare.query.functions, в ClickHouse — groupArray |
StringAgg(field, delimiter, distinct=False, _filter=None, order_by=()) |
STRING_AGG(CAST(field AS TEXT), ...) — delimiter — текст или выражение; другое значение вызывает QueryError |
JSONBAgg(field, distinct=False, _filter=None, order_by=()) |
JSONB_AGG(...) — каждый элемент раскодируется через поле |
BoolAnd(field) / BoolOr(field) |
BOOL_AND/BOOL_OR |
BitAnd(field) / BitOr(field) / BitXor(field) |
BIT_AND/BIT_OR/BIT_XOR целочисленной колонки |
RangeAgg(field) |
RANGE_AGG(...) — диапазоны каждой группы, слитые в мультидиапазон; читается через поле мультидиапазона этого поля диапазона (Поля мультидиапазонов) |
Corr(y, x) |
CORR(y, x) — коэффициент корреляции |
CovarPop(y, x, sample=False) |
COVAR_POP, или COVAR_SAMP при sample=True |
RegrAvgX(y, x) / RegrAvgY(y, x) |
REGR_AVGX/REGR_AVGY — среднее x/y |
RegrCount(y, x) |
REGR_COUNT — строки, где оба значения не NULL (целое число) |
RegrIntercept(y, x) / RegrSlope(y, x) / RegrR2(y, x) |
свободный член, наклон и R² прямой по методу наименьших квадратов |
RegrSXX(y, x) / RegrSXY(y, x) / RegrSYY(y, x) |
REGR_SXX/REGR_SXY/REGR_SYY |
TransactionNow() |
CURRENT_TIMESTAMP — начало транзакции, одинаковое для всех её команд |
RandomUUID() |
gen_random_uuid() — случайный UUID версии 4 для каждой строки |
ArraySubquery(queryset, *, shares_outer_scope=False) |
ARRAY(SELECT ...) — единственная колонка, которую выбирает запрос values()/values_list(), по всем строкам, списком в порядке order_by() запроса ([], если строк нет); каждый элемент раскодируется через поле колонки; shares_outer_scope — как у Subquery |
Subpath(field, offset, length=None) |
subpath(...) — часть пути LtreeField, читается как путь (LtreeField) |
ArrayItem(field, index) |
field[index + 1] (позиция с 0) — раскодируется через base_field массива; у вложенного массива — целая строка ([[1, 2], [3, 4]] → [1, 2]) |
await Store.objects.annotate(distance_m=STDistance("location", (55.75, 37.62))).order_by("distance_m")
await Book.objects.all().annotate(tag_names=ArrayAgg("tags__name")).group_by("id")
await Book.objects.annotate(first_keyword=ArrayItem("keywords", 0)).values("first_keyword")
await Book.objects.annotate(
tag_names=ArraySubquery(Tag.objects.filter(books=OuterReference("pk")).order_by("name").values("name"))
)
ArraySubquery¶
ArraySubquery принимает запрос, выбирающий ровно одну колонку, — запрос моделей или запрос с
несколькими колонками даёт QueryError. Его значение — ArrayField поля колонки, поэтому по нему
фильтруют операторами ArrayField. На другом диалекте — UnSupportedError до отправки запроса.
Функции даты¶
TruncYear/TruncMonth/TruncDay и ExtractYear/ExtractMonth/ExtractDay — это функции даты из
hare.query.functions; они работают на любой базе — см.
Функции даты.
Агрегаты в массив и JSON¶
ArrayAgg/JSONBAgg раскодируют каждый элемент через агрегируемое поле (элемент JSONField
разбирается и получает объявленный field_type, если он задан; элемент DatetimeField — в часовом
поясе hare, элемент TimeDeltaField — timedelta). Нечисловой float, который jsonb хранит текстом
("NaN", "Infinity"), в JSONBAgg от FloatField и от массива чисел снова становится float.
JSONBAgg от DecimalField даёт Decimal, округлённый до decimal_places поля: сам JSON хранит число
с точностью числа с плавающей точкой, поэтому, если важны точные цифры, используйте ArrayAgg.
JSONBAgg от поля диапазона даёт значения Range (включая пустой диапазон, сторону без границы и
включённые/невключённые границы). JSONBAgg от IntervalField или BinaryField даёт FieldError — в
JSON нет такого значения, только текст по IntervalStyle/bytea_output сервера; ArrayAgg отдаёт их
точно. Отказ до SQL действует и для массивов этих полей любой вложенности, а также для вычисляемых
полей с такими выходными типами. ArrayAgg от ArrayField строит один многомерный массив, строки
которого PostgreSQL требует одной длины: массивы разной длины или пустой среди непустых дают
OperationalError (cannot accumulate arrays of different dimensionality). JSONBAgg собирает их
списком списков — [['a', 'b'], [], ['c']], — как ArrayAgg в ClickHouse.
_filter=Q(...) не пускает в агрегат строку, которая ему не соответствует (FILTER (WHERE ...)), —
ArrayAgg/JSONBAgg собирают только подходящие строки, а группа без них даёт None.
Вычисляемое значение ArrayAgg фильтруется операторами ArrayField — contains, contained_by,
overlap, len, равенство, — а значение JSONBAgg — операторами JSONField:
await Author.objects.annotate(titles=ArrayAgg("books__title")).filter(titles__overlap=["War and Peace"])
await Author.objects.annotate(titles=ArrayAgg("books__title")).filter(titles__len=2)
Такой оператор у вычисляемого значения, которое не является массивом, диапазоном или значением JSON, даёт
FieldError.
order_by= задаёт порядок строк, которые читают ArrayAgg/StringAgg/JSONBAgg: имя поля ("-name" —
по убыванию), F("name"), выражение (Lower("name"), Coalesce("rank", Value(0)).desc()) или
F("name").desc(nulls_last=True), либо список таких значений, в том числе через связь. Любой другой
агрегат даёт для него QueryError — его результат от порядка не зависит. StringAgg(..., distinct=True),
упорядоченный по полю, которое он соединяет, упорядочивает значения текстового поля по тексту, которым они
соединяются; нетекстовое поле здесь даёт QueryError — PostgreSQL упорядочивает агрегат с DISTINCT только
по его аргументу, тексту поля, а тогда 10 шло бы раньше 9.
await Author.objects.annotate(titles=ArrayAgg("books__title", order_by="-books__published")).group_by("id")
await Tag.objects.all().aggregate(names=StringAgg("name", ", ", order_by=["name", "id"]))
Двухколоночные статистики¶
Двухколоночные статистики принимают зависимое значение первым (y), как PostgreSQL, и не учитывают
строку, где любое из значений NULL; они возвращают float, а RegrCount — целое число.