Объединение таблиц в 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 производит результат, похожий на таблицу (грубый эквивалент SELECT * FROM companies, employees WHERE companies.id = employees.company_id SQL), используя подзапрос создаёт вложенный документ, где объекты сотрудников возвращаются в списке в поле 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/