Агрегатные функции
Reindexer поддерживает агрегатные функции для более гибкого поиска:
| SQL | Go | Python | Java | Описание |
|---|---|---|---|---|
MAX() |
AggregateMax |
aggregate_max |
aggregateMax |
Получить максимальное значение в столбце |
MIN() |
AggregateMin |
aggregate_min |
aggregateMin |
Получить минимальное значение в столбце |
AVG() |
AggregateAvg |
aggregate_avg |
aggregateAvg |
Получить среднее значение в столбце |
SUM() |
AggregateSum |
aggregate_sum |
aggregateSum |
Получить сумму значений элементов в столбце |
COUNT() |
ReqTotal |
request_total |
reqTotal |
Получить общее количество элементов, удовлетворяющих условиям выборки |
COUNT_CACHED() |
CachedTotal |
cached_total |
— | То же, что COUNT, но с использованием кешированного значения. Может быть более эффективно, чем COUNT для последовательных запросов, отличающихся только значениями LIMIT/OFFSET |
DISTINCT() |
Distinct |
distinct |
aggregateDistinct |
Получить записи с уникальными значениями |
FACET() |
AggregateFacet |
aggregate_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("Нет данных для агрегации")
}
query = (
db
.new_query("test_table")
.aggregate_max("field")
.execute()
)
result = query.get_agg_results()[0]
if result["value"] is not None:
print("Максимальное значение поля 'field' = ", result["value"])
else:
print("Нет данных для агрегации")
Query<Item> query = db.query("test_table", Item.class)
.aggregateMax("field");
ResultIterator<Item> iterator = query.execute();
AggregationResult result = iterator.aggResults().get(0);
System.out.println("Максимальное значение поля 'field' = " + result.getValue());
Функция 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("Нет данных для агрегации")
}
from pyreindexer.query import CondType
query = (
db
.new_query("test_table")
.where("price", CondType.CondGt, 200)
.limit(10)
.request_total()
.execute()
)
result = query.get_agg_results()[0]
if result["value"] is not None:
print("Количество документов в неймспейсе с price > 200 = ", result["value"])
else:
print("Нет данных для агрегации")
Query<Item> query = db.query("test_table", Item.class)
.where("price", Conditions.GT, 200)
.limit(10).
.reqTotal();
ResultIterator<Item> iterator = query.execute();
AggregationResult result = iterator.aggResults().get(0);
System.out.println("Количество документов в неймспейсе с price > 200 = " + result.getValue());
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 элементу
}
query = (
db
.new_query("test_table")
.distinct("field")
.execute()
)
result = query.get_agg_results()[0]
print("Уникальные значения столбца 'field':")
for distinct in result["distincts"]:
print(distinct)
Query<Item> query = db.query("test_table", Item.class)
.aggregateDistinct("field")
ResultIterator<Item> iterator = query.execute();
AggregationResult result = iterator.aggResults().get(0);
List<String> dictinctResults = result.getDistincts();
System.out.println("Уникальные значения столбца 'field':");
for (int i = 0; i < dictinctResults.size(); i++) {
System.out.println(dictinctResults.get(i));
}
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-значениями до одинаковой длины. Скаляры преобразуются в массивы с максимальной длиной массива из списка
[c, c, c, ...]. Таким образом в рамках каждой строки получается прямоугольная таблица, из которой в качестве очередного кортежа выбирается каждая отдельная строка{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 элемента
}
query = (
db
.new_query("test_table")
.distinct("field")
.execute()
)
result = query.get_agg_results()[0]
print("Уникальные значения столбца 'field':")
for distinct in result["distincts"]:
print(distinct)
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])
}
query = (
db
.new_query("test_table")
.distinct("field1")
.distinct("field2")
.execute()
)
result1 = query.get_agg_results()[0]
print("Уникальные значения столбца 'field 1':")
for distinct in result1["distincts"]:
print(distinct)
result2 = query.get_agg_results()[1]
print("Уникальные значения столбца 'field 2':")
for distinct in result2["distincts"]:
print(distinct)
Query<Item> query = db.query("items", Item.class)
.aggregateDistinct("field1")
.aggregateDistinct("field2");
ResultIterator<Item> iterator = query.execute();
AggregationResult result = iterator.aggResults().get(0);
List<String> dictinctResults = result.getDistincts();
System.out.println("Уникальные значения столбца 'field1':");
for (int i = 0; i < dictinctResults.size(); i++) {
System.out.println(dictinctResults.get(i));
}
AggregationResult result2 = iterator.aggResults().get(1);
List<String> dictinctResults2 = result2.getDistincts();
System.out.println("Уникальные значения столбца 'field2':");
for (int i = 0; i < dictinctResults2.size(); i++) {
System.out.println(dictinctResults2.get(i));
}
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)
}
В Python для получения уникальных значений полей и подсчёта количества соответствующих им документов реализован метод aggregate_facet, который возвращает _AggregationFacet с методами sort, limit и offset.
Агрегатная функция должна быть вызвана перед выполнением запроса.
query = db.new_query("test_table")
query.aggregate_facet("name")
result = (
query
.execute()
.get_agg_results()[0]
)
print("'Значение name' -> Кол. уникальных")
for facet in result["facets"]:
print(facet["values"][0], " -> ", facet["count"])
aggregate_facet может использоваться для нескольких полей одновременно. Например, для подсчёта количества уникальных пар:
query = db.new_query("test_table")
query.aggregate_facet("name", "country")
result = (
query
.execute()
.get_agg_results()[0]
)
print("'Значение name' / 'значение country' -> Кол. уникальных пар")
for facet in result["facets"]:
print(facet["values"][0], "/", facet["values"][1], " -> ", facet["count"])
Агрегатная функция должна быть вызвана перед выполнением запроса.
В Java для поддержки фасетов агрегации реализован метод aggregateFacet, возвращающий AggregationFacetRequest, который имеет методы sort, limit и offset.
Query<Item> query = db.query("test_table", Item.class);
query.aggregateFacet("name");
ResultIterator<Item> iterator = query.execute();
AggregationResult result = iterator.aggResults().get(0);
List<AggregationResult.Facet> facets = result.getFacets();
System.out.println("'Значение name' -> Кол. уникальных");
for (int i = 0; i < facets.size(); i++) {
System.out.println(facets.get(i).getValues() + " -> " + facets.get(i).getCount());
}
aggregateFacet может использоваться для нескольких столбцов одновременно для подсчёта количества уникальных пар:
Query<Item> query = db.query("test_table", Item.class);
query.aggregateFacet("name", "country");
ResultIterator<Item> iterator = query.execute();
AggregationResult result = iterator.aggResults().get(0);
List<AggregationResult.Facet> facets = result.getFacets();
System.out.println("'Значение name' / 'значение country' -> Кол. уникальных пар");
for (int i = 0; i < facets.size(); i++) {
System.out.println(facets.get(i).getValues().get(0) + "/" + facets.get(i).getValues().get(1) + " -> "
+ facets.get(i).getCount());
}
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)
}
query = db.new_query("test_table")
(
query
.aggregate_facet("name", "age")
.sort("name", False)
.sort("age", True)
)
result = (
query
.execute()
.get_agg_results()[0]
)
print("'Значение name' / 'Значение age' -> Кол. уникальных пар")
for facet in result["facets"]:
print(facet["values"][0], "/", facet["values"][1], " -> ", facet["count"])
Query<Item> query = db.query("test_table", Item.class);
query
.aggregateFacet("name", "age")
.sort("name", false)
.sort("age", true);
ResultIterator<Item> iterator = query.execute();
AggregationResult result = iterator.aggResults().get(0);
List<AggregationResult.Facet> facets = result.getFacets();
System.out.println("'Значение name' / 'Значение age' -> Кол. уникальных пар");
for (int i = 0; i < facets.size(); i++) {
System.out.println(facets.get(i).getValues().get(0) + "/" + facets.get(i).getValues().get(1) + " -> "
+ facets.get(i).getCount());
}
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)
}
query = db.new_query("test_table")
(
query
.aggregate_facet("name", "age")
.sort("count", False)
)
result = (
query
.execute()
.get_agg_results()[0]
)
print("'Значение name' / 'Значение age' -> Кол. уникальных пар")
for facet in result["facets"]:
print(facet["values"][0], "/", facet["values"][1], " -> ", facet["count"])
Query<Item> query = db.query("test_table", Item.class);
query
.aggregateFacet("name", "age")
.sort("count", false);
ResultIterator<Item> iterator = query.execute();
AggregationResult result = iterator.aggResults().get(0);
List<AggregationResult.Facet> facets = result.getFacets();
System.out.println("'Значение name' / 'Значение age' -> Кол. уникальных пар");
for (int i = 0; i < facets.size(); i++) {
System.out.println(facets.get(i).getValues().get(0) + "/" + facets.get(i).getValues().get(1) + " -> "
+ facets.get(i).getCount());
}
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)
}
query = db.new_query("test_table")
(
query
.aggregate_facet("name", "age")
.sort("count", False)
.offset(10)
.limit(100)
)
result = (
query
.execute()
.get_agg_results()[0]
)
print("'Значение name' / 'Значение age' -> Кол. уникальных пар")
for facet in result["facets"]:
print(facet["values"][0], "/", facet["values"][1], " -> ", facet["count"])
Query<Item> query = db.query("test_table", Item.class);
query
.aggregateFacet("name", "age")
.sort("count", false)
.offset(10)
.sort(100);
ResultIterator<Item> iterator = query.execute();
AggregationResult result = iterator.aggResults().get(0);
List<AggregationResult.Facet> facets = result.getFacets();
System.out.println("'Значение name' / 'Значение age' -> Кол. уникальных пар");
for (int i = 0; i < facets.size(); i++) {
System.out.println(facets.get(i).getValues().get(0) + "/" + facets.get(i).getValues().get(1) + " -> "
+ facets.get(i).getCount());
}
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)
}
query = db.new_query("test_table")
(
query
.aggregate_max("price")
.aggregate_facet("name")
.limit(100)
)
max_result = (
query
.execute()
.get_agg_results()[0]
)
if max_result["value"] is not None:
print("max price = ", max_result["value"])
else:
print("Нет данных для агрегации")
aggr_result = (
query
.execute()
.get_agg_results()[1]
)
print("'name' -> count")
for facet in aggr_result["facets"]:
print(facet["values"][0], " -> ", facet["count"])
Query<Item> query = db.query("test_table", Item.class);
query
.aggregateMax("price")
.aggregateFacet("name")
.limit(100);
ResultIterator<Item> iterator = query.execute();
AggregationResult result = iterator.aggResults().get(0);
System.out.println("max price = " + result.getValue());
AggregationResult result1 = iterator.aggResults().get(1);
List<AggregationResult.Facet> facets1 = result1.getFacets();
System.out.println("'name' -> count");
for (int i = 0; i < facets1.size(); i++) {
System.out.println(facets1.get(i).getValues().get(0) + " -> "
+ facets1.get(i).getCount());
}
Агрегатные функции можно указывать через запятую с разными аргументами.
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
}'