MongoDB/CouchDB: присоединить таблицу к самой себе

У меня есть реляционная модель данных, которую я думаю о переходе на MongoDB или CouchDB, и я пытаюсь понять, как будут работать запросы. Предположим, у меня есть две сущности: «Сотрудники» и «Проекты» и таблица соединения «многие ко многим» под названием «Назначения». Я хочу запросить все проекты, над которыми сотрудничали два пользователя. В SQL я мог бы сделать что-то вроде этого:

SELECT DISTINCT a1.project_id
FROM assignments a1, assignments a2
WHERE a1.project_id = a2.project_id
AND a1.employee_id = ?
AND a2.employee_id = ?

Как бы я сделал это в NoSQL, если у меня есть «документы» о сотрудниках, проектах и ​​назначениях? Или вы бы структурировали документы по-другому и как это повлияло бы на запрос?

Я был бы рад услышать ответы как для API запросов Mongo, так и для подхода Couch map/reduce.


person Paul A Jungwirth    schedule 29.05.2011    source источник


Ответы (3)


Я могу только поделиться видом с дивана, где я, гм, время от времени расслабляюсь. Во-первых, вам почти всегда приходится намеренно забывать SQL при работе с системами, основанными на документах.

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

Первый (редизайн) является предпочтительным методом, поскольку соединения по сути являются чуждым подходом для nosql, поскольку нормализация не является обязательным требованием. Основанный на документах не является реляционным.

person Dennis Kreminsky    schedule 29.05.2011
comment
Хорошо, спасибо за ваш ответ. Но у меня возникли проблемы с определением модели документа, которая позволит мне запрашивать структуру такого типа. Любые идеи? - person Paul A Jungwirth; 30.05.2011
comment
Я считаю, что вам придется выбрать основной объект, я не могу помочь с этим, потому что я ничего не знаю о вашем приложении. Если вы сосредотачиваете свою систему на пользователях, а проект относительно прост, оберните проекты пользователями или наоборот. Если пользователи и проекты одинаково важны, включите часть данных пользователя в документ проекта/назначения, чтобы этого было достаточно для отображения всего, что было получено вашим примером соединения (с недостатком необходимости обновлять два документа, если один из них изменяется). Наконец, вы можете включать идентификаторы пользователей в проекты, и это будет объединением. - person Dennis Kreminsky; 30.05.2011
comment
Назначения PS, скорее всего, должны быть объединены либо с пользователями, либо с проектами, в зависимости от вашего приложения. - person Dennis Kreminsky; 30.05.2011

В MongoDB вы можете сделать это так. Я использую интерактивную оболочку JavaScript.

Сначала создайте несколько пользователей:

> db.so.employee.insert({name: "Joe"})
> db.so.employee.insert({name: "Moe"})
> db.so.employee.insert({name: "Bart"})
> db.so.employee.insert({name: "Homer"})
> db.so.employee.find()
{ "_id" : ObjectId("4de35ccbcc0379536e1ac43b"), "name" : "Joe" }
{ "_id" : ObjectId("4de35ccfcc0379536e1ac43c"), "name" : "Moe" }
{ "_id" : ObjectId("4de35cd3cc0379536e1ac43d"), "name" : "Bart" }
{ "_id" : ObjectId("4de35cd7cc0379536e1ac43e"), "name" : "Homer" }

Теперь создайте несколько проектов

> db.so.project.insert({name: "Web App A"})
> db.so.project.insert({name: "Web App B"})
> db.so.project.insert({name: "Web App C"})
> db.so.project.find();
{ "_id" : ObjectId("4de35d0fcc0379536e1ac43f"), "name" : "Web App A" }
{ "_id" : ObjectId("4de35d13cc0379536e1ac440"), "name" : "Web App B" }
{ "_id" : ObjectId("4de35d15cc0379536e1ac441"), "name" : "Web App C" }

Добавьте пользователей в проекты

> db.so.project.update({name: "Web App A"}, {$push: {employees: ObjectId('4de35ccbcc0379536e1ac43b') }})
> db.so.project.update({name: "Web App A"}, {$push: {employees: ObjectId('4de35ccfcc0379536e1ac43c') }})
> db.so.project.update({name: "Web App B"}, {$push: {employees: ObjectId('4de35ccfcc0379536e1ac43c') }})
> db.so.project.update({name: "Web App C"}, {$push: {employees: ObjectId('4de35ccfcc0379536e1ac43c') }})
> db.so.project.update({name: "Web App B"}, {$push: {employees: ObjectId('4de35cd3cc0379536e1ac43d') }})
> db.so.project.update({name: "Web App C"}, {$push: {employees: ObjectId('4de35cd3cc0379536e1ac43d') }})
> db.so.project.update({name: "Web App B"}, {$push: {employees: ObjectId('4de35cd7cc0379536e1ac43e') }})

> db.so.project.find()
{ "_id" : ObjectId("4de35d0fcc0379536e1ac43f"), "employees" : [
    ObjectId("4de35ccbcc0379536e1ac43b"),
    ObjectId("4de35ccfcc0379536e1ac43c")
], "name" : "Web App A" }
{ "_id" : ObjectId("4de35d15cc0379536e1ac441"), "employees" : [
    ObjectId("4de35ccfcc0379536e1ac43c"),
    ObjectId("4de35cd3cc0379536e1ac43d")
], "name" : "Web App C" }
{ "_id" : ObjectId("4de35d13cc0379536e1ac440"), "employees" : [
    ObjectId("4de35cd3cc0379536e1ac43d"),
    ObjectId("4de35cd7cc0379536e1ac43e")
], "name" : "Web App B" }

Если вы сейчас хотите найти все проекты, над которыми работает "Джо"

> db.so.project.find({employees: ObjectId('4de35ccbcc0379536e1ac43b') }, {name: 1})
{ "_id" : ObjectId("4de35d0fcc0379536e1ac43f"), "name" : "Web App A" }

Найти все проекты, над которыми работает Joe OR Moe

> db.so.project.find({employees: {$in: [ObjectId('4de35ccbcc0379536e1ac43b'), ObjectId('4de35ccfcc0379536e1ac43c')] }}, {name: 1})
{ "_id" : ObjectId("4de35d0fcc0379536e1ac43f"), "name" : "Web App A" }
{ "_id" : ObjectId("4de35d15cc0379536e1ac441"), "name" : "Web App C" }
{ "_id" : ObjectId("4de35d13cc0379536e1ac440"), "name" : "Web App B" }

Найти все проекты, над которыми работает Joe AND moe

> db.so.project.find({employees: {$all: [ObjectId('4de35ccbcc0379536e1ac43b'), ObjectId('4de35ccfcc0379536e1ac43c')] }}, {name: 1})
{ "_id" : ObjectId("4de35d0fcc0379536e1ac43f"), "name" : "Web App A" }

Чтобы получить всех сотрудников для конкретного проекта, вам нужно два запроса. Поиск всех сотрудников из проекта C.

> db.so.project.find({name: "Web App C"}, {employees: 1})
{ "_id" : ObjectId("4de35d15cc0379536e1ac441"), "employees" : [
    ObjectId("4de35ccfcc0379536e1ac43c"),
    ObjectId("4de35cd3cc0379536e1ac43d")
] }

В своем приложении вы создаете новый запрос из возвращаемых значений и строите этот запрос:

> db.so.employee.find({_id: {$in: [ObjectId('4de35ccfcc0379536e1ac43c'), ObjectId('4de35cd3cc0379536e1ac43d')] }})
{ "_id" : ObjectId("4de35ccfcc0379536e1ac43c"), "name" : "Moe" }
{ "_id" : ObjectId("4de35cd3cc0379536e1ac43d"), "name" : "Bart" }

Я надеялся, что это помогло понять, как работает MongoDB и как можно строить отношения. Здесь я использовал ручное разыменование. Это означает, что я сохраняю ObjectID напрямую и извлекаю его вручную. Также существует «DBRef», и ваш драйвер доставит его для вас.

person David Raab    schedule 30.05.2011
comment
Есть ли способ присоединиться к сотрудникам в запросе проекта, чтобы можно было объединить два последних запроса в один? - person FrederickCook; 30.07.2011
comment
Нет, в MongoDB нет соединений, это фича. Вам нужно сделать это на клиенте. В вашем ПО. - person David Raab; 31.07.2011
comment
Отличные комментарии Сид, действительно полезно - person Rob Boerman; 22.09.2011

То, что вы ищете, - это отображение N: M. В этом случае вы пытаетесь сопоставить таблицу с самой собой, но это не сильно отличается от попытки сопоставить «Сотрудников» с «Проектами».

Здесь есть длинный ответ здесь на SO что я не буду повторяться здесь.

В вашем конкретном случае, я думаю, вам нужно немного изменить дизайн ваших данных. У вас есть таблица assignments, содержащая две точки данных: projectID и employeedID. Это классическая таблица для присоединения N проектов к M сотрудникам.

В MongoDB у вас вообще нет этой «таблицы». Если вы хотите назначить сотрудников для проекта, вы можете просто сохранить массив employeeID в самом проекте.

{ name: 'projectx', emps: [ 1, 2, 3] }
{ name: 'projecty', emps: [ 3, 2] }

Похоже, ваш запрос в основном "найти все проекты, в которых сотрудники 2 и 3 работают вместе". В MongoDB есть $all оператор запроса, который сделает это для вы с вышеуказанной структурой.

Обратите внимание, что это всего лишь один запрос. При разработке схемы важно проанализировать всю систему и определить все важные запросы.

person Gates VP    schedule 30.05.2011