Как выполнить эквивалент SQL Join в MongoDB? - PullRequest
451 голосов
/ 28 февраля 2010

Как выполнить эквивалент SQL-соединения в MongoDB?

Например, скажем, у вас есть две коллекции (пользователи и комментарии), и я хочу получить все комментарии с pid = 444 вместе с информацией о пользователях для каждой.

comments
  { uid:12345, pid:444, comment="blah" }
  { uid:12345, pid:888, comment="asdf" }
  { uid:99999, pid:444, comment="qwer" }

users
  { uid:12345, name:"john" }
  { uid:99999, name:"mia"  }

Есть ли способ получить все комментарии с определенным полем (например, ... find ({pid: 444})) и информацию о пользователе, связанную с каждым комментарием, за один раз?

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

Ответы [ 19 ]

272 голосов
/ 04 ноября 2015

Начиная с версии Mongo 3.2, ответы на этот вопрос больше не являются правильными. Новый оператор $ lookup, добавленный в конвейер агрегации, по сути идентичен левому внешнему соединению:

https://docs.mongodb.org/master/reference/operator/aggregation/lookup/#pipe._S_lookup

Из документов:

{
   $lookup:
     {
       from: <collection to join>,
       localField: <field from the input documents>,
       foreignField: <field from the documents of the "from" collection>,
       as: <output array field>
     }
}

Конечно, Mongo не реляционная база данных, и разработчики стараются рекомендовать конкретные варианты использования для $ lookup, но, по крайней мере, начиная с 3.2, теперь возможно соединение с MongoDB.

137 голосов
/ 08 февраля 2011

Эта страница на официальном сайте mongodb имеет адрес точно этот вопрос:

http://docs.mongodb.org/ecosystem/tutorial/model-data-for-ruby-on-rails/

Когда мы показываем наш список историй, нам нужно будет показать имя пользователя, который опубликовал историю. Если бы мы использовали реляционную базу данных, мы могли бы выполнить объединение пользователей и хранилищ и получить все наши объекты в одном запросе. Но MongoDB не поддерживает объединения и поэтому иногда требует некоторой денормализации. Здесь это означает кэширование атрибута 'username'.

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

130 голосов
/ 30 марта 2014

Мы можем объединить / объединить все данные только в одной коллекции с помощью простой функции в несколько строк, используя клиентскую консоль mongodb, и теперь мы можем выполнить требуемый запрос. Ниже полный пример,

.- Авторы:

db.authors.insert([
    {
        _id: 'a1',
        name: { first: 'orlando', last: 'becerra' },
        age: 27
    },
    {
        _id: 'a2',
        name: { first: 'mayra', last: 'sanchez' },
        age: 21
    }
]);

.- Категории:

db.categories.insert([
    {
        _id: 'c1',
        name: 'sci-fi'
    },
    {
        _id: 'c2',
        name: 'romance'
    }
]);

.- Книги

db.books.insert([
    {
        _id: 'b1',
        name: 'Groovy Book',
        category: 'c1',
        authors: ['a1']
    },
    {
        _id: 'b2',
        name: 'Java Book',
        category: 'c2',
        authors: ['a1','a2']
    },
]);

.- Книжное кредитование

db.lendings.insert([
    {
        _id: 'l1',
        book: 'b1',
        date: new Date('01/01/11'),
        lendingBy: 'jose'
    },
    {
        _id: 'l2',
        book: 'b1',
        date: new Date('02/02/12'),
        lendingBy: 'maria'
    }
]);

.- Магия:

db.books.find().forEach(
    function (newBook) {
        newBook.category = db.categories.findOne( { "_id": newBook.category } );
        newBook.lendings = db.lendings.find( { "book": newBook._id  } ).toArray();
        newBook.authors = db.authors.find( { "_id": { $in: newBook.authors }  } ).toArray();
        db.booksReloaded.insert(newBook);
    }
);

.- Получить новые данные коллекции:

db.booksReloaded.find().pretty()

.- Ответ:)

{
    "_id" : "b1",
    "name" : "Groovy Book",
    "category" : {
        "_id" : "c1",
        "name" : "sci-fi"
    },
    "authors" : [
        {
            "_id" : "a1",
            "name" : {
                "first" : "orlando",
                "last" : "becerra"
            },
            "age" : 27
        }
    ],
    "lendings" : [
        {
            "_id" : "l1",
            "book" : "b1",
            "date" : ISODate("2011-01-01T00:00:00Z"),
            "lendingBy" : "jose"
        },
        {
            "_id" : "l2",
            "book" : "b1",
            "date" : ISODate("2012-02-02T00:00:00Z"),
            "lendingBy" : "maria"
        }
    ]
}
{
    "_id" : "b2",
    "name" : "Java Book",
    "category" : {
        "_id" : "c2",
        "name" : "romance"
    },
    "authors" : [
        {
            "_id" : "a1",
            "name" : {
                "first" : "orlando",
                "last" : "becerra"
            },
            "age" : 27
        },
        {
            "_id" : "a2",
            "name" : {
                "first" : "mayra",
                "last" : "sanchez"
            },
            "age" : 21
        }
    ],
    "lendings" : [ ]
}

Надеюсь, эти строки помогут вам.

39 голосов
/ 28 февраля 2010

Вы должны сделать это так, как вы описали. MongoDB является нереляционной базой данных и не поддерживает объединения.

17 голосов
/ 25 марта 2012

Вот пример «соединения» * Актеры и Фильмы коллекции:

https://github.com/mongodb/cookbook/blob/master/content/patterns/pivot.txt

Используется метод .mapReduce()

* объединение - альтернатива объединению в документно-ориентированных базах данных

16 голосов
/ 04 мая 2015

Как уже отмечали другие, вы пытаетесь создать реляционную базу данных из не реляционной базы данных, которую вы на самом деле не хотите делать, но в любом случае, если у вас есть такой случай, вам нужно использовать это решение , Сначала мы делаем поиск foreach в коллекции A (или в вашем случае пользователей), а затем мы получаем каждый элемент как объект, затем мы используем свойство объекта (в вашем случае uid) для поиска во второй коллекции (в комментариях к вашему делу), если может найти его, тогда у нас есть совпадение, и мы можем напечатать или сделать что-то с ним. Надеюсь, это поможет вам и удачи:)

db.users.find().forEach(
function (object) {
    var commonInBoth=db.comments.findOne({ "uid": object.uid} );
    if (commonInBoth != null) {
        printjson(commonInBoth) ;
        printjson(object) ;
    }else {
        // did not match so we don't care in this case
    }
});
11 голосов
/ 19 апреля 2017

При правильной комбинации $ lookup , $ project и $ match вы можете объединять несколько таблиц по нескольким параметрам. Это потому, что они могут быть связаны несколько раз.

Предположим, мы хотим сделать следующее ( ссылка )

SELECT S.* FROM LeftTable S
LEFT JOIN RightTable R ON S.ID =R.ID AND S.MID =R.MID WHERE R.TIM >0 AND 
S.MOB IS NOT NULL

Шаг 1: связать все таблицы

вы можете $ искать столько таблиц, сколько хотите.

$ lookup - по одному для каждой таблицы в запросе

$ unwind - потому что данные корректно денормализованы, иначе они обернуты в массивы

код Python ..

db.LeftTable.aggregate([
                        # connect all tables

                        {"$lookup": {
                          "from": "RightTable",
                          "localField": "ID",
                          "foreignField": "ID",
                          "as": "R"
                        }},
                        {"$unwind": "R"}

                        ])

Шаг 2: Определить все условия

$ project : задайте здесь все условные операторы, а также все переменные, которые вы хотите выбрать.

Код Python ..

db.LeftTable.aggregate([
                        # connect all tables

                        {"$lookup": {
                          "from": "RightTable",
                          "localField": "ID",
                          "foreignField": "ID",
                          "as": "R"
                        }},
                        {"$unwind": "R"},

                        # define conditionals + variables

                        {"$project": {
                          "midEq": {"$eq": ["$MID", "$R.MID"]},
                          "ID": 1, "MOB": 1, "MID": 1
                        }}
                        ])

Шаг 3: объединить все условия

$ match - объединить все условия, используя OR или AND и т. Д. Их может быть несколько.

$ project : отменить все условия

Код Python ..

db.LeftTable.aggregate([
                        # connect all tables

                        {"$lookup": {
                          "from": "RightTable",
                          "localField": "ID",
                          "foreignField": "ID",
                          "as": "R"
                        }},
                        {"$unwind": "$R"},

                        # define conditionals + variables

                        {"$project": {
                          "midEq": {"$eq": ["$MID", "$R.MID"]},
                          "ID": 1, "MOB": 1, "MID": 1
                        }},

                        # join all conditionals

                        {"$match": {
                          "$and": [
                            {"R.TIM": {"$gt": 0}}, 
                            {"MOB": {"$exists": True}},
                            {"midEq": {"$eq": True}}
                        ]}},

                        # undefine conditionals

                        {"$project": {
                          "midEq": 0
                        }}

                        ])

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

10 голосов
/ 05 ноября 2013

Это зависит от того, что вы пытаетесь сделать.

В настоящее время она настроена как нормализованная база данных, и это хорошо, и то, как вы это делаете, уместно.

Однако есть и другие способы сделать это.

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

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

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

TL; DR: То, что вы делаете, хорошо, и есть другие способы сделать это. Посмотрите шаблоны моделей данных документации mongodb для некоторых замечательных примеров. http://docs.mongodb.org/manual/data-modeling/

10 голосов
/ 09 августа 2016

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

db.comments.aggregate({
    $lookup:{
        from:"users",
        localField:"uid",
        foreignField:"uid",
        as:"users_comments"
    }
})

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

db.users.aggregate({
    $lookup:{
        from:"comments",
        localField:"uid",
        foreignField:"uid",
        as:"users_comments"
    }
})

Это будет работать так же, как левое и правое объединение в SQL.

9 голосов
/ 01 марта 2010

Существует спецификация, которую поддерживают многие драйверы, которая называется DBRef.

DBRef - более формальная спецификация для создания ссылок между документами. DBRefs (как правило) включают имя коллекции, а также идентификатор объекта. Большинство разработчиков используют DBRef только в том случае, если коллекция может перейти от одного документа к другому. Если ваша ссылочная коллекция всегда будет одинаковой, приведенные выше справочные руководства более эффективны.

Взято из документации MongoDB: Модели данных> Справочник по моделям данных> База данных ссылок

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...