Выборка с подзапросами

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

Результатом подзапроса может быть:

  • Определенное поле, на которое указывает SELECT. Для этого требуется установить фильтр по одному полю.

  • Агрегация.

Ограничения по использованию подзапросов

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

  1. Подзапрос не может содержать других подзапросов.
  2. Отсутствие поддержки операторов JOIN и MERGE в подзапросах.
  3. Подзапрос не может возвращать кортежи из нескольких полей и/или результатов агрегаций.
  4. Подзапросы в reindexer не могут быть использованы внутри UPDATE и DELETE-запросов.

Примеры использования подзапросов

Ниже приведены типовые примеры выборки данных с использованием подзапросов.

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

При выполнении представленного ниже запроса составится список всех id из неймспейса second_ns, у которых age >= 18. Если хотя бы один из этих id >= 100, то условие истинно: в результат попадут все записи из main_ns. Если по результатам выполнения подзапроса не обнаружится ни одного id >= 100, условие ложно: запрос не вернет записей.

query := db.Query("main_ns").
	WhereQuery(db.Query("second_ns").Select("id").Where("age", reindexer.GE, 18), reindexer.GE, 100)
SELECT * FROM main_ns WHERE (SELECT id FROM second_ns WHERE age >= 18) >= 100
curl --location 'http://127.0.0.1:9088/api/v1/db/new_db/query' \
--header 'Content-Type: application/json' \
--data '{
  "namespace": "main_ns",
  "type": "select",
  "filters": [
    {
      "op": "and",
      "cond": "ge",
      "value": 100,
      "subquery": {
        "namespace": "second_ns",
        "select_filter": [
          "id"
        ],
        "filters": [
          {
            "op": "and",
            "cond": "ge",
            "field": "age",
            "value": 18
          }
        ]
      }
    }
  ]
}'

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

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

query := db.Query("main_ns").
	Where("id", reindexer.EQ, db.Query("second_ns").Select("id").Where("age", reindexer.LE, 18))
SELECT * FROM main_ns WHERE id = (SELECT id FROM second_ns WHERE age <= 18)
curl --location 'http://127.0.0.1:9098/api/v1/db/new_db/query' \
--header 'Content-Type: application/json' \
--data '{
  "namespace": "main_ns",
  "type": "select",
  "filters": [
    {
      "op": "and",
      "cond": "eq",
      "field": "id",
      "subquery": {
        "namespace": "second_ns",
        "select_filter": [
          "id"
        ],
        "filters": [
          {
            "op": "and",
            "cond": "le",
            "field": "age",
            "value": 18
          }
        ]
      }
    }
  ]
}'

Результат выборки по такому запросу полностью эквивалентен результату выборки по запросу с фильтрующим inner join:

SELECT * FROM main_ns INNER JOIN (SELECT * FROM second_ns WHERE age <= 18 LIMIT 0) ON main_ns.id = second_ns.id

Выборка данных с агрегацией в подзапросе

Reindexer поддерживает агрегации в подзапросе. Допускается использование не более одной агрегации. Поддерживаемые агрегации: Min, Max, Avg, Sum, Count, CountCached (подробнее о них — в разделе «Агрегатные функции»).

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

query := db.Query("main_ns").
	WhereQuery(db.Query("second_ns").Where("age", reindexer.GE, 18).AggregateMax("age"), reindexer.GE, 20)

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

query := db.Query("main_ns").
	Where("id", reindexer.GT, db.Query("second_ns").Where("age", reindexer.LE, 18).ReqTotal())

Выборка с использованием агрегатной функции MAX():

SELECT * FROM main_ns WHERE (SELECT MAX (age) FROM second_ns WHERE age >= 18) >= 20

Выборка с использованием агрегатной функции COUNT():

SELECT * FROM main_ns WHERE id > (SELECT COUNT(*) FROM second_ns WHERE age <= 18)

Выборка с использованием агрегатной функции MAX():

curl --location 'http://127.0.0.1:9088/api/v1/db/new_db/query' \
--header 'Content-Type: application/json' \
--data '{
  "namespace": "main_ns",
  "type": "select",
  "filters": [
    {
      "op": "and",
      "cond": "ge",
      "value": 20,
      "subquery": {
        "namespace": "second_ns",
        "aggregations": [
          {
            "fields": [
              "age"
            ],
            "type": "MAX"
          }
        ],
        "filters": [
          {
            "op": "and",
            "cond": "ge",
            "field": "age",
            "value": 18
          }
        ]
      }
    }
  ]
}'

Выборка с использованием агрегатной функции COUNT():

curl --location 'http://127.0.0.1:9098/api/v1/db/new_db/query' \
--header 'Content-Type: application/json' \
--data '{
  "namespace": "main_ns",
  "type": "select",
  "filters": [
    {
      "op": "and",
      "cond": "gt",
      "field": "id",
      "subquery": {
        "namespace": "second_ns",
        "aggregations": [
          {
            "fields": [],
            "type": "COUNT"
          }
        ],
        "filters": [
          {
            "op": "and",
            "cond": "ge",
            "field": "age",
            "value": 18
          }
        ]
      }
    }
  ]
}'

Выборка данных с проверкой условия

Если необходимо проверить, удовлетворяет ли хотя бы один из элементов основного запроса результату подзапроса, используйте выборку данных с условиями ANY или EMPTY.

query := db.Query("main_ns").
		WhereQuery(db.Query("second_ns").Where("age", reindexer.LE, 18), reindexer.EMPTY, nil)
SELECT * FROM main_ns WHERE (SELECT * FROM second_ns WHERE age <= 18) IS EMPTY
curl --location 'http://127.0.0.1:9088/api/v1/db/new_db/query' \
--header 'Content-Type: application/json' \
--data '{
  "namespace": "main_ns",
  "type": "select",
  "filters": [
    {
      "op": "and",
      "cond": "empty",
      "subquery": {
        "namespace": "second_ns",
        "filters": [
          {
            "op": "and",
            "cond": "le",
            "field": "age",
            "value": 18
          }
        ]
      }
    }
  ]
}'

Выборка данных из одного и того же неймспейса в основном запросе и подзапросе

query := db.Query("second_ns").
  Where("age", reindexer.GT, db.Query("second_ns").Where("age", reindexer.GT, 18).AggregateAvg("age"))
SELECT * FROM second_ns WHERE age > (SELECT AVG(age) FROM second_ns WHERE age > 18)
curl --location 'http://127.0.0.1:9098/api/v1/db/new_db/query' \
--header 'Content-Type: application/json' \
--data '{
  "namespace": "second_ns",
  "type": "select",
  "filters": [
    {
      "op": "and",
      "cond": "gt",
      "field": "age",
      "subquery": {
        "namespace": "second_ns",
        "aggregations": [
          {
            "fields": [
              "age"
            ],
            "type": "AVG"
          }
        ],
        "filters": [
          {
            "op": "and",
            "cond": "gt",
            "field": "age",
            "value": 18
          }
        ]
      }
    }
  ]
}'