Простая выборка данных и выборка с условием

Простая выборка данных

Простая выборка используется для поиска и вывода данных из неймспейса БД без условий. Пример:

query := db.Query("test_namespace")
query = db.new_query("test_namespace")
ResultIterator<Item> query = db.query("test_namespace", Item.class);
SELECT * 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"
}'

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

Используя Query.Select() в go, перечисление в SELECT-секции (SELECT field1, field2, field3 from ns) в sql и select_filter в JSON при HTTP-запросах, можно управлять тем, какие поля документов будут возвращены в результате выполнения запроса. При перечислении полей требуется указывать существующие в документах JSON-пути с учётом их регистра. Несуществующие поля или поля с неправильным регистром игнорируются. Если в этом списке нет полей, удовлетворяющих данным условиям, то фильтрация не применяется, и в результате выполнения запроса будут возвращены все поля документов.

Примеры:

Пусть структура документа определена следующим образом:

type Item struct {
	ID       int64  `reindex:"id,,pk"`
	Name     string `json:"author_name" reindex:"name"`
	Articles []int  `reindex:"articles"`
	Year     int    `reindex:"year,tree"`
}
index_definitions = [
    {
        "name": "id",
        "json_paths": ["id"],
        "field_type": "int64",
        'is_pk': True,
    },
    {
        "name": "name",
        "json_paths": ["author_name"],
        "field_type": "string",
    },
    {
        "name": "articles",
        "json_paths": ["articles"],
        "field_type": "int",
        "is_array": True,
    },
    {
        "name": "year",
        "json_paths": ["year"],
        "field_type": "int",
        "index_type": "tree",
    },
]
public static class Item {
    @Reindex(name = "id", isPrimaryKey = true)
    private Integer id;

    @Reindex(name = "name")
    private String name;

    @Reindex(name = "articles")
    private List<Integer> articles;

    @Reindex(name = "year", type = TREE)
    private Integer year;

    public Item() {
    }

    public Item(Integer id, String name, List<Integer> articles, Integer year) {
        this.id = id;
        this.name = name;
        this.articles = articles;
        this.year = year;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Integer> getArticles() {
        return articles;
    }

    public void setArticles(List<Integer> articles) {
        this.articles = articles;
    }

    public Integer getYear() {
        return year;
    }

    public void setYear(Integer year) {
        this.year = year;
    }

    @Override
    public String toString() {
        return "Item{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", articles=" + articles +
                ", year=" + year +
                '}';
    }
}

Тогда корректный запрос с фильтрацией полей id и name выглядит следующим образом:

query := db.Query("test_namespace").Select("ID", "author_name")
query = db.new_query("test_namespace").select_fields("id", "author_name")
ResultIterator<Item> query = db.query("test_namespace", Item.class)
    .select("id", "author_name");
SELECT ID, author_name 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",
  "select_filter": [
    "ID",
    "author_name"
  ],
  "type": "select"
}'

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

Для выборки данных с условиями в Reindexer совместно с оператором SELECT используется ключевое слово WHERE.

Можно использовать выражения с общими операторами сравнения (в SQL-запросах) и операторами сравнения значений (при взаимодействии с БД через коннекторы):

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

Оператор Описание/пояснение
= Равно
> Больше
< Меньше
>= Больше или равно
<= Меньше или равно
<> Не равно
IN Определяет набор значений, с которым будут сравниваться результаты при выборке
RANGE Определяет диапазон значений, которые будут использоваться при выборке
LIKE Поиск по заданному шаблону. Подробнее об операторе — в разделе Простой поиск
IS NULL Значение не задано
IS NOT NULL Задано какое-либо значение
ST_DWithin Получение объектов, находящихся на определенном расстоянии от других объектов (только для геометрического типа данных)

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

Оператор Описание/пояснение
EQ Равно
GT Больше
LT Меньше
GE Больше или равно
LE Меньше или равно
SET Проверяет значение в наборе значений, разделенных запятыми, и извлекает соответствующие строки из неймспейса. Также позволяет проверить, есть ли у массивов общие элементы
ALLSET Проверяет значение в наборе значений, разделенных запятыми, и извлекает соответствующие строки из неймспейса. Также позволяет проверить, есть ли у массивов общие элементы, при этом все элементы первого массива должны входить во второй
RANGE Определяет диапазон значений, которые будут использоваться при выборке
ANY Любое значение
EMPTY Пустое значение (в том числе и массив нулевой длины)
LIKE Поиск по заданному шаблону. Подробнее об операторе — в разделе Простой поиск
ST_DWithin() Получение объектов, находящихся на определенном расстоянии от других объектов (только для геометрического типа данных)
Match() Запросы к полнотекстовым индексам. Подробнее — в разделе Запросы к полнотекстовым индексам
Not() Логическое отрицание для любого следующего оператора
Оператор Описание/пояснение
CondType.CondEq Равно
CondType.CondGt Больше
CondType.CondLt Меньше
CondType.CondGe Больше или равно
CondType.CondLe Меньше или равно
CondType.CondSet Проверяет значение в наборе значений, разделенных запятыми, и извлекает соответствующие строки из неймспейса. Также позволяет проверить, есть ли у массивов общие элементы
CondType.CondAllSet Проверяет значение в наборе значений, разделенных запятыми, и извлекает соответствующие строки из неймспейса. Также позволяет проверить, есть ли у массивов общие элементы, при этом все элементы первого массива должны входить во второй
CondType.CondRange Определяет диапазон значений, которые будут использоваться при выборке
CondType.CondAny Любое значение
CondType.CondEmpty Пустое значение (в том числе и массив нулевой длины)
CondType.CondLike Поиск по заданному шаблону. Подробнее об операторе — в разделе Простой поиск
CondType.CondDWithin Получение объектов, находящихся на определенном расстоянии от других объектов (только для геометрического типа данных)
match() Запросы к полнотекстовым индексам. Подробнее — в разделе Запросы к полнотекстовым индексам
op_not() Логическое отрицание для любого следующего оператора

Условия можно комбинировать с помощью логических операторов OR и AND. Большинство условий также может быть инвертировано при помощи оператора NOT (за исключением полнотекстовых и KNN).

При этом операции с OR имеют более высокий приоритет, чем AND. Например, условие вида WHERE a AND b OR c AND d эквивалентно WHERE a AND (b OR c) AND d.

Также Reindexer поддерживает условия с фильтрацией при помощи сравнения полей одного и того же документа.

Пример запроса с одним условием:

query := db.Query("test_namespace").
    Where("year", reindexer.EQ, 2022)
query = db.new_query("test_namespace").where("year", CondType.CondEq, 2022)
ResultIterator<Item> query = db.query("test_namespace", Item.class)
    .where("year", Condition.EQ, 2022);
SELECT * FROM test_namespace WHERE year = 2022
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": "year",
      "cond": "EQ",
      "value": 2022
    }
  ]
}'

Пример запроса с одним отрицание (NOT). Обратите внимание, что NOT в этом случае ставится перед самим условием, а не перед оператором:

query := db.Query("test_namespace").
    Not().Where("year", reindexer.EQ, 2022)
query = db.new_query("test_namespace").op_not().where("year", CondType.CondEq, 2022)
ResultIterator<Item> query = db.query("test_namespace", Item.class)
    .not().where("year", Condition.EQ, 2022);
SELECT * FROM test_namespace WHERE NOT year = 2022
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": [
    {
      "op": "NOT",
      "field": "year",
      "cond": "EQ",
      "value": 2022
    }
  ]
}'

Пример запроса с несколькими условиями:

query := db.Query("test_namespace").
	Where("year", reindexer.EQ, 2022).
	Where("price", reindexer.LE, 1000).
	Not().Where("price", reindexer.SET, []int{100, 200, 300})
query = (
    db.new_query("test_namespace")
        .where("year", CondType.CondEq, 2022)
        .where("price", CondType.CondLe, 1000)
        .op_not().where("price", CondType.CondSet, [100, 200, 300])
)
ResultIterator<Item> query = db.query("test_namespace", Item.class)
    .where("year", Condition.EQ, 2022)
    .where("price", Condition.LE, 1000)
    .not().where("price", Condition.SET, 100, 200, 300);
SELECT * FROM test_namespace WHERE year = 2022 AND price <= 1000 AND NOT price IN (100, 200, 300)
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": "year",
      "cond": "EQ",
      "value": 2022
    },
    {
      "field": "price",
      "cond": "LE",
      "value": 1000
    },
    {
      "op": "NOT",
      "field": "price",
      "cond": "LE",
      "value": [100, 200, 300]
    }
  ]
}'

Приоритет операций может быть изменён при помощи использования скобок. Механизм задания скобок зависит от конкретного API:

query := db.Query("test_namespace").
	OpenBracket().
	Where("year", reindexer.EQ, 2022).
	Where("price", reindexer.LE, 1000).
	CloseBracket().
	Or().
	OpenBracket().
	Where("year", reindexer.EQ, 2010).
	Where("price", reindexer.GT, 2000).
	CloseBracket()
query = (
    db.new_query("test_namespace")
        .open_bracket()
            .where("year", CondType.CondEq, 2022)
            .where("price", CondType.CondLe, 1000)
        .close_bracket()
        .op_or()
        .open_bracket()
            .where("year", CondType.CondEq, 2010)
            .where("price", CondType.CondGt, 2000)
        .close_bracket()
)
ResultIterator<Item> query = db.query("test_namespace", Item.class)
    .openBracket()
        .where("year", Condition.EQ, 2022)
        .where("price", Condition.LE, 1000)
    .closeBracket()
    .or()
    .openBracket()
        .where("year", Condition.EQ, 2010)
        .where("price", Condition.GT, 2000)
    .closeBracket();
SELECT * FROM test_namespace WHERE (year = 2022 AND price <= 1000) OR (year = 2010 AND price > 2000)
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": [
    {
      "filters": [
        {
          "field": "year",
          "cond": "EQ",
          "value": 2022
        },
        {
          "field": "price",
          "cond": "LE",
          "value": 1000
        }
      ]
    },
    {
      "op": "OR",
      "filters": [
        {
          "field": "year",
          "cond": "EQ",
          "value": 2010
        },
        {
          "field": "price",
          "cond": "GT",
          "value": 2000
        }
      ]
    }
  ]
}'

Пример запроса с использованием условного оператора LIKE:

query := db.Query("test_namespace").
	Where("field_description", reindexer.LIKE, "info%")
query = (
    db.new_query("test_namespace")
        .where("field_description", CondType.CondLike, "info%")
)
ResultIterator<Item> query = db.query("test_namespace", Item.class)
    .where("field_description", Condition.LIKE, "info%");
SELECT * FROM test_namespace WHERE field_description LIKE 'info%'
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": "name",
      "cond": "LIKE",
      "value": "info%"
    }
  ]
}'

LIKE использует метод полного сканирования поля и его применение ведет к увеличению нагрузки на сервер. Для полнотекстового поиска с высокой скоростью рекомендуется использовать полнотекстовые индексы.

Пример запроса с фильтрацией при помощи сравнения полей одного и того же документа:

В Go-коннекторе для запросов с фильтрацией при помощи сравнения полей одного и того документа используется метод WhereBetweenFields().

query := db.Query("test_namespace").
	WhereBetweenFields("price", reindexer.GE, "recommended_price")
query = (
    db.new_query("test_namespace")
        .where_between_fields("price", CondType.CondGe, "recommended_price")
ResultIterator<Item> query = db.query("test_namespace", Item.class)
    .whereBetweenFields("price", Condition.GE, "recommended_price");

В SQL-запросах имена индексов (полей неймспейсов) должны быть указаны в двойных кавычках. При указании в одинарных кавычках они будут восприниматься как строки.

SELECT * FROM test_namespace WHERE "recommended_price" > "price"
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": [
    {
    "cond": "le",
    "left_expression": {
        "type": "field",
        "value": "recommended_price"
    },
    "right_expression": {
        "type": "field",
        "value": "price"
    }
  ]
}'

Пример запроса для выборки из композитного индекса:

query := db.Query("test_namespace")
  .WhereComposite("price+year", reindexer.EQ, []interface{}{10000,2022})
db.index_add(
    "test_namespace",
    {
        "name": "price+year",
        "field_type": "composite",
        "json_paths": ["price", "year"]
    }
)

query = (
    db.new_query("test_namespace")
        .where_composite("price+year", CondType.CondEq, (10000,2022))
ResultIterator<Item> query = db.query("test_namespace", Item.class)
    .whereComposite("price+year", Condition.EQ, 10000, 2022);

Имя композитного индекса необходимо передавать в кавычках, а требуемые значения — в фигурных скобках.

SELECT * FROM test_namespace WHERE "price+year" = {10000, 2022}

Особенности поиска по условию IS NULL

  • Фильтрация по IS NULL допустима только для sparse-индексов, массивов, векторных индексов и неиндексных полей;
  • Условие f = NULL будет заменено планировщиком на f IS NULL;
  • Условие f IN (1, 2, 3, NULL) будет заменено планировщиком на f IN (1, 2, 3) OR f IS NULL;
  • Условие f ALLSET (1, 2, 3, NULL) будет заменено планировщиком на f ALLSET (1, 2, 3) AND f IS NULL;
  • Для остальных условий c аргументом NULL (например f > NULL) будет возвращена ошибка.

Результаты запросов по условию IS NULL в зависимости от значения поля в документе:

  • + - документ будет в выдаче,
  • - - документа не будет в выдаче,
  • n - невозможно создать такой документ.

Поле без вложенности. В индексе задан json_paths: ["f"] или json_paths: ["obj"] в зависимости от контекста:

Документ Неиндексное Массив Sparse Sparse массив
{} + + + +
{"f": null} + + + +
{"f": []} + + + +
{"f": [null]} + - + +
{"f": [1, null]} + - + +
{"f": 0} - - - -
{"f": [0]} - - - -
{"obj": {}} - n n n
{"obj": {"f": null}} - n n n
{"obj": {"f": null, "a": 0}} - n n n

Поле без вложенности. В индексе задан json_paths: ["f1", "f2"] или json_paths: ["obj1", "obj2"] в зависимости от контекста:

Документы Массив с несколькими json путями
{}, {} +
{"f1": 1}, {} -
{"f1": null}, {"f2": null} +
{"f1": 1}, {"f2": null} -
{"f1": []}, {"f2": []} +
{"f1": [1]}, {"f2": []} -
{"f1": [null]}, {"f2": [null]} -
{"f1": [1, null]}, {"f2": [1, null]} -
{"f1": 0}, {"f2": 0} -
{"f1": [0]}, {"f2": [0]} -
{"obj1": {}}, {"obj2": {}} n
{"obj1": {"f": null}}, {"obj2": {"f": null}} n
{"obj1": {"f": null, "a": 0}}, {"obj2": {"f": null, "a": 0}} n

Поле со вложенностью. В индексе задан json_paths: ["obj.f"]:

Документ Массив Sparse Sparse массив
{"obj": {}} + + +
{"obj": {"f": null}} + + +
{"obj": {"f": null, "a": 0}} + + +
{"obj": {"f": [null]}} - n +
{"obj": {"f": [1, null]}} - n +

Поле со вложенностью. В индексе задан json_paths: ["obj1.f", "obj2.f"]:

Документы Массив с несколькими json путями
{"obj1": {}}, {"obj2": {}} +
{"obj1": {"f": 1}}, {"obj2": {}} -
{"obj1": {"f": null}}, {"obj2": {"f": null}} +
{"obj1": {"f": 1}}, {"obj2": {"f": null}} -
{"obj1": {"f": null, "a": 0}}, {"obj2": {"f": null, "a": 0}} +
{"obj1": {"f": 1, "a": 0}}, {"obj2": {"f": null, "a": 0}} -
{"obj1": {"f": [null]}}, {"obj2": {"f": [null]}} -
{"obj1": {"f": [1, null]}}, {"obj2": {"f": [1, null]}} -