Агрегатные функции
Reindexer поддерживает агрегатные функции для более гибкого поиска:
Агрегатная функция для SQL | Агрегатная функция для GO | Описание |
---|---|---|
MAX() |
AggregateMax |
Получить максимальное значение в столбце |
MIN() |
AggregateMin |
Получить минимальное значение в столбце |
AVG() |
AggregateAvg |
Получить среднее значение в столбце |
SUM() |
AggregateSum |
Получить сумму значений элементов в столбце |
COUNT() |
ReqTotal |
Получить общее количество элементов, удовлетворяющих условиям выборки |
COUNT_CACHED() |
CachedTotal |
То же, что COUNT , но с использованием кэшированного значения. Может быть более эффективно, чем COUNT для последовательных запросов, отличающихся только значениями LIMIT /OFFSET |
DISTINCT() |
Distinct |
Получить записи с уникальными значениями |
FACET() |
AggregateFacet |
Получить уникальные записи в неймспейсе с указанием количества |
Фасеты агрегации (фильтры) могут применяться к нескольким столбцам.
Результат может быть отсортирован по любому столбцу и ограничен с помощью Limit
и Offset
.
Если было задано более одного оператора DISTINCT
, в выборку попадет пересечение множеств уникальных полей.
То есть будут выбраны только те записи, для которых уникальные значения всех указанных полей были найдены в первую очередь.
Например, если сперва была найдена запись со значениями полей { year: "2019", name: "Motherless Brooklyn" }
, а затем { year: "2019", name: "Joker" }
, в выборку попадет только первая запись.
В Go-коннекторе подсчёта общего количества записей, удовлетворяющих условиям, в запросе используются методы ReqTotal()
и CachedTotal()
.
Результат вычисления в обоих случаях может быть получен при помощи вызова метода TotalCount()
у возвращённого из запроса итератора.
Примеры
Ниже приведены примеры использования агрегатных функций в Reindexer.
Получение максимального значения поля
Агрегатная функция должна быть вызвана перед выполнением запроса.
query := db.Query("test_table")
query.AggregateMax("field")
it := query.Exec()
if it.Error() != nil {
panic(it.Error())
}
defer it.Close()
aggMaxRes := it.AggResults()[0]
if aggMaxRes.Value != nil {
fmt.Printf("Максимальное значение поля 'field' = %v\n", *aggMaxRes.Value)
} else {
fmt.Println("Нет данных для агрегации")
}
Функция MAX()
может принимать в качестве аргумента число или строку, которую можно преобразовать в число.
Агрегатные функции можно указывать через запятую с разными аргументами.
SELECT MAX('field') FROM test_table
curl --location --request POST 'http://127.0.0.1:9088/api/v1/db/testdb/query' \
--header 'Content-Type: application/json' \
--data-raw '{
"namespace": "test_table",
"type": "select",
"aggregations": [
{
"fields": [
"field"
],
"type": "MAX"
}
]
}'
Получение общего количества документов, удовлетворяющих условиям запроса
Агрегатная функция должна быть вызвана перед выполнением запроса.
query := db.Query("test_table").
Where("price", reindexer.GT, 200).
Limit(10).
ReqTotal()
it := query.Exec()
if it.Error() != nil {
panic(it.Error())
}
defer it.Close()
aggReqTotal := it.AggResults()[0]
if aggReqTotal.Value != nil {
fmt.Printf("Количество документов в неймспейсе с price > 200 = %v\n", *aggReqTotal.Value)
} else {
fmt.Println("Нет данных для агрегации")
}
SELECT COUNT(*) FROM test_table WHERE price > 200 LIMIT 10
curl --location --request POST 'http://127.0.0.1:9088/api/v1/db/testdb/query' \
--header 'Content-Type: application/json' \
--data-raw '{
"namespace": "test_table",
"type": "select",
"aggregations": [
{
"fields": [
],
"type": "COUNT"
}
],
"filters": [
{
"field": "price",
"cond": "GT",
"value": [
200
],
}
],
"limit": 10
}'
Получение уникальных значений столбцов
В reindexer есть возможность получения уникальных значений для одного столбца (метод Distinct(field)
) или для кортежей из нескольких столбцов (метод Distinct(field1, field2, ...)
), в том числе для массивов.
Если в Distinct
указано одно поле (скаляра, массива или композитного индекса), строка пространства имен считается уникальной в следующих случаях:
- Скаляр уникален;
- Одно из значений массива уникально;
- Составное значение композитного индекса
v1+v2+...
уникально.
Все уникальные значения (включая все значения массива) добавляются к результату агрегации.
Агрегатная функция должна быть вызвана перед выполнением запроса.
query := db.Query("test_table")
query.Distinct("field")
it := query.Exec()
if it.Error() != nil {
panic(it.Error())
}
defer it.Close()
aggResults := it.AggResults()
distinctsRes := aggResults[0]
fmt.Println("Уникальные значения столбца 'field':")
for _, res := range distinctsRes.Distincts {
fmt.Println(res[0]) // В этом случае каждой строке гарантированно будет по 1 элементу
}
SELECT DISTINCT('field') FROM test_table
curl --location --request POST 'http://127.0.0.1:9088/api/v1/db/testdb/query' \
--header 'Content-Type: application/json' \
--data-raw '{
"namespace": "test_table",
"type": "select",
"aggregations": [
{
"fields": [
"field"
],
"type": "DISTINCT"
}
]
}'
Если в Distinct
указано несколько полей (все поля должны быть скалярами или массивами), строка пространства имен считается уникальной в следующих случаях:
- Кортеж значений
{v1, v2, ...}
уникален.
При формировании кортежей массивы дополняются пустыми null-значениями до одинаковой длины. Таким образом в рамках каждой строки получается прямоугольная таблица, из которой в качестве очередного кортежа выбирается каждая отдельная строка {v1[i], v2[i], ...}
и проверяется, есть ли такое значение в списке уникальных значений.
Если хотя бы одно из этих значений уникально, вся строка пространства имен считается уникальной, а результат агрегирования содержит все уникальные значения {v1[i], v2[i], ...}
. При этом кортежи, состоящие целиком из null, будут проигнорированы.
Для неиндексных значений совместимость типа (массив или скаляр) проверяется в пределах одной строки пространства имён.
Агрегатная функция должна быть вызвана перед выполнением запроса.
query := db.Query("test_table")
query.Distinct("field1", "field2")
it := query.Exec()
if it.Error() != nil {
panic(it.Error())
}
defer it.Close()
aggResults := it.AggResults()
distinctsRes := aggResults[0]
fmt.Println("Уникальные значения столбцов 'field1', 'field2':")
for _, res := range distinctsRes.Distincts {
fmt.Println(res[0], res[1]) // В этом случае каждой строке гарантированно будет по 2 элемента
}
SELECT DISTINCT('field1', 'field2') FROM test_table
curl --location --request POST 'http://127.0.0.1:9088/api/v1/db/testdb/query' \
--header 'Content-Type: application/json' \
--data-raw '{
"namespace": "test_table",
"type": "select",
"aggregations": [
{
"fields": [
"field1",
"field2"
],
"type": "DISTINCT"
}
]
}'
Получение уникальных значений для нескольких столбцов по отдельности
Distinct
можно применять несколько раз: к разным колонкам в рамках одного запроса. В этом случае запрос вернёт только результаты агреграции.
Агрегатные функции должны быть вызваны перед выполнением запроса.
query := db.Query("test_table")
query.Distinct("field1").Distinct("field2")
it := query.Exec()
if it.Error() != nil {
panic(it.Error())
}
defer it.Close()
aggResults := it.AggResults()
distFields1 := aggResults[0]
fmt.Println("Уникальные значения столбца 'field1':")
for _, field1 := range distFields1.Distincts {
fmt.Println(field1[0])
}
distFields2 := aggResults[1]
fmt.Println("Уникальные значения столбца 'field2':")
for _, field2 := range distFields2.Distincts {
fmt.Println(field2[0])
}
SELECT DISTINCT('field1'), DISTINCT('field2') FROM test_table
curl --location --request POST 'http://127.0.0.1:9088/api/v1/db/testdb/query' \
--header 'Content-Type: application/json' \
--data-raw '{
"namespace": "test_table",
"type": "select",
"aggregations": [
{
"fields": [
"field1"
],
"type": "DISTINCT"
},
{
"fields": [
"field2"
],
"type": "DISTINCT"
}
]
}'
Получение уникальных значений в столбце с указанием их количества
Агрегатная функция должна быть вызвана перед выполнением запроса.
В Go для поддержки фасетов агрегации реализован метод AggregateFacet
, возвращающий AggregationFacetRequest
, который имеет методы Sort
, Limit
и Offset
.
query := db.Query("test_table")
query.AggregateFacet("name")
it := query.Exec()
if it.Error() != nil {
panic(it.Error())
}
defer it.Close()
aggFacetRes := it.AggResults()[0]
fmt.Printf("'Значение name' -> Кол. уникальных\n")
for _, facet := range aggFacetRes.Facets {
fmt.Printf("'%v' -> %v\n", facet.Values[0], facet.Count)
}
AggregateFacet
может использоваться для нескольких столбцов одновременно для подсчёта количества уникальных пар:
query := db.Query("test_table")
query.AggregateFacet("name", "country")
it := query.Exec()
if it.Error() != nil {
panic(it.Error())
}
defer it.Close()
aggFacetRes := it.AggResults()[0]
fmt.Printf("'Значение name' / 'значение country' -> Кол. уникальных пар\n")
for _, facet := range aggFacetRes.Facets {
fmt.Printf("'%v' / '%v' -> %v\n", facet.Values[0], facet.Values[1], facet.Count)
}
SELECT facet('name') FROM test_table
facet
может использоваться для нескольких столбцов одновременно:
SELECT facet('name', 'country') FROM test_table
curl --location --request POST 'http://127.0.0.1:9088/api/v1/db/testdb/query' \
--header 'Content-Type: application/json' \
--data-raw '{
"namespace": "test_table",
"type": "select",
"aggregations": [
{
"fields": [
"name"
],
"type": "FACET"
}
]
}'
FACET
может использоваться для нескольких столбцов одновременно:
curl --location --request POST 'http://127.0.0.1:9088/api/v1/db/testdb/query' \
--header 'Content-Type: application/json' \
--data-raw '{
"namespace": "test_table",
"type": "select",
"aggregations": [
{
"fields": [
"name",
"country"
],
"type": "FACET"
}
]
}'
Также Reindexer поддерживает сортировку по агрегированным полям facet
.
Примеры:
query := db.Query("test_table")
query.AggregateFacet("name", "age").Sort("name", false).Sort("age", true)
it := query.Exec()
if it.Error() != nil {
panic(it.Error())
}
defer it.Close()
aggFacetRes := it.AggResults()[0]
fmt.Printf("'Значение name' / 'Значение age' -> Кол. уникальных пар\n")
for _, facet := range aggFacetRes.Facets {
fmt.Printf("'%v' / '%v' -> %v\n", facet.Values[0], facet.Values[1], facet.Count)
}
SELECT FACET ('name', 'age' ORDER BY 'name' ASC, 'age' DESC) FROM test_table
curl --location --request POST 'http://127.0.0.1:9088/api/v1/db/testdb/query' \
--header 'Content-Type: application/json' \
--data-raw '{
"namespace": "test_table",
"type": "select",
"aggregations": [
{
"fields": [
"name",
"age"
],
"type": "FACET"
}
],
"sort": [
{
"field": "name",
"desc": false
},
{
"field": "age",
"desc": true
}
]
}'
Сортировать можно и по значениям count
, которые формирует facet
.
Примеры:
query := db.Query("test_table")
query.AggregateFacet("name", "age").Sort("count", false)
it := query.Exec()
if it.Error() != nil {
panic(it.Error())
}
defer it.Close()
aggFacetRes := it.AggResults()[0]
fmt.Printf("'Значение name' / 'Значение age' -> Кол. уникальных пар\n")
for _, facet := range aggFacetRes.Facets {
fmt.Printf("'%v' / '%v' -> %v\n", facet.Values[0], facet.Values[1], facet.Count)
}
SELECT FACET ('name', 'age' ORDER BY 'count' ASC) FROM test_table
curl --location --request POST 'http://127.0.0.1:9088/api/v1/db/testdb/query' \
--header 'Content-Type: application/json' \
--data-raw '{
"namespace": "test_table",
"type": "select",
"aggregations": [
{
"fields": [
"name",
"age"
],
"type": "FACET",
"sort": [
{
"field": "count",
"desc": false
}
]
}
]
}'
Совместно с сортировкой по агрегируемым facet
полям можно использовать параметры LIMIT
и OFFSET
для ограничения выдачи.
Примеры:
query := db.Query("test_table")
query.AggregateFacet("name", "age").Sort("count", false).Offset(10).Limit(100)
it := query.Exec()
if it.Error() != nil {
panic(it.Error())
}
defer it.Close()
aggFacetRes := it.AggResults()[0]
fmt.Printf("'Значение name' / 'Значение age' -> Кол. уникальных пар\n")
for _, facet := range aggFacetRes.Facets {
fmt.Printf("'%v' / '%v' -> %v\n", facet.Values[0], facet.Values[1], facet.Count)
}
SELECT FACET ('name', 'age' ORDER BY 'count' ASC OFFSET 10 LIMIT 100) FROM test_table
curl --location --request POST 'http://127.0.0.1:9088/api/v1/db/testdb/query' \
--header 'Content-Type: application/json' \
--data-raw '{
"namespace": "test_table",
"type": "select",
"aggregations": [
{
"fields": [
"name",
"age"
],
"type": "FACET",
"sort": [
{
"field": "count",
"desc": false
}
],
"limit": 100,
"offset": 10
}
]
}'
Использование нескольких агрегатных функций в одном запросе
В рамках одного запроса может быть использовано несколько агрегатных функций.
query := db.Query("test_table")
query.AggregateMax("price")
query.AggregateFacet("name").Limit(100)
it := query.Exec()
if it.Error() != nil {
panic(it.Error())
}
defer it.Close()
aggMaxRes := it.AggResults()[0]
if aggMaxRes.Value != nil {
fmt.Printf("max price = %v\n", *aggMaxRes.Value)
} else {
fmt.Println("Нет данных для агрегации")
}
aggFacetRes := it.AggResults()[1]
fmt.Printf("'name' -> count\n")
for _, facet := range aggFacetRes.Facets {
fmt.Printf("'%v' -> %d\n", facet.Values[0], facet.Count)
}
Агрегатные функции можно указывать через запятую с разными аргументами.
SELECT MAX('price'), facet('name') FROM test_table LIMIT 100
curl --location --request POST 'http://127.0.0.1:9088/api/v1/db/testdb/query' \
--header 'Content-Type: application/json' \
--data-raw '{
"namespace": "test_table",
"type": "select",
"aggregations": [
{
"fields": [
"price"
],
"type": "MAX"
},
{
"fields": [
"name"
],
"type": "FACET"
}
],
"limit": 100
}'