Объединение таблиц в RethinkDB
Хотите узнать, как смоделировать данные? Прочитайте о моделировании данных в RethinkDB.
Как и многие традиционные системы баз данных, RethinkDB поддерживает JOIN команды для объединения данных из нескольких таблиц. В RethinkDB объединения автоматически распределяются — команда объединения автоматически отправляется на соответствующие узлы по всему кластеру, соответствующие данные объединяются, и конечный результат предоставляется пользователю.
Давайте посмотрим, как мы можем использовать объединения в RethinkDB для запроса данных на основе отношений один ко многим и многие ко многим.
Отношения один ко многим
Использование первичных ключей
Предположим, что мы создали две таблицы: employees и companies. Мы будем использовать эти таблицы для моделирования понятия людей, работающих в организациях (каждая организация имеет несколько человек, работающих в ней, но любой человек работает только в одной организации). Вот пример документа в таблице employees:
{
"id": "543ad9c8-1744-4001-bb5e-450b2565d02c",
"name": "Jean-Luc Picard",
"company_id": "064058b6-cea9-4117-b92d-c911027a725a",
"rank": "captain"
}
И вот пример документа в таблице companies:
{
"id": "064058b6-cea9-4117-b92d-c911027a725a",
"company": "Starfleet",
"type": "paramilitary"
}
Мы можем объединить две таблицы следующим образом:
r.table("employees").eq_join("company_id", r.table("companies")).run()
Этот запрос объединяет company_id таблицы сотрудников с первичным ключом таблицы компаний. Он возвращает последовательность документов, где каждый документ содержит два поля — информацию о сотруднике и информацию о компании:
{
"left": {
"id": "543ad9c8-1744-4001-bb5e-450b2565d02c",
"name": "Jean-Luc Picard",
"company_id": "064058b6-cea9-4117-b92d-c911027a725a",
"rank": "captain"
},
"right": {
"id": "064058b6-cea9-4117-b92d-c911027a725a",
"company": "Starfleet",
"type": "paramilitary"
}
}
- Поле
leftсодержит информацию из левой таблицы в запросе (в данном случае, информация о сотруднике). - Поле
rightсодержит информацию из правой таблицы в запросе (в данном случае, информация о компании).
Мы можем применить команду zip в конце запроса для объединения двух полей в один документ. Например, следующий запрос:
r.table("employees").eq_join("company_id", r.table("companies")).zip().run()
Возвращает следующий результат:
{
"id": "064058b6-cea9-4117-b92d-c911027a725a",
"name": "Jean-Luc Picard",
"company_id": "064058b6-cea9-4117-b92d-c911027a725a",
"rank": "captain",
"company": "Starfleet",
"type": "paramilitary"
}
Использование подзапросов
Общая задача доступа к данным — извлечение одного документа с ассоциированными «дочерними» документами. (Это часто встречается в отношениях один ко многим, как показано здесь, но может быть и отношением многие ко многим или один к одному.) В нашем наборе данных мы можем захотеть извлечь информацию о компании и всех её сотрудниках. Это можно сделать одной командой ReQL с использованием merge и подзапроса в его лямбда-функции.
id = "064058b6-cea9-4117-b92d-c911027a725a"
r.table("companies").get(id).merge(lambda company:
{ 'employees': r.table('employees').get_all(company['id'],
index='company_id').coerce_to('array') }
).run()
Это вернёт результат, похожий на:
{
"id": "064058b6-cea9-4117-b92d-c911027a725a",
"company": "Starfleet",
"type": "paramilitary",
"employees": [
{
"id": "543ad9c8-1744-4001-bb5e-450b2565d02c",
"name": "Jean-Luc Picard",
"company_id": "064058b6-cea9-4117-b92d-c911027a725a",
"rank": "captain"
},
...
]
}
Где eq_join генерирует табличный результат (примерно эквивалентный SQL SELECT * FROM companies, employees WHERE companies.id = employees.company_id ), использование подзапроса создаёт вложенный документ, где объекты сотрудников возвращаются в списке в поле employees.
Использование вторичных индексов
Предположим, что наша модель данных для сотрудников хранит название компании вместо идентификатора компании:
{
"id": "543ad9c8-1744-4001-bb5e-450b2565d02c",
"name": "Jean-Luc Picard",
"company_name": "Starfleet",
"rank": "captain"
}
Мы можем создать вторичный индекс на поле company таблицы companies, и выполнить наш запрос, используя вторичный индекс:
r.table("companies").index_create("company").run()
Запрос будет выглядеть так:
r.table("employees").eq_join("company_name",
r.table("companies"), index="company").run()
Хотите узнать больше об индексах?: Прочитайте о использовании вторичных индексов в RethinkDB.
Примечание: вы также можете объединять таблицы по произвольным полям без создания индекса, используя команду inner_join. Однако произвольные внутренние объединения менее эффективны, чем объединения по равенству.
Отношения многие ко многим
Вы также можете использовать RethinkDB для запроса отношений многие ко многим. Предположим, у нас есть платформа совместного ведения блогов, где авторы сотрудничают для создания постов (несколько авторов могут работать над одним постом и публиковать несколько постов).
Для моделирования этих данных мы бы создали три таблицы — authors, posts и authors_posts, аналогично тому, как это делается в реляционной системе. Вот пример данных для таблицы authors:
{
"id": "7644aaf2-9928-4231-aa68-4e65e31bf219",
"name": "William Adama",
"tv_show": "Battlestar Galactica"
}
{
"id": "064058b6-cea9-4117-b92d-c911027a725a",
"name": "Laura Roslin",
"tv_show": "Battlestar Galactica"
}
Вот пример данных для таблицы posts:
{
"id": "543ad9c8-1744-4001-bb5e-450b2565d02c",
"title": "Decommissioning speech",
"content": "The Cylon War is long over..."
}
И вот пример данных для таблицы authors_posts:
{
"author_id": "7644aaf2-9928-4231-aa68-4e65e31bf219",
"post_id": "543ad9c8-1744-4001-bb5e-450b2565d02c"
}
{
"author_id": "064058b6-cea9-4117-b92d-c911027a725a",
"post_id": "543ad9c8-1744-4001-bb5e-450b2565d02c"
}
В отношении многие ко многим мы можем использовать несколько eq_join команд для объединения данных из всех трёх таблиц:
r.table("authors_posts").eq_join("author_id", r.table("authors")).zip().
eq_join("post_id", r.table("posts")).zip().run()
Результат этого запроса — поток документов, включающий каждый пост, написанный каждым автором в нашей базе данных:
{
"tv_show": "Battlestar Galactica",
"title": "Decommissioning speech",
"post_id": "543ad9c8-1744-4001-bb5e-450b2565d02c",
"name": "William Adama",
"id": "543ad9c8-1744-4001-bb5e-450b2565d02c",
"content": "The Cylon War is long over...",
"author_id": "7644aaf2-9928-4231-aa68-4e65e31bf219"
}
{
"tv_show": "Battlestar Galactica",
"title": "Decommissioning speech",
"post_id": "543ad9c8-1744-4001-bb5e-450b2565d02c",
"name": "Laura Roslin",
"id": "543ad9c8-1744-4001-bb5e-450b2565d02c",
"content": "The Cylon War is long over...",
"author_id": "064058b6-cea9-4117-b92d-c911027a725a"
}
Разрешение конфликтов имен полей
Если вы используете команду zip после join, документ из правой таблицы будет объединён с левым.
Рассмотрим следующий запрос:
r.table("employees").eq_join("company_id", r.table("companies"))
Предположим, его выходной результат такой:
{
# Employee
"left": {
"id": "543ad9c8-1744-4001-bb5e-450b2565d02c",
"name": "Jean-Luc Picard",
"company_id": "064058b6-cea9-4117-b92d-c911027a725a",
"rank": "captain"
},
# Company
"right": {
"id": "064058b6-cea9-4117-b92d-c911027a725a",
"company": "Starfleet",
"type": "paramilitary"
}
}
Конфликтующее поле — id. Если вы напрямую используете команду zip, поле id результата будет тем, которое из компании. Есть три способа разрешить потенциальные конфликты имен полей.
Удаление конфликтующих полей
Предположим, что вы хотите сохранить поле id сотрудника, но не поле компании. Вы можете сделать это, удалив поле right.id, а затем вызвав команду zip.
r.table("employees").eq_join("company_id", r.table("companies"))
.without({"right": {"id": True}}) # Remove the field right.id
.zip()
.run()
Переименование полей
Если вам нужно сохранить оба поля, вы можете переименовать их с помощью map и without перед использованием команды zip.
r.table("employees").eq_join("company_id", r.table("companies"))
# Copy the field right.id into right.c_id
.map( r.row.merge({
"right": {
"c_id": r.row["right"]["id"]
}
}))
# Remove the field right.id
.without({"right": {"id": True}})
.zip()
.run()
Ручное объединение левого и правого полей
Вы можете вручную объединить поля left и right без использования команды zip . Предположим, вы хотите сохранить имя сотрудника и имя его компании. Вы можете сделать так:
r.table("employees").eq_join("company_id", r.table("companies"))
.map({
"name": r.row["left"]["name"],
"company": r.row["right"]["company"]
}).run()
Дополнительная информация
Чтобы узнать больше, прочитайте о моделировании данных в RethinkDB. Для получения подробной информации ознакомьтесь с документацией API команд объединения:
© RethinkDB contributors
Licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License.
https://rethinkdb.com/docs/table-joins/