Выборка данных из массивов

Reindexer поддерживает следующие виды выборки данных из массивов:

  • выборка с условиями,
  • выборка данных с использованием агрегатных функций,
  • выборка с условиями для элементов с одинаковыми индексами,
  • выборка с условиями для элементов с одинаковыми индексами с учетом группировки.

Выборка с условиями из полей-массивов

Reindexer поддерживает выборку из полей-массивов со следующими операторами сравнения (условиями фильтрации): <, >, =, IN, IS NULL, ALLSET.

Подробнее об операторах сравнения для SQL и Go в Reindexer — в разделе «Выборка данных с условиями».

Запросы с условиями фильтрации <, > и = возвращают записи, в которых в поле-массиве найден хотя бы один элемент, соответствующий условию. Примеры запросов:

db.Query("test_namespace")
		.Where("array_field", reindexer.GT, 5)
SELECT * FROM test_namespace WHERE array_field > 5
curl \
  --location \
  --request POST \
  http://127.0.0.1:9088/api/v1/db/testdb/query \
  --header 'Content-Type: application/json' \
  --data-raw \
'
{
  "namespace": "test_namespace",
  "type": "select",
  "filters": [
    {
      "field": "array_field",
      "cond": "GT",
      "value": [
        5
      ]
    }
  ]
}
'

Запросы с условием фильтрации IN (SET) возвращают записи, в которых найдено любое из заданных в условии значений. Примеры запросов:

db.Query("test_namespace")
		.WhereInt("array_field", reindexer.SET, 5, 6, 9)
SELECT * FROM test_namespace WHERE array_field IN (5, 6, 9)
curl \
  --location \
  --request POST \
  http://127.0.0.1:9088/api/v1/db/testdb/query \
  --header 'Content-Type: application/json' \
  --data-raw \
'
{
  "namespace": "test_namespace",
  "type": "select",
  "filters": [
    {
      "field": "array_field",
      "cond": "SET",
      "value": [
        5,
        6,
        9
      ]
    }
  ]
}
'

Условие фильтрации EMPTY(IS NULL) используется для поиска пустых массивов и null-значений (для sparse-индексов вместо поля-массива допустим null, и такой документ тоже будет найден по IS NULL, как и в случае со скалярными индексами).

db.Query("test_namespace")
    .WhereInt("array_field", reindexer.EMPTY)
SELECT * FROM test_namespace WHERE array_field IS NULL
curl \
  --location \
  --request POST \
  http://127.0.0.1:9088/api/v1/db/testdb/query \
  --header 'Content-Type: application/json' \
  --data-raw
'
{
  "namespace": "test_namespace",
  "type": "select",
  "filters": [
    {
      "field": "array_field",
      "cond": "EMPTY"
    }
  ]
}
'

Запросы с условием фильтрации ALLSET возвращают записи, в которых в поле-массиве найдены все из заданных в условии значений.

db.Query("test_namespace")
    .WhereInt("array_field", reindexer.ALLSET, 1, 2, 3)
SELECT * FROM test_namespace WHERE array_field ALLSET (1, 2, 3)
curl \
  --location \
  --request POST \
  http://127.0.0.1:9088/api/v1/db/testdb/query \
  --header 'Content-Type: application/json' \
  --data-raw
'
{
  "namespace": "test_namespace",
  "type": "select",
  "filters": [
    {
      "field": "array_field",
      "cond": "ALLSET",
      "value": [
        1,
        2,
        3
      ]
    }
  ]
}
'

Также запросы с условием фильтрации ALLSET могут использоваться для поиска вхождений одного поля-массива в другое. Примеры:

db.Query("test_namespace")
    .WhereBetweenFields("array_field1", reindexer.ALLSET, "array_field2")
SELECT * FROM test_namespace WHERE array_field1 ALLSET array_field2
curl \
  --location \
  --request POST \
  http://127.0.0.1:9088/api/v1/db/testdb/query \
  --header 'Content-Type: application/json' \
  --data-raw
'
{
  "namespace": "test_namespace",
  "type": "select",
  "filters": [
    {
      "first_field": "array_field1",
      "cond": "ALLSET",
      "second_field": "array_field2"
    }
  ]
}
'

Выборка данных с использованием агрегатных функций

Reindexer поддерживает выборку из полей-массивов c использованием следующих агрегатных функций: SUM, AVG, MIN, MAX, COUNT, FACET.

Подробнее об агрегатных функциях для SQL и Go в Reindexer — в разделе «Агрегатные функции».

Запросы с агрегатной функцией SUM могут использоваться для:

  • вычисления суммы элементов всех полей-массивов с указанным в запросе именем из всех записей неймспейса. Пример:
query := db.Query("test_namespace")
query.AggregateSum("array_field")

iterator := query.Exec()

if err := iterator.Error(); err != nil {
    panic(err)
}
defer iterator.Close()

aggSumRes := iterator.AggResults()[0]

if aggSumRes.Value != nil {
    fmt.Printf("Сумма всех элементов полей-массивов 'array_field' всех записей неймспейса = %v\n", *aggSumRes.Value)
} else {
    fmt.Println("Нет данных для агрегации")
}
SELECT SUM (array_field) FROM test_namespace
curl \
  --location \
  --request POST \
  http://127.0.0.1:9088/api/v1/db/testdb/query \
  --header 'Content-Type: application/json' \
  --data-raw
'
{
  "namespace": "test_namespace",
  "type": "select",
  "aggregations": [
    {
      "fields": [
        "array_field"
      ],
      "type": "SUM"
    }
  ]
}
'
  • вычисления суммы элементов определенного поля-массива записи, указанной в условии. Пример:
query := db.Query("test_namespace")
    .Where("id", reindexer.EQ, 0)
query.AggregateSum("array_field")

iterator := query.Exec()

if err := iterator.Error(); err != nil {
    panic(err)
}
defer iterator.Close()

aggSumRes := iterator.AggResults()[0]

if aggSumRes.Value != nil {
    fmt.Printf("Сумма элементов поля-массива 'array_field' для записи неймспейса с заданным `id` = %v\n", *aggSumRes.Value)
} else {
    fmt.Println("Нет данных для агрегации")
}
SELECT SUM(array_field) FROM test_namespace WHERE id = 0
curl \
  --location \
  --request POST \
  http://127.0.0.1:9088/api/v1/db/testdb/query \
  --header 'Content-Type: application/json' \
  --data-raw
'
{
  "namespace": "test_namespace",
  "type": "select",
  "aggregations": [
    {
      "fields": [
        "array_field"
      ],
      "type": "SUM"
    }
  ],
  "filters": [
    {
      "field": "id",
      "cond": "EQ",
      "value": [
        0
      ]
    }
  ]
}
'

Запросы с агрегатной функцией AVG могут использоваться для:

  • вычисления среднего значения элемента всех полей-массивов с указанным в запросе именем из всех записей неймспейса. Пример:
query := db.Query("test_namespace")
query.AggregateAvg("array_field")

iterator := query.Exec()

if err := iterator.Error(); err != nil {
    panic(err)
}
defer iterator.Close()

aggAvgRes := iterator.AggResults()[0]

if aggAvgRes.Value != nil {
    fmt.Printf("Среднее значение элемента поля-массива 'array_field' для всех записей неймспейса `id` = %v\n", *aggAvgRes.Value)
} else {
    fmt.Println("Нет данных для агрегации")
}
SELECT AVG(array_field) FROM test_namespace
curl \
  --location \
  --request POST \
  http://127.0.0.1:9088/api/v1/db/testdb/query \
  --header 'Content-Type: application/json' \
  --data-raw
'
{
  "namespace": "test_namespace",
  "type": "select",
  "aggregations": [
    {
      "fields": [
        "array_field"
      ],
      "type": "AVG"
    }
  ]
}
'
  • вычисления среднего значения элемента определенного поля-массива записи, указанной в условии. Пример:
query := db.Query("test_namespace")
		.Where("id", reindexer.EQ, 0)
query.AggregateAvg("array_field")

iterator := query.Exec()

if err := iterator.Error(); err != nil {
    panic(err)
}
defer iterator.Close()

aggAvgRes := iterator.AggResults()[0]

if aggAvgRes.Value != nil {
    fmt.Printf("Среднее значение элемента поля-массива 'array_field' для записи неймспейса с заданным `id` = %v\n", *aggAvgRes.Value)
} else {
    fmt.Println("Нет данных для агрегации")
}
SELECT AVG(array_field) FROM test_namespace WHERE id = 0
curl \
  --location \
  --request POST \
  http://127.0.0.1:9088/api/v1/db/testdb/query \
  --header 'Content-Type: application/json' \
  --data-raw
'
{
  "namespace": "test_namespace",
  "type": "select",
  "aggregations": [
    {
      "fields": [
        "array_field"
      ],
      "type": "AVG"
    }
  ],
  "filters": [
    {
      "field": "id",
      "cond": "EQ",
      "value": [
        0
      ]
    }
  ]
}
'

Запросы с агрегатными функциями MIN/MAX могут использоваться для:

  • поиска минимального/максимального значения элемента во всех полях-массива с указанным в запросе именем из всех записей неймспейса. Пример:
query := db.Query("test_namespace")
query.AggregateMin("array_field")

iterator := query.Exec()

if err := iterator.Error(); err != nil {
    panic(err)
}
defer iterator.Close()

aggMinRes := iterator.AggResults()[0]

if aggMinRes.Value != nil {
    fmt.Printf("Минимальное значение элемента для полей-массивов 'array_field' всех записей неймспейса = %v\n", *aggSumRes.Value)
} else {
    fmt.Println("Нет данных для агрегации")
}
SELECT MIN(array_field) FROM test_namespace
curl \
  --location \
  --request POST \
  http://127.0.0.1:9088/api/v1/db/testdb/query \
  --header 'Content-Type: application/json' \
  --data-raw
'
{
  "namespace": "test_namespace",
  "type": "select",
  "aggregations": [
    {
      "fields": [
        "array_field"
      ],
      "type": "MIN"
    }
  ]
}
'
  • поиска минимального/максимального значения элемента определенного поля-массива записи, указанной в условии. Пример:
query := db.Query("test_namespace")
	.Where("id", reindexer.EQ, 0)
query.AggregateMin("array_field")

iterator := query.Exec()

if err := iterator.Error(); err != nil {
    panic(err)
}
defer iterator.Close()

aggMinRes := iterator.AggResults()[0]

if aggMinRes.Value != nil {
    fmt.Printf("Минимальное значение элемента поля-массива 'array_field' для записи неймспейса с заданным `id` = %v\n", *aggMinRes.Value)
} else {
    fmt.Println("Нет данных для агрегации")
}
SELECT MIN(array_field) FROM test_namespace WHERE id = 0
curl \
  --location \
  --request POST \
  http://127.0.0.1:9088/api/v1/db/testdb/query \
  --header 'Content-Type: application/json' \
  --data-raw
'
{
  "namespace": "test_namespace",
  "type": "select",
  "aggregations": [
    {
      "fields": [
        "array_field"
      ],
      "type": "MIN"
    }
  ],
  "filters": [
    {
      "field": "id",
      "cond": "EQ",
      "value": [
        0
      ]
    }
  ]
}
'

Агрегатная функция FACET используется для получения уникальных значений полей-массивов в записях неймспейса и подсчета их количества. Фасет формируется по значениям элементов в массивах, а не по самим уникальным массивам.

Например, если в неймспейсе есть 3 записи с полем-массивом array_field:

Запись 1:

{
  "id": 0,
  "array_field": [1, 2]
}

Запись 2:

{
  "id": 1,
  "array_field": [1, 3]
}

Запись 3:

{
  "id": 2,
  "array_field": [1, 4]
}

то выдача по запросу SELECT FACET(array_field) FROM test_namespace будет выглядеть следующим образом:

count array_field
3 1
1 2
1 3
1 4

В этом примере количество уникальных значений элементов полей-массивов будет равно 4 (уникальные значения: 1, 2, 3, 4). Количество самих значений в полях-массивах суммарно во всех записях указано в поле count.

Примеры запросов:

query := db.Query("test_namespace")
query.AggregateFacet("array_field")

iterator := query.Exec()

if err := iterator.Error(); err != nil {
    panic(err)
}
defer iterator.Close()

aggFacet := iterator.AggResults()[0]

fmt.Printf("'Значение поля массива' -> Кол. уникальных\n")
for _, facet := range aggFacet.Facets {
    fmt.Printf("'%v' -> %v\n", facet.Values[0], facet.Count)
}
SELECT FACET(array_field) FROM test_namespace
curl \
  --location \
  --request POST \
  http://127.0.0.1:9088/api/v1/db/testdb/query \
  --header 'Content-Type: application/json' \
  --data-raw
'
{
  "namespace": "test_namespace",
  "type": "select",
  "aggregations": [
    {
      "fields": [
        "array_field"
      ],
      "type": "FACET"
    }
  ]
}
'

Агрегатная функция FACET может использоваться совместно с операторами сравнения (подробнее о них — в разделе «Выборка данных с условиями»). Примеры:

query := db.Query("test_namespace")
    .WhereInt("id", reindexer.GT, 5)
query.AggregateFacet("array_field")

iterator := query.Exec()

if err := iterator.Error(); err != nil {
    panic(err)
}
defer iterator.Close()

aggFacet := iterator.AggResults()[0]

fmt.Printf("'Значение поля массива' -> Кол. уникальных\n")
for _, facet := range aggFacet.Facets {
    fmt.Printf("'%v' -> %v\n", facet.Values[0], facet.Count)
}
SELECT FACET(array_field) FROM test_namespace WHERE id > 5
curl \
  --location \
  --request POST \
  http://127.0.0.1:9088/api/v1/db/testdb/query \
  --header 'Content-Type: application/json' \
  --data-raw
'
{
  "namespace": "test_namespace",
  "type": "select",
  "aggregations": [
    {
      "fields": [],
      "type": "FACET"
    }
  ],
  "filters": [
    {
      "field": "array_field",
      "cond": "GT",
      "value": [
        5
      ]
    }
  ]
}
'

Выборка элементов с одинаковыми индексами из полей-массивов

В Reindexer есть возможность добавить дополнительное условие к фильтрам по полям-массивам, требующее, чтобы индексы элементов массивов, удовлетворяющих фильтрам, совпадали для указанных в условии полей. Для этого используется функция EQUAL_POSITION().

Например, есть массив структур:

type Elem struct {
    Price    int `reindex:"array_field1"`
    Discount int `reindex:"array_field2"`
}

type Payment struct {
    Elems []Elem
}

В случае простой выборки запрос будет выглядеть следующим образом:

db.Query("test_namespace")
		.Where("array_field1", reindexer.EQ, 1)
		.Where("array_field2", reindexer.EQ, 2)
SELECT * FROM test_namespace WHERE array_field1 = 1 AND array_field2 = 2
curl \
  --location \
  --request POST \
  http://127.0.0.1:9088/api/v1/db/testdb/query \
  --header 'Content-Type: application/json' \
  --data-raw
'
{
  "namespace": "test_namespace",
  "type": "select",
  "filters": [
    {
      "field": "array_field1",
      "cond": "EQ",
      "value": [
        1
      ]
    },
    {
      "field": "array_field2",
      "cond": "EQ",
      "value": [
        2
      ]
    },
  ]
}
'

Этот запрос вернет все записи, в которых в поле array_field1 найдется значение 1, а в поле array_field2 найдется значение 2. Например, если в таблице будет 2 записи:

{
  "id": 0,
  "array_field1": [1, 2, 7],
  "array_field2": [1, 2, 5]
}

и

{
  "id": 1,
  "array_field1": [1, 2, 7],
  "array_field2": [2, 1, 5]
}

запрос вернет их обе.

Запрос с EQUAL_POSITION будет выглядеть следующим образом:

db.Query("test_namespace")
    .Where("array_field1", reindexer.EQ, 1)
    .Where("array_field2", reindexer.EQ, 2)
    .EqualPosition("array_field1", "array_field2")
SELECT * FROM test_namespace WHERE array_field1 = 1 AND array_field2 = 2 EQUAL_POSITION(array_field1, array_field2)
curl \
  --location \
  --request POST \
  http://127.0.0.1:9088/api/v1/db/testdb/query \
  --header 'Content-Type: application/json' \
  --data-raw
'
{
  "namespace": "test_namespace",
  "type": "select",
  "filters": [
    {
      "field": "array_field1",
      "cond": "EQ",
      "value": [
        1
      ]
    },
    {
      "field": "array_field2",
      "cond": "EQ",
      "value": [
        2
      ]
    },
    {
      "equal_positions": [
        {
          "positions": [
            "array_field1",
            "array_field2"
          ]
        }
      ]
    }
  ]
}
'

Этот запрос вернет все записи, в которых в поле array_field1 найдется значение 1, а в поле array_field2 найдется значение 2 и индексы найденных значений в массивах совпадут. Например, если в таблице будет 2 записи:

{
  "id": 0,
  "array_field1": [1, 2, 7],
  "array_field2": [1, 2, 5]
}

и

{
  "id": 1,
  "array_field1": [1, 2, 7],
  "array_field2": [2, 1, 5]
}

запрос вернет только вторую запись:

{
  "id": 1,
  "array_field1": [1, 2, 7],
  "array_field2": [2, 1, 5]
}

т. к. в первой для заданных в запросе значений не совпадают индексы в массивах: значение array_field1 = 1 найдено на нулевой позиции в массиве [1, 2 ,7], значение array_field2 = 2 найдено на первой позиции в массиве [1, 2, 5].

В EQUAL_POSITION можно передавать не менее 2 полей и не более, чем полей в условии.

Использование EQUAL_POSITION в сложных выражениях

Функция EQUAL_POSITION() может использоваться в сложных выражениях. При этом она должна находиться внутри скобок, т.к. применяется к полям внутри скобок. При использовании функции EQUAL_POSITION() в сложных выражениях вне скобок возможно неопределенное поведение.

Примеры:

EQUAL_POSITION() применяется к полям внутри скобок:

SELECT
    *
FROM
    test_namespace
WHERE
    (
        array_field1 >= 5
        AND array_field2 = 100
        EQUAL_POSITION(array_field1, array_field2)
    )
    OR
    (
        array_field3 = 3
        AND array_field4 < 4
        AND array_field5 = 7
        EQUAL_POSITION(array_field3, array_field4, array_field5)
    )

EQUAL_POSITION() применяется к полям внутри скобок:

SELECT
    *
FROM
    test_namespace
WHERE
    (
        array_field1 >= 5
        AND array_field2 = 100
        AND array_field3 = 3
        AND array_field4 < 4
        EQUAL_POSITION(array_field1, array_field3)
        EQUAL_POSITION(array_field2, array_field4)
    )
    OR
    (
        array_field5 = 3
        AND array_field6 < 4
        AND array_field7 = 7
        EQUAL_POSITION(array_field5, array_field7)
    )
SELECT
    *
FROM
    test_namespace
WHERE
    array_field1 >= 5
    AND
    (
        array_field2 = 100
        AND array_field3 = 3
        AND array_field4 < 4
        EQUAL_POSITION(array_field2, array_field3)
    )
    AND array_field5 = 3
    AND array_field6 < 4
    EQUAL_POSITION(array_field1, array_field5, array_field6)

Ограничения EQUAL_POSITION

В Reindexer функция EQUAL_POSITION() не работает в запросах со следующими условиями:

  • IS NULL,
  • IS EMPTY,
  • IN() с пустым списком значений.

В условиях и EQUAL_POSITION() можно передавать как имена индексов, так и JSON-пути. Для поля в условии и EQUAL_POSITION возможны следующие комбинации:

  • индекс (name), построенный поверх одного JSON-пути (json_paths1):

    Условие EQUAL_POSITION Допустимо
    name name +
    name json_paths1 +
    json_paths1 json_paths1 +
    json_paths1 name +
  • индекс (name), построенный поверх нескольких JSON-путей (json_paths1, json_paths2):

    Условие EQUAL_POSITION Допустимо
    name name +
    name json_paths1 +
    name json_paths2 +
    json_paths1 json_paths1 +
    json_paths1 json_paths2 -
    json_paths2 json_paths1 -
    json_paths2 json_paths2 +
    json_paths1 name -
    json_paths1 name -

Выборка элементов с одинаковыми индексами с учетом группировки

Для вложенных массивов поддерживается логика группировки. Для этого после имени поля надо поставить метку [#]. Она должна присутствовать в каждом поле, переданном в EQUAL_POSITION, причём только одна. В этом случае при обработке EQUAL_POSITION() будет сформирована таблица. Каждая строка содержит все значения по одному индексу помеченного массива, а номер строки равен этому индексу.

Примеры выборки с группировкой

Пусть есть структура:

type Filters struct {
	ID      int       	`reindex:"id,,pk"`
	Filters []Element 	`reindex:"filters"`
}

type Element struct {
	Price   int64   		`reindex:"price"`
	Discounts []int64 	`reindex:"discount"`
}

и запись:

{
  "ID": 1,
  "Filters": [
    {
      "Price": 99,
      "Discounts": [
        1,
        2
      ]
    },
    {
      "Price": 87,
      "Discounts": [
        1
      ]
    }
  ]
}

Пример 1:

Если требуется найти все документы, у которых в массиве Filters есть хотя бы один объект, удовлетворяющий условиям, запрос будет выглядеть следующим образом:

db.Query("test_namespace").
		Where("Filters.Price", reindexer.EQ, 87).
		Where("Filters.Discounts", reindexer.EQ, 1).
		EqualPosition("Filters[#].Price", "Filters[#].Discounts")
SELECT * FROM test_namespace WHERE Filters.Price = 87 AND Filters.Discounts = 1 EQUAL_POSITION(Filters[#].Price, Filters[#].Discounts)
curl \
  --location \
  --request POST \
  http://127.0.0.1:9088/api/v1/db/testdb/query \
  --header 'Content-Type: application/json' \
  --data-raw
'
{
  "namespace": "test_namespace",
  "type": "select",
  "filters": [
    {
      "field": "Filters.Price",
      "cond": "EQ",
      "value": [
        87
      ]
    },
    {
      "field": "Filters.Discounts",
      "cond": "EQ",
      "value": [
        1
      ]
    },
    {
      "equal_positions": [
        {
          "positions": [
            "Filters[#].Price",
            "Filters[#].Discounts"
          ]
        }
      ]
    }
  ]
}
'

Промежуточная таблица для Filters[#].Price будет такой:

N
0 99
1 87

Промежуточная таблица для Filters[#].Discounts будет такой:

N
0 1 2
1 1

Условия выполняются для строки 2 обеих таблиц, и запрос возвращает всю запись.

Пример 2:

Рассмотрим случай, когда Filters.Discounts должно быть равно 2:

db.Query("test_namespace").
		Where("Filters.Price", reindexer.EQ, 87).
		Where("Filters.Discounts", reindexer.EQ, 2).
		EqualPosition("Filters[#].Price", "Filters[#].Discounts")
SELECT
    *
FROM
    test_namespace
WHERE
    Filters.Price = 87
    AND Filters.Discounts = 2
    EQUAL_POSITION(Filters[#].Price, Filters[#].Discounts)
curl \
  --location \
  --request POST \
  http://127.0.0.1:9088/api/v1/db/testdb/query \
  --header 'Content-Type: application/json' \
  --data-raw
'
{
  "namespace": "test_namespace",
  "type": "select",
  "filters": [
    {
      "field": "Filters.Price",
      "cond": "EQ",
      "value": [
        87
      ]
    },
    {
      "field": "Filters.Discounts",
      "cond": "EQ",
      "value": [
        2
      ]
    },
    {
      "equal_positions": [
        {
          "positions": [
            "Filters[#].Price",
            "Filters[#].Discounts"
          ]
        }
      ]
    }
  ]
}
'

Промежуточная таблица для Filters[#].Price:

N
0 99
1 87

Промежуточная таблица для Filters[#].Discounts:

N
0 1 2
1 1

Условия выполняются на разных строках таблиц, запрос не возвращает записей.

Пример 3:

Пусть теперь Filters.Price должно быть равно 99:

db.Query("test_namespace").
		Where("Filters.Price", reindexer.EQ, 99).
		Where("Filters.Discounts", reindexer.EQ, 2).
		EqualPosition("Filters[#].Price", "Filters[#].Discounts")
SELECT * FROM test_namespace WHERE Filters.Price = 99 AND Filters.Discounts = 2 EQUAL_POSITION(Filters[#].Price, Filters[#].Discounts)
curl \
  --location \
  --request POST \
  http://127.0.0.1:9088/api/v1/db/testdb/query \
  --header 'Content-Type: application/json' \
  --data-raw
'
{
  "namespace": "test_namespace",
  "type": "select",
  "filters": [
    {
      "field": "Filters.Price",
      "cond": "EQ",
      "value": [
        99
      ]
    },
    {
      "field": "Filters.Discounts",
      "cond": "EQ",
      "value": [
        2
      ]
    },
    {
      "equal_positions": [
        {
          "positions": [
            "Filters[#].Price",
            "Filters[#].Discounts"
          ]
        }
      ]
    }
  ]
}
'

Промежуточная таблица для Filters[#].Price:

N
0 99
1 87

Промежуточная таблица для Filters[#].Discounts:

N
0 1 2
1 1

Условия выполняются для строки 1 обеих таблиц и запрос возвращает запись.

Примеры сложных случаев группировки

Рассмотрим запись:

{
  "array": [
    {
      "nested_object": {
        "nested_array": [
          {
            "field": 1
          },
          {
            "field": [5, 3]
          }
        ]
      }
    },
    {},
    {
      "nested_object": {
        "nested_array": [
          {
            "field": null
          },
          {
            "field": [4, 7]
          }
        ]
      }
    }
  ]
}

Группировка array.nested_object.nested_array.field[#] последовательно помещает все элементы массивов в полях field на строку, соответсвующую их номеру, включая скаляры 1 и null:

N
0 1 5 null 4
1 3 7

Группировка array.nested_object.nested_array[#].field заполняет строки таблицы всеми значениями внутри элементов массива nested_array:

N
0 1 null
1 5 3 4 7

Группировка array.nested_object[#].nested_array.field помещает все значения на нулевую строку, т. к. оба объекта nested_object по одному индексу:

N
0 1 5 3 null 4 7

Группировка array[#].nested_object.nested_array.field действует следующим образом:

N
0 1 5 3
1
2 null 4 7

Важно

Обратите внимание, что метка [#] может быть помещена только после названия поля, т. е. для примера ниже группировка по индексам вложенных массивов ([3, 4], [4, 7]) не поддерживается:

{
  "array": [
    {
      "field": [
         5,
         [3, 4]
      ]
    },
    {
      "field": [
         1,
         [4, 7]
      ]
    }
  ]
}