Вложенный запрос Mongoose к модели по полю ссылочной модели

Кажется, что на stackoverflow есть много вопросов и ответов по этой теме, но я нигде не могу найти точного ответа.

Что у меня есть:

У меня есть модели Company и Person:

var mongoose = require('mongoose');
var PersonSchema = new mongoose.Schema{
                        name: String, 
                        lastname: String};

// company has a reference to Person
var CompanySchema = new mongoose.Schema{
                        name: String, 
                        founder: {type:Schema.ObjectId, ref:Person}};

Что мне нужно:

Найдите все компании, которые основали люди с фамилией «Робертсон».

Что я пробовал:

Company.find({'founder.id': 'Robertson'}, function(err, companies){
    console.log(companies); // getting an empty array
});

Затем я понял, что Person не встроен, но на него ссылаются, поэтому я использовал populate для заполнения основателя-Person, а затем попытался использовать find с фамилией «Робертсон».

// 1. retrieve all companies
// 2. populate their founders
// 3. find 'Robertson' lastname in populated Companies
Company.find({}).populate('founder')
       .find({'founder.lastname': 'Robertson'})
       .exec(function(err, companies) {
        console.log(companies); // getting an empty array again
    });

Я все еще могу запрашивать компании с идентификатором человека в виде строки. Но это не совсем то, что я хочу, как вы понимаете.

Company.find({'founder': '525cf76f919dc8010f00000d'}, function(err, companies){
    console.log(companies); // this works
});

person AzaFromKaza    schedule 15.10.2013    source источник


Ответы (3)


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

// Get the _ids of people with the last name of Robertson.
Person.find({lastname: 'Robertson'}, {_id: 1}, function(err, docs) {

    // Map the docs into an array of just the _ids
    var ids = docs.map(function(doc) { return doc._id; });

    // Get the companies whose founders are in that set.
    Company.find({founder: {$in: ids}}, function(err, docs) {
        // docs contains your answer
    });
});
person JohnnyHK    schedule 15.10.2013
comment
Вы правы, это похоже на косяк. Я дал вам простейший вложенный запрос, который я думаю реализовать, это, вероятно, тот случай, когда реляционные БД более удобны. Но в любом случае ваше решение сработало. Спасибо! - person AzaFromKaza; 15.10.2013
comment
Здравствуйте, я искал этот ответ, так как у меня та же проблема, последний вопрос, изменилось ли это или возможно ли это на мангусте 3.8.5 сейчас? Я понял, что с тех пор, как этот вопрос был опубликован, мангуст поднял несколько версий. - person maumercado; 13.02.2014
comment
@maumercado Нет, это не изменилось и вряд ли изменится. - person JohnnyHK; 13.02.2014
comment
Есть ли какие-либо обновления по этому поводу или мне все еще нужно использовать этот обходной путь? - person Steve2955; 15.08.2019

Я довольно поздно к этому: p Но я просто искал похожий ответ и подумал, что поделюсь тем, что придумал, на случай, если кто-нибудь найдет это по той же причине.

Я не смог найти способ добиться этого с помощью запросов мангуста, но я думаю, что это работает с использованием агрегации MongoDB конвейер

Чтобы получить запрос, который вы ищете, вы можете сделать что-то вроде этого:

const result=await Company.aggregate([
    {$lookup: {
        from: 'persons', 
        localField: 'founder', 
        foreignField: '_id', 
        as: 'founder'}
    },
    {$unwind: {path: '$founder'}},
    {$match: {'founder.lastname': 'Robertson'}}
]);

$lookup действует как .populate(), заменяя ссылку фактическими данными. Однако он возвращает массив, поскольку его можно использовать для сопоставления нескольких документов.

$unwind удаляет элементы из массива, и в этом случае будет просто превратить массив из одного элемента в поле.

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

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

Я не проверял производительность на этом, но я определенно предпочитаю, чтобы Mongo выполнял работу, а не отфильтровывал ненужные результаты на стороне сервера.

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

person sbrass    schedule 16.03.2020
comment
Это так помогло! Спасибо за ясное объяснение. - person Paul; 13.04.2020

На случай, если кто-то столкнется с этим в более поздние времена, Mongoose теперь поддерживает функцию объединения с функцией Populate.

Из документации мангуста:

Story.findOne({ 
    title: 'Casino Royale' 
}).populate('author').exec(function (err, story) {
    if (err) return handleError(err);
    console.log('The author is %s', story.author.name);
    // prints "The author is Ian Fleming"
});

http://mongoosejs.com/docs/populate.html

person evanmcd    schedule 01.02.2018
comment
Как бы вы сделали это и получили бы доступ к имени автора, если бы вместо этого вам нужно было сделать findMany({}) и было несколько авторов authors [{ type: Schema.ObjectId, ref: 'Author'}]? - person SS-Salt; 19.04.2020
comment
Он просит запросить вложенную ссылку, чтобы не заполнять. Он хочет найти историю по имени автора. - person Abanoub Istfanous; 22.04.2020