Как реализовать вложенный json-объектный запрос в ES, объясните сразу!
Как реализовать вложенный json-объектный запрос в ES, объясните сразу!

1. Введение

В реальном процессе разработки программного проекта из-за потребностей бизнеса структура между нашими таблицами базы данных представляет собой связь «один-ко-многим». Если взять в качестве примера таблицу заказов и таблицу позиций заказа, то в базе данных mysql их отношения следующие. картина:

Если мы хотим узнать, какие продукты были приобретены в течение определенного периода времени,Его можно передать следующим образомjoinЗапрос путем объединения таблиц。

Язык кода:javascript
копировать
select t.*
from tb_order t
left join tb_order_item tt on t.order_id = tt.order_id
where tt.product_name like '%Название продукта%'
and  t.createTime >= '2022-06-01 00:00:00'
and t.createTime <= '2022-06-16 00:00:00';

Затем Elasticsearch и большинство NoSQL база Подобно данным, это плоская структура хранения, которая не может быть похожа на реляционную базу данных. данныхтаким образом,может пройтиjoinКак выполнить поиск по списку участников。

Однако ElasticsSearch (далее именуемый ES) все-таки дошел до версии 8.x, и уже существует несколько дополнительных способов эффективной поддержки сопоставления и поиска этой связи «один-ко-многим».

Существует три наиболее часто используемых практических решения:

  • Вложенные объекты
  • Вложенные документы
  • родительско-дочерний документ

Вторая часть — это та часть, на которой мы хотим сосредоточиться сегодня. Без лишних слов, давайте объясним вам конкретные практические идеи в виде реальных случаев.

2. Практический пример

2.1. Вложенные объекты.

так называемый Вложенные объекты,Это настоящееjsonОбъект имеет встроенныйjsonобъект,Возьмем данные заказа в качестве примера,Содержит данные о нескольких позициях,Формат следующий:

Язык кода:javascript
копировать
{
    "orderId":"1",
    "orderNo":"123456",
    "orderUserName":"Чжан Сан",
    "orderItems":[
        {
            "orderItemId":"12234",
            "orderId":"1",
            "productName":"колбаса с ветчиной",
            "brandName":"Шуанхуэй",
            "sellPrice":"28"
        },
        {
            "orderItemId":"12235",
            "orderId":"1",
            "productName":"желе",
            "brandName":"Хуюань",
            "sellPrice":"12"
        }
    ]
}

Создайтеorder_indexИндекс,Мы храним вышеуказанные данные документа в ЭП.,Взгляните на структуру индексного документа.mappingкак это выглядит,Содержание следующее:

Язык кода:javascript
копировать
{
    "order_index":{
        "mappings":{
            "_doc":{
                "properties":{
                    "orderId":{
                        "type":"keyword"
                    },
                    "orderNo":{
                        "type":"keyword"
                    },
                    "orderUserName":{
                        "type":"keyword"
                    },
                    "orderItems":{
                        "properties":{
                            "orderItemId":{
                                "type":"keyword"
                            },
                            "orderId":{
                                "type":"keyword"
                            },
                            "productName":{
                                "type":"keyword"
                            },
                            "brandName":{
                                "type":"keyword"
                            },
                            "sellPrice":{
                                "type":"keyword"
                            }
                        }
                    }
                }
            }
        }
    }
}

Вы можете очень четко видеть, куда приезжать.,Внутри поля сопоставления индекса заказа,содержитorderItemsполя,это тип объекта,Он имеет свои собственные внутренние свойства поля. На самом деле это отношения включения,Указывает, что заказ может содержать информацию о нескольких позициях.

Мы можем запросить набор результатов индекса, чтобы увидеть результаты.,использоватьpostmanВыполнить запрос для всех данных документа по индексу.!

Язык кода:javascript
копировать
POST order_index/_search
{
  "query": {
    "match_all": {}
  }
}

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

Язык кода:javascript
копировать
[
    {
        "_index":"order_index",
        "_type":"_doc",
        "_id":"1",
        "_score":1,
        "_source":{
            "orderId":"1",
            "orderNo":"123456",
            "orderUserName":"Чжан Сан",
            "orderItems":[
                {
                    "orderItemId":"12234",
                    "orderId":"1",
                    "productName":"колбаса с ветчиной",
                    "brandName":"Шуанхуэй",
                    "sellPrice":"28"
                },
                {
                    "orderItemId":"12235",
                    "orderId":"1",
                    "productName":"желе",
                    "brandName":"Хуюань",
                    "sellPrice":"12"
                }
            ]
        }
    }
]

Вы можете очень четко видеть, куда приезжать.,Возвращенные результаты также прекрасно представлены.,orderItemsНа самом деле этоlist,Содержит два объекта,Вся информация в одном документе.

Давайте попробуем еще раз ES по торговому наименованию и названию бренда,Условная фильтрация двух объединений,Чтобы запросить информацию о заказе клиента,писатьDSLоператор запроса,Найдите название продукта дляВетчина колбасаИ бренд естьХуэйюаньзаказ,Содержание следующее:

Язык кода:javascript
копировать
POST order_index/_search

{
    "query":{
        "bool":{
            "must":[
                {
                    "match":{
                        "orderItems.productName":"Колбаса с ветчиной"
                    }
                },
                {
                    "match":{
                        "orderItems.brandName":"Хуюань"
                    }
                }
            ]
        }
    }
}

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

Язык кода:javascript
копировать
[
    {
        "_index":"order_index",
        "_type":"_doc",
        "_id":"1",
        "_score":1,
        "_source":{
            "orderId":"1",
            "orderNo":"123456",
            "orderUserName":"Чжан Сан",
            "orderItems":[
                {
                    "orderItemId":"12234",
                    "orderId":"1",
                    "productName":"колбаса с ветчиной",
                    "brandName":"Шуанхуэй",
                    "sellPrice":"28"
                },
                {
                    "orderItemId":"12235",
                    "orderId":"1",
                    "productName":"желе",
                    "brandName":"Хуюань",
                    "sellPrice":"12"
                }
            ]
        }
    }
]

Анализируйте ожидаемые результаты,Ни один клиент не приобрел торговую маркуХуэйюаньИ название продуктаВетчина колбасазаказ,Теоретически,Не должно быть никаких данных

Однако в результате данные этого заказа возвращаются! Почему это?

оказаться ES дляjsonобъект数组из做了压扁иметь дело с,Например, приведенный выше пример в ES Структура хранения такая:

Язык кода:javascript
копировать
{
  "orderId": [ 1 ],
  "orderItems.productName":["колбаса с ветчиной","желе"],
  "orderItems.brandName": [«Шуанхуэй», «Хуюань»],
  ...
}

Это очевидно,Такая структура теряет связь между названием продукта и торговой маркой.,При запросе,Происходит сбой,Если бизнес требует точного поиска,Тогда это решение не соответствует требованиям

Если ваш бизнес-сценарий не чувствителен к этой проблеме, вы можете выбрать этот метод, поскольку он достаточно прост и более эффективен, чем два решения, представленные ниже.

2.2. Вложенные документы.

Это очевидно上面объект数组из方案没有иметь дело с好内部объектиз边界问题,JSON数组объект被 ES Принудительное хранение в плоском списке пар ключ-значение. Чтобы решить эту проблему, ES Было запущено так называемое решение для вложенных документов. Официальное описание этого решения выглядит следующим образом:

The nested type is a specialised version of the object datatype that allows arrays of objects to be indexed in a way that they can be queried independently of each other.

Можно смотретьприезжать Вложенные документыиз方案其实是对普通内部объект方案из补充。我们将上面заказиндекс结构中изorderItems数据тип,измените это наnestedтип,Пересоздайте индекс.

Язык кода:javascript
копировать
{
    "properties":{
        "orderItems":{
            "properties":{
                ....
            },
            "type":"nested"
        }
  ....
    }
}

orderItems数据тип,Изменить на даnested,Представляет внедренный документ,Остальные свойства остаются неизменными.

Давайте попробуем еще раз по названию продукта и торговой марки.Чтобы запросить информацию о заказе клиента,**Разница в том, что,При запросе,Необходимо указатьnestedключевые слова и путиpath**,Позиция запроса следующая:

Язык кода:javascript
копировать
POST order_index/_search

{
    "query":{
        "nested":{
            "path":"orderItems",
            "query":{
                "bool":{
                    "must":[
                        {
                            "match":{
                                "orderItems.productName":"Колбаса с ветчиной"
                            }
                        },
                        {
                            "match":{
                                "orderItems.brandName":"Хуюань"
                            }
                        }
                    ]
                }
            }
        }
    }
}

Результат запроса пуст[],Соответствует ожидаемым результатам

Давайте снова изменим условия запроса,Название продукта запроса:Ветчина колбасаи торговая маркаШуанхуэйзаказ。

Язык кода:javascript
копировать
POST order_index/_search

{
    "query":{
        "nested":{
            "path":"orderItems",
            "query":{
                "bool":{
                    "must":[
                        {
                            "match":{
                                "orderItems.productName":"Колбаса с ветчиной"
                            }
                        },
                        {
                            "match":{
                                "orderItems.brandName":"Шуанхуэй"
                            }
                        }
                    ]
                }
            }
        }
    }
}

Результаты запроса следующие:

Язык кода:javascript
копировать
[
    {
        "_index":"order_index",
        "_type":"_doc",
        "_id":"1",
        "_score":1,
        "_source":{
            "orderId":"1",
            "orderNo":"123456",
            "orderUserName":"Чжан Сан",
            "orderItems":[
                {
                    "orderItemId":"12234",
                    "orderId":"1",
                    "productName":"колбаса с ветчиной",
                    "brandName":"Шуанхуэй",
                    "sellPrice":"28"
                },
                {
                    "orderItemId":"12235",
                    "orderId":"1",
                    "productName":"желе",
                    "brandName":"Хуюань",
                    "sellPrice":"12"
                }
            ]
        }
    }
]

В конце концов, в соответствии с ожидаемыми результатами кажется, что вложенные документы очень полезны. В предыдущем решении нет проблемы отсутствия границ объекта, и оно не кажется сложным в использовании. То есть у него нет недостатков? Конечно, сначала проведем эксперимент.

Сначала посмотрите на количество документов, индексируемых в настоящее время.

Язык кода:javascript
копировать
GET _cat/indices?v

Результаты запроса.

Язык кода:javascript
копировать
green  open   order_index                   FJsEIFf_QZW4Q4SlZBsqJg   1   1          3            0     17.7kb          8.8kb

Возможно, вы заметили, что я не использовал следующую информацию для просмотра количества документов здесь.

Язык кода:javascript
копировать
GET order_index/_count

Вместо этого индексная информация просматривается напрямую. Разница между ними заключается в следующем:

  • Первый запрос — узнать общее количество документов в каждом индексе базы данных индексов.
  • второй запрос,Количество документов, запрашиваемых в данный момент в индексе,Не включено Вложенные документыколичество

Вы можете очень четко видеть, куда приезжать.,order_indexиндекс,существовать ES Данные документа ACCCIM: 3. Почему бы и нет? 1 Шерстяная ткань?

Это потому, чтоnested子文档существовать ES Внутри он фактически независим lucene Документ, только когда мы запрашиваем, ES Внутри мы сделали что-то вроде базы данныхизjoinиметь дело с。最终看起来好像是一个独立из文档一样。

Если в заказе 1000 позиций заказа, то количество документов, существующих в ЭП, будет равно 1001, что увеличится вдвое по мере увеличения количества заказов.

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

Одно можно сказать наверняка: он может удовлетворить требования по точному поиску внутренних данных объекта!

2.3. Родительско-детские документы.

Давайте посмотрим на пример выше,假如我需要更新文档изorderNo属性из值,ES обновления документации,Принцип работы:Удалить данные прохождения,Вставьте еще один,Но id индекса такие же

Это означает,Несмотря на тоorderItemsПоле,мне не нужны обновления,Он также будет переиндексирован вместе с основным документом.

Кроме того, если между определенной таблицей и определенной таблицей существует связь «многие ко многим»,Например, вложенный документ может принадлежать нескольким основным документам.,использоватьnestedНевозможно достичь,В настоящее время вы можете рассмотреть возможность использованияродительско-дочерний документ结构来иметь дело с。

Ниже мы рассмотрим экзаменационные вопросы в качестве примера.,Вопрос может иметь несколько ответов, и ответ может соответствовать нескольким вопросам.

Сначала мы определяем структуру индексного документа следующим образом:

Язык кода:javascript
копировать
PUT exam_index

{
    "mappings":{
        "_doc":{
            "properties":{
                "my_id":{
                    "type":"keyword"
                },
                "parent_join_child":{
                    "type":"join",
                    "relations":{
                        "question":"answer"
                    }
                }
            }
        }
    }
}

my_id是自定义поля,parent_join_child是给我们изродительско-дочерний документсвязьиз名字,Это можно настроить,joinозначает, что этородительско-дочерний документсвязь,relationsвыраженный внутриquestionотец,answerДа。

Сначала мы вставляем два родительских документа.

Язык кода:javascript
копировать
PUT exam_index/_doc/1

{
    "my_id":"1",
    "text":"Это вопрос 1",
    "parent_join_child":{
        "name":"question"
    }
}

PUT exam_index/_doc/2

{
    "my_id":"2",
    "text":"Это вопрос 2",
    "parent_join_child":{
        "name":"question"
    }
}

其中"name":"question"表示插入изотец文档。

Затем вставьте два вложенных документа

Язык кода:javascript
копировать
PUT exam_index/_doc/3?routing=1

{
    "my_id":"3",
    "text":"Это ответ 1, соответствующий вопросу 1",
    "parent_join_child":{
        "name":"answer",
        "parent":"1"
    }
}

PUT exam_index/_doc/4?routing=1

{
    "my_id":"4",
    "text":"Это ответ 2, соответствующий вопросу 1",
    "parent_join_child":{
        "name":"answer",
        "parent":"1"
    }
}

Вложенные документы могут объяснить больше,Сначала из документацииid我们可以判断子文档都是独立из文档(иnestedнет то же самое)。Во-вторыхroutingКлючевые слова指明了路由изidотец文档1, этотid和下面изparentКлючевые слова对应изid是一致из。

Что необходимо подчеркнуть, так это,При индексировании поддокументов,routing是必须из,Потому что вам необходимо убедиться, что дочерний документ и родительский документ находятся в одном сегменте.

"name":"answer"Ключевое слово указывает, что это вложенный документ.。

现существоватьexam_indexиндекс中有四个独立из文档,Давайте посмотримродительско-дочерний Каково положение документа при поиске?

Начнем с безусловного запроса на возврат всех данных документа.

Язык кода:javascript
копировать
POST exam_index/_search
{
    "query":{
        "match_all":{
        }
    },
    "sort":["my_id"]
}

Возвращенные результаты следующие:

Язык кода:javascript
копировать
[
    {
        "_index":"crm_exam_index",
        "_type":"_doc",
        "_id":"1",
        "_score":null,
        "_source":{
            "my_id":"1",
            "text":"Это вопрос 1",
            "parent_join_child":{
                "name":"question"
            }
        }
    },
    {
        "_index":"crm_exam_index",
        "_type":"_doc",
        "_id":"2",
        "_score":null,
        "_source":{
            "my_id":"2",
            "text":"Это вопрос 2",
            "parent_join_child":{
                "name":"question"
            }
        }
    },
    {
        "_index":"crm_exam_index",
        "_type":"_doc",
        "_id":"3",
        "_score":null,
        "_routing":"1",
        "_source":{
            "my_id":"3",
            "text":"Это ответ 1, соответствующий вопросу 1",
            "parent_join_child":{
                "name":"answer",
                "parent":"1"
            }
        }
    },
    {
        "_index":"crm_exam_index",
        "_type":"_doc",
        "_id":"4",
        "_score":null,
        "_routing":"1",
        "_source":{
            "my_id":"4",
            "text":"Это ответ 2, соответствующий вопросу 1",
            "parent_join_child":{
                "name":"answer",
                "parent":"1"
            }
        }
    }
]

Можно смотретьприезжать返回из结果带了parent_join_childКлючевые слова,Указывает, является ли это родительским или дочерним документом.

Если мы хотим запросить родительский документ через информацию вложенного документа, мы можем сделать это следующим образом:

Язык кода:javascript
копировать
POST exam_index/_search

{
    "query":{
        "has_child":{
            "type":"answer",
            "query":{
                "match":{
                    "текст":"ответ"
                }
            }
        }
    }
}

Результаты возврата:

Язык кода:javascript
копировать
[
    {
        "_index":"exam_index",
        "_type":"_doc",
        "_id":"1",
        "_score":1,
        "_source":{
            "my_id":"1",
            "text":"Это вопрос 1",
            "parent_join_child":{
                "name":"question"
            }
        }
    }
]

Если мы хотим запросить вложенные документы через информацию родительского документа, мы можем сделать это следующими способами:

Язык кода:javascript
копировать
POST exam_index/_search

{
    "query":{
        "has_parent":{
            "parent_type":"question",
            "query":{
                "match":{
                    "текст":"вопрос"
                }
            }
        }
    }
}

Результаты возврата:

Язык кода:javascript
копировать
[
    {
        "_index":"crm_exam_index",
        "_type":"_doc",
        "_id":"3",
        "_score":1,
        "_routing":"1",
        "_source":{
            "my_id":"3",
            "text":"Это ответ 1, соответствующий вопросу 1",
            "parent_join_child":{
                "name":"answer",
                "parent":"1"
            }
        }
    },
    {
        "_index":"crm_exam_index",
        "_type":"_doc",
        "_id":"4",
        "_score":1,
        "_routing":"1",
        "_source":{
            "my_id":"4",
            "text":"Это ответ 2, соответствующий вопросу 1",
            "parent_join_child":{
                "name":"answer",
                "parent":"1"
            }
        }
    }
]

Если мы хотим запросить вложенные документы по родительскому идентификатору, мы можем сделать это следующим образом:

Язык кода:javascript
копировать
POST exam_index/_search

{
    "query":{
        "parent_id":{
            "type":"answer",
            "id":"1"
        }
    }
}

Возвращаемый результат такой же, как указано выше,区别существовать于parent_id搜索默认использовать相关性算分,иhas_parent默认情况下不использовать算分。

Есть некоторые моменты, которые требуют особого внимания при использовании шаблона документа «родитель-потомок»:

  • 每一个индекс只能定义一个join field
  • родительско-дочерний документ должен находиться на одном шарде,означает запрос,Необходимо добавить операции обновления.routing
  • 可以向一个已经存существоватьизjoin field上新增связь
  • родительско-дочерний документ,Подходит для сценариев, где структура данных в основном одинакова.,Если две структуры таблицы совершенно несовместимы,Не рекомендуется использовать эту структуру.
  • родительско-дочерний Документ также имеет недостатки. Скорость выполнения запросов является самой низкой среди трех решений.

3. Резюме

Подводя итог, можно сказать, что вложенные объекты повышают производительность запросов за счет избыточных данных и подходят для сценариев, в которых требуется больше чтения и меньше записи. ES даjson数组объект进行压平иметь дело с,В результате поиск встроенных объектов будет не очень точным.,Если требования к поиску бизнес-сценария не высоки,Это решение рекомендуется.

Если бизнес-сценарий требует точного поиска, вы можете использовать решение с вложенными документами. При каждом обновлении данные документа будут удаляться, а затем вставляться снова, а производительность записи и запросов будет ниже, чем у вложенных объектов.

Если между таблицами существует сценарий «многие ко многим», можно использовать решение родительско-дочерних документов. Каждое обновление будет обновлять данные только одного документа, и запись будет быстрее, чем вложенные документы. Скорость запроса будет выше, чем у того же документа. Запросы вложенных документов выполняются в 5–10 раз медленнее!

Выбор конкретного решения также должен быть сделан разумно, исходя из текущего бизнес-сценария.

4. Справочник

1. Rhino Breeder - вложенные документы серии ES и родительско-дочерние документы.

boy illustration
Углубленный анализ переполнения памяти CUDA: OutOfMemoryError: CUDA не хватает памяти. Попыталась выделить 3,21 Ги Б (GPU 0; всего 8,00 Ги Б).
boy illustration
[Решено] ошибка установки conda. Среда решения: не удалось выполнить первоначальное зависание. Повторная попытка с помощью файла (графическое руководство).
boy illustration
Прочитайте нейросетевую модель Трансформера в одной статье
boy illustration
.ART Теплые зимние предложения уже открыты
boy illustration
Сравнительная таблица описания кодов ошибок Amap
boy illustration
Уведомление о последних правилах Points Mall в декабре 2022 года.
boy illustration
Даже новички могут быстро приступить к работе с легким сервером приложений.
boy illustration
Взгляд на RSAC 2024|Защита конфиденциальности в эпоху больших моделей
boy illustration
Вы используете ИИ каждый день и до сих пор не знаете, как ИИ дает обратную связь? Одна статья для понимания реализации в коде Python общих функций потерь генеративных моделей + анализ принципов расчета.
boy illustration
Используйте (внутренний) почтовый ящик для образовательных учреждений, чтобы использовать Microsoft Family Bucket (1T дискового пространства на одном диске и версию Office 365 для образовательных учреждений)
boy illustration
Руководство по началу работы с оперативным проектом (7) Практическое сочетание оперативного письма — оперативного письма на основе интеллектуальной системы вопросов и ответов службы поддержки клиентов
boy illustration
[docker] Версия сервера «Чтение 3» — создайте свою собственную программу чтения веб-текста
boy illustration
Обзор Cloud-init и этапы создания в рамках PVE
boy illustration
Корпоративные пользователи используют пакет регистрационных ресурсов для регистрации ICP для веб-сайта и активации оплаты WeChat H5 (с кодом платежного узла версии API V3)
boy illustration
Подробное объяснение таких показателей производительности с высоким уровнем параллелизма, как QPS, TPS, RT и пропускная способность.
boy illustration
Удачи в конкурсе Python Essay Challenge, станьте первым, кто испытает новую функцию сообщества [Запускать блоки кода онлайн] и выиграйте множество изысканных подарков!
boy illustration
[Техническая посадка травы] Кровавая рвота и отделка позволяют вам необычным образом ощипывать гусиные перья! Не распространяйте информацию! ! !
boy illustration
[Официальное ограниченное по времени мероприятие] Сейчас ноябрь, напишите и получите приз
boy illustration
Прочтите это в одной статье: Учебник для няни по созданию сервера Huanshou Parlu на базе CVM-сервера.
boy illustration
Cloud Native | Что такое CRD (настраиваемые определения ресурсов) в K8s?
boy illustration
Как использовать Cloudflare CDN для настройки узла (CF самостоятельно выбирает IP) Гонконг, Китай/Азия узел/сводка и рекомендации внутреннего высокоскоростного IP-сегмента
boy illustration
Дополнительные правила вознаграждения амбассадоров акции в марте 2023 г.
boy illustration
Можно ли открыть частный сервер Phantom Beast Palu одним щелчком мыши? Супер простой урок для начинающих! (Прилагается метод обновления сервера)
boy illustration
[Играйте с Phantom Beast Palu] Обновите игровой сервер Phantom Beast Pallu одним щелчком мыши
boy illustration
Maotouhu делится: последний доступный внутри страны адрес склада исходного образа Docker 2024 года (обновлено 1 декабря)
boy illustration
Кодирование Base64 в MultipartFile
boy illustration
5 точек расширения SpringBoot, супер практично!
boy illustration
Глубокое понимание сопоставления индексов Elasticsearch.
boy illustration
15 рекомендуемых платформ разработки с нулевым кодом корпоративного уровня. Всегда найдется та, которая вам понравится.
boy illustration
Аннотация EasyExcel позволяет экспортировать с сохранением двух десятичных знаков.