MongoDB: Як дізнатися, чи містить поле масиву елемент?


76

У мене є дві колекції. Перша збірка містить студентів:

{ "_id" : ObjectId("51780f796ec4051a536015cf"), "name" : "John" }
{ "_id" : ObjectId("51780f796ec4051a536015d0"), "name" : "Sam" }
{ "_id" : ObjectId("51780f796ec4051a536015d1"), "name" : "Chris" }
{ "_id" : ObjectId("51780f796ec4051a536015d2"), "name" : "Joe" }

Друга колекція містить курси:

{
        "_id" : ObjectId("51780fb5c9c41825e3e21fc4"),
        "name" : "CS 101",
        "students" : [
                ObjectId("51780f796ec4051a536015cf"),
                ObjectId("51780f796ec4051a536015d0"),
                ObjectId("51780f796ec4051a536015d2")
        ]
}
{
        "_id" : ObjectId("51780fb5c9c41825e3e21fc5"),
        "name" : "Literature",
        "students" : [
                ObjectId("51780f796ec4051a536015d0"),
                ObjectId("51780f796ec4051a536015d0"),
                ObjectId("51780f796ec4051a536015d2")
        ]
}
{
        "_id" : ObjectId("51780fb5c9c41825e3e21fc6"),
        "name" : "Physics",
        "students" : [
                ObjectId("51780f796ec4051a536015cf"),
                ObjectId("51780f796ec4051a536015d0")
        ]
}

Кожен документ курсу містить studentsмасив із списком студентів, зареєстрованих на курс. Коли студент переглядає курс на веб-сторінці, йому потрібно перевірити, чи він уже зареєструвався на курс чи ні. Для цього, коли coursesколекція отримує запит від імені студента, нам потрібно з’ясувати, чи studentsвже масив містить ObjectId студента. Чи є спосіб вказати в проекції запиту пошуку, щоб отримати студента ObjectIdз studentsмасиву, лише якщо він є?

Я спробував перевірити, чи можу я оператор $ elemMatch, але він орієнтований на масив піддокументів. Я розумію, що я міг би використовувати структуру агрегування, але, здається, у цьому випадку це було б надмірно. Фреймворк агрегації, ймовірно, не буде таким швидким, як один запит пошуку. Чи є спосіб запитати колекцію курсів, щоб повернутий документ міг бути у формі, подібній до цієї?

{
        "_id" : ObjectId("51780fb5c9c41825e3e21fc4"),
        "name" : "CS 101",
        "students" : [
                ObjectId("51780f796ec4051a536015d0"),
        ]
}

Відповіді:


73

[ редагувати на основі цього, що тепер можливо в останніх версіях]

[Оновлена ​​відповідь] Ви можете надіслати запит таким чином, щоб повернути назву класу та ідентифікатор студента, лише якщо вони вже зареєстровані.

db.student.find({},
 {_id:0, name:1, students:{$elemMatch:{$eq:ObjectId("51780f796ec4051a536015cf")}}})

і ви повернете те, що очікували:

{ "name" : "CS 101", "students" : [ ObjectId("51780f796ec4051a536015cf") ] }
{ "name" : "Literature" }
{ "name" : "Physics", "students" : [ ObjectId("51780f796ec4051a536015cf") ] }

[Оригінальна відповідь] Зараз неможливо робити те, що ви хочете зробити. Це прикро, бо ви змогли б це зробити, якби студент зберігався в масиві як об’єкт. Насправді, я трохи здивований, що ви використовуєте просто ObjectId (), оскільки це завжди вимагатиме від вас пошуку студентів, якщо ви хочете відобразити список студентів, які навчаються на певному курсі (спочатку шукайте список ідентифікаторів, а потім дивіться вгору імена в колекції студентів - два запити замість одного!)

Якщо ви зберігали (як приклад) ідентифікатор та ім'я в масиві курсу, як це:

{
        "_id" : ObjectId("51780fb5c9c41825e3e21fc6"),
        "name" : "Physics",
        "students" : [
                {id: ObjectId("51780f796ec4051a536015cf"), name: "John"},
                {id: ObjectId("51780f796ec4051a536015d0"), name: "Sam"}
        ]
}

Тоді ваш запит буде просто таким:

db.course.find( { }, 
                { students : 
                    { $elemMatch : 
                       { id : ObjectId("51780f796ec4051a536015d0"), 
                         name : "Sam" 
                       } 
                    } 
                } 
);

Якби цього студента зарахували лише до CS 101, ви б повернули:

{ "name" : "Literature" }
{ "name" : "Physics" }
{
    "name" : "CS 101",
    "students" : [
        {
            "id" : ObjectId("51780f796ec4051a536015cf"),
            "name" : "John"
        }
    ]
}

Дякуємо за підтвердження моїх думок щодо неможливості запитувати прості масиви, а також масиви піддокументів. Думаю, мені доведеться переоцінити свою схему. Чи знаєте ви, чи 10Gen планує вирішити цю проблему?
Шаргорс

Я не зміг знайти квиток на Jira для цього на jira.mongodb.org, і нічого не планується до випуску без квитка там, тому я б сказав точно точно не скоро.
Ася Камський,

Що про використання $ все , як описано в stackoverflow.com/questions/8145523 / ...
Xerri

$ all - це оператор для запитів - запит не є проблемою, проблема повертається, тому ми обговорюємо саме оператори проектування.
Ася Камський

Проблема збереження імені та ідентифікатора об’єкта в courseколекції полягає в тому, що тоді у вас є ім’я студентів, яке зберігається у кількох місцях, що потенційно може вийти з синхронізації. У цьому випадку ваш запит поверне неповні дані. Якщо навантаження на сервер та апаратне забезпечення не дуже тонкі, додатковий запит для отримання імені може бути кращим. Ви все ще можете зберігати студентські ObjectIdоб’єкти як об’єкт, щоб дозволити запит ОП:{"id":...}
drevicko 14.03.14

21

Здається, $inоператор цілком відповідає вашим цілям.

Ви можете зробити щось подібне (псевдозапит):

if (db.courses.find({"students" : {"$in" : [studentId]}, "course" : courseId }).count() > 0) {
  // student is enrolled in class
}

Крім того, ви можете видалити "course" : courseIdпункт і повернути набір усіх класів, до яких студент зарахований.


Проблема такого підходу полягає в тому, що він поверне документ із колекції курсів, лише якщо студент зареєструвався на курсі. Якщо студент не зареєструвався, потрібно буде повторити запит. Це не ефективно.
Шаргорс

Другим запитом було б отримати ідентифікатор об’єкта студента? Але хіба це вже не було б відомо (врешті-решт, ви використовуєте його для першого запиту).
xbonez

Кінцева мета - отримати документ курсу та мати прапорець, який вказує, чи студент зареєструвався на нього. Запит, який ви надали, отримає документ курсу, лише якщо студент зареєструвався на нього. Якщо студент не зареєструвався на курс, для отримання документа курсу потрібен ще один запит.
Шаргорс

1
Я не думаю, що в моделі даних є щось погане. Я підозрюю, що немає реального способу досягти того, що ви хочете, без двох запитів, тому що в кінці дня ви задаєте своїй базі даних два запитання: дайте мені документ курсу І чи студент зарахований до курсу. Навіть у традиційному реляційному базі даних ви зіткнетесь з тією ж проблемою.
xbonez

1
Пізня відповідь (вибачте!), Але жодна, що ніколи не була метою Монго. Mongo має зберігати нереляційні об'єкти даних без схеми. AFAIK не робить спроб зменшити кількість необхідних запитів.
xbonez

0

Я намагаюся пояснити, подаючи постановку проблеми та її рішення. Сподіваюся, це допоможе

Постановка проблеми:

Знайдіть усі опубліковані товари, чиє ім’я на зразок ABC Product або PQR Product, і ціна повинна бути менше 15 / -

Рішення:

Нижче наведені умови, за якими потрібно піклуватися

  1. Ціна товару повинна бути менше 15
  2. Назва товару повинна бути або ABC Product, або PQR Product
  3. Продукт повинен бути в опублікованому стані.

Нижче наведено твердження, яке застосовується вищезазначеним критерієм для створення запитів та отримання даних.

$elements = $collection->find(
             Array(
                [price] => Array( [$lt] => 15 ),
                [$or] => Array(
                            [0]=>Array(
                                    [product_name]=>Array(
                                       [$in]=>Array(
                                            [0] => ABC Product,
                                            [1]=> PQR Product
                                            )
                                        )
                                    )
                                ),
                [state]=>Published
                )
            );
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.