Выборка данных из нескольких неймспейсов
Reindexer может объединять документы из нескольких неймспейсов в один результат при выполнении запросов на выборку данных.
JOIN
Оператор JOIN
используется для выборки данных из двух неймспейсов и включения этих данных в один результирующий набор. Примеры:
type Actor struct {
ID int `reindex:"id"`
Name string `reindex:"name"`
IsVisible bool `reindex:"is_visible"`
}
type ItemWithJoin struct {
ID int `reindex:"id"`
Name string `reindex:"name"`
ActorsIDs []int `reindex:"actors_ids"`
ActorsNames []int `reindex:"actors_names"`
Actors []*Actor `reindex:"actors,,joined"`
}
....
query := db.Query("items_with_join").Join(
db.Query("actors").
WhereBool("is_visible", reindexer.EQ, true),
"actors"
).On("actors_ids", reindexer.SET, "id")
query.Exec ()
В данном примере Go-коннектор при помощи рефлексии создаёт объектное представление приджойненных данных в виде указателя на массив Actors.
Чтобы избежать использования рефлексии, можно создать структуру, которая реализует интерфейс Joinable. Это увеличивает общую производительность на 10-20% и уменьшает количество аллокаций:
// Реализация Joinable интерфейса.
// Join добавляет к объекту `ItemWithJoin` элементы из присоединенного неймспейса.
// При вызове Joinable interface можно передать дополнительную контекстную переменную для реализации дополнительной логики в Join.
func (item *ItemWithJoin) Join(field string, subitems []interface{}, context interface{}) {
switch field {
case "actors":
for _, joinItem := range subitems {
item.Actors = append(item.Actors, joinItem.(*Actor))
}
}
}
Запрос с Join
может содержать одно или несколько условий в On
, соединенных с помощью операторов And
(по умолчанию), Or
или Not
:
query := db.Query("items_with_join").
Join(
db.Query("actors").
WhereBool("is_visible", reindexer.EQ, true),
"actors").
On("actors_ids", reindexer.SET, "id").
Or().
On("actors_names", reindexer.SET, "name")
InnerJoin
объединяет данные из двух неймспейсов, в которых есть совпадение на присоединяющихся полях в обоих неймспейсах.
LeftJoin
возвращает все элементы из неймспейса на левой стороне ключевого слова LeftJoin
, вместе со значениями из неймспейса на правой стороне, или ничего, если соответствующий элемент не существует.
Join
- это псевдоним для LeftJoin
.
InnerJoin
может использоваться как условие в Where
:
query1 := db.Query("items_with_join").
WhereInt("id", reindexer.RANGE, []int{0, 100}).
Or().
InnerJoin(db.Query("actors").WhereString("name", reindexer.EQ, "ActorName"), "actors").
On("actors_ids", reindexer.SET, "id").
Or().
InnerJoin(db.Query("actors").WhereInt("id", reindexer.RANGE, []int{100, 200}), "actors").
On("actors_ids", reindexer.SET, "id")
query2 := db.Query("items_with_join").
WhereInt("id", reindexer.RANGE, []int{0, 100}).
Or().
OpenBracket().
InnerJoin(db.Query("actors").WhereString("name", reindexer.EQ, "ActorName"), "actors").
On("actors_ids", reindexer.SET, "id").
InnerJoin(db.Query("actors").WhereInt("id", reindexer.RANGE, []int{100, 200}), "actors").
On("actors_ids", reindexer.SET, "id").
CloseBracket()
Обратите внимание, что обычно оператор Or
реализует логику сокращенных вычислений для условий: если предыдущее условие верно, то следующее не вычисляется.
Но в случае InnerJoin
это работает иначе: в query1
(из примера выше) оба условия InnerJoin
вычисляются, несмотря на результат WhereInt
.
INNER JOIN
дает пересечение данных неймспейсов, LEFT JOIN
— добавляет данные в левую часть неймспейса. Запросы JOIN
могут быть объединены с другими SQL-запросами.
SELECT name FROM media_items WHERE year > 1990 INNER JOIN favorites ON content_id IN id
-- Для удобства неймспейсы в условии могут быть заданы явно:
SELECT name FROM media_items WHERE year > 1990 INNER JOIN favorites ON favorites.content_id IN media_items.id
-- Пример использования join в формате подзапроса
SELECT name FROM media_items INNER JOIN (SELECT id, name FROM services WHERE mrf = 'mos' ORDER BY year LIMIT 5) ON media_items.packages IN services.id
curl --location --request POST 'http://127.0.0.1:9088/api/v1/db/testdb/query' \
--header 'Content-Type: application/json' \
--data-raw '{
"namespace": "media_items",
"type": "select",
"filters": [
{
"field": "year",
"cond": "GE",
"value": [
1990
],
"join_query": {
"namespace": "favorites",
"type": "INNER",
"on": [
{
"left_field": "id",
"right_field": "content_id",
"cond": "EQ"
}
]
}
}
],
"select_filter": [
"name"
]
}'
Также в условии можно использовать вложенные операторы SELECT
для предварительной выборки необходимых данных.
С помощью оператора LIMIT
со значением 0
данные предварительной выборки можно использовать в качестве фильтра без добавления в итоговую выдачу:
query := db.Query("items_with_join").
WhereInt("id", reindexer.RANGE, []int{0, 100}).
Or().
InnerJoin(db.Query("actors").WhereInt("id", reindexer.RANGE, []int{100, 200}), "actors").
On("actors_ids", reindexer.SET, "id").
Limit(0)
Здесь Limit(0)
как часть InnerJoin
работает как фильтр для проверки условий.
SELECT name FROM media_items WHERE year > 1990 INNER JOIN (SELECT id, name FROM services WHERE mrf = 'mos' LIMIT 0) ON media_items.packages IN services.id
curl --location --request POST 'http://127.0.0.1:9088/api/v1/db/testdb/query' \
--header 'Content-Type: application/json' \
--data-raw '{
"namespace": "media_items",
"type": "select",
"filters": [
{
"field": "year",
"cond": "GE",
"value": [
1990
],
"join_query": {
"namespace": "services",
"type": "INNER",
"filters": [
{
"field": "mrf",
"cond": "EQ",
"value": [
"mos"
]
}
],
"limit": 0,
"on": [
{
"left_field": "packages",
"right_field": "id",
"cond": "EQ"
}
]
}
}
],
"select_filter": [
"name"
]
}'
ANTI-JOIN
Reindexer не поддерживает ANTI JOIN
напрямую, однако полностью эквивлентную выборку можно получить, используя комбинацию NOT
и INNER JOIN
.
!> На текущий момент скобки после NOT являются обязательными, это поведение будет изменено позднее.
query := db.Query("items_with_join").
Not().
OpenBracket(). // Brackets are essential here for NOT to work
InnerJoin(
db.Query("actors").
WhereBool("is_visible", reindexer.EQ, true).
Limit(0),
"actors").
On("id", reindexer.EQ, "id")
CloseBracket()
SELECT * FROM items_with_join WHERE NOT (INNER JOIN (SELECT * FROM actors WHERE is_visible = true) ON items_with_join.id = actors.id)
curl --location --request POST 'http://127.0.0.1:9088/api/v1/db/testdb/query' \
--header 'Content-Type: application/json' \
--data-raw '{
"namespace": "items_with_join",
"type": "select",
"filters": [
{
"op": "NOT",
"filters": [
{
"join_query": {
"type": "inner",
"namespace": "actors",
"filters": [
{
"field": "is_visible",
"cond": "EQ",
"value": [
true
]
}
],
"limit": 0,
"on": [
{
"left_field": "id",
"right_field": "id",
"cond": "EQ"
}
]
}
}
]
}
]
}'
MERGE
Для выборки данных из нескольких неймспейсов может использоваться оператор MERGE
.
Пример:
query := db.Query("test_namespace").
Where("year", reindexer.QE, 2022)
query2 := db.Query("test_namespace2").
Where("price", reindexer.LE, 1000)
query.Merge (query2)
SELECT * FROM test_namespace WHERE year = 2022 MERGE (SELECT * FROM test_namespace2 WHERE price <=1000)
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
]
}
],
"merge_queries": [
{
"namespace": "test_namespace2",
"type": "select",
"filters": [
{
"field": "price",
"cond": "LE",
"value": [
1000
]
}
]
}
]
}'
С информацией про объединение результатов при выборке из нескольких неймспейсов при полнотекстовом поиске можно ознакомиться в разделе «Объединение результатов полнотекстового поиска».