Как получить документ в mongodb, только если по крайней мере 3 столбца имеют ненулевые значения

#mongodb #mongodb-query

#mongodb #mongodb-запрос

Вопрос:

У меня есть коллекция в MongoDB, скажем, STUDENT с атрибутами id, name, standard, marks, average. Теперь я хочу написать запрос, чтобы получить только те документы, в которых по крайней мере 3 атрибута содержат ненулевые значения.

Должны быть напечатаны все те документы, которые содержат ненулевое значение в (имя, стандарт, отметки) или (имя, отметки, среднее) или (имя, стандарт, отметки, среднее) или (идентификатор, имя, стандарт, отметки, среднее). Но если какой-либо документ содержит только (name, standard) как ненулевой или (standard, marks), его следует игнорировать.

Комментарии:

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

2. Теперь это более понятно?

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

4. Реальные примеры документов сделают его более понятным для более широкой аудитории. Помните, что не все видят то, что видите вы. Ваша задача — четко представить свое дело миру, который не работает за вашим столом.

5. Ну, при этом 4 оператора $ or будут работать

Ответ №1:

Я бы сказал, что, учитывая «студенческие» документы, подобные этому:

 { name: "a", standard: "b", marks: 10 },
{ name: "b", marks: 5, average: 2 },
{ id: 2, name: "c", marks: 10, average: 7 },
{ name: "c", standard: "b" },
{ standard: "c", marks: 3 }
 

Тогда «в идеале» вы должны сделать что-то вроде этого:

 db.students.find({
    "$or": [
        { 
           "$and": [
                { "name": { "$exists": true } },
                { "name": { "$ne": null } },
                { "standard": { "$exists": true } },
                { "standard": { "$ne": null } },
                { "marks": { "$exists": true } },
                { "marks": { "$ne": null } },
           ],
        },
        {
           "$and": [
                { "name": { "$exists": true } },
                { "name": { "$ne": null } },
                { "marks": { "$exists": true } },
                { "marks": { "$ne": null } },
                { "average": { "$exists": true } },
                { "average": { "$ne": null } }
           ],
        },
        {
           "$and": [
                { "name": { "$exists": true } },
                { "name": { "$ne": null } },
                { "marks": { "$exists": true } },
                { "marks": { "$ne": null } },
                { "standard": { "$exists": true } },
                { "standard": { "$ne": null } },
                { "average": { "$exists": true } },
                { "average": { "$ne": null } }
           ],
        },
        {
           "$and": [
                { "id": { "$exists": true } },
                { "id": { "$ne": null } },
                { "name": { "$exists": true } },
                { "name": { "$ne": null } },
                { "marks": { "$exists": true } },
                { "marks": { "$ne": null } },
                { "standard": { "$exists": true } },
                { "standard": { "$ne": null } },
                { "average": { "$exists": true } },
                { "average": { "$ne": null } }
           ],
        }
    ]
})
 

Что исключает эти последние два документа.

Также в современных версиях MongoDB 2.6 и выше вы получаете пересечение индексов или версию такого в версиях 2.4 с учетом $or операнда. Таким образом, вы можете индексировать следующим образом:

  db.student.ensureIndex({ "name": 1, "standard": 1, "marks": 1 })
 db.student.ensureIndex({ "name": 1, "marks": 1, "average": 1 })
 db.student.ensureIndex({ "name": 1, "marks": 1, "standard": 1, "average": 1 })
 db.student.ensureIndex({ "id": 1, "name": 1, "marks": 1, "standard": 1, "average": 1 })
 

Это может привести к значительному использованию «индексного» пространства, поэтому в этом случае средства могут перевесить цели.

Конечно, для более гибкого подхода к определению этого (хотя и не так быстро) вы можете обратиться к структуре агрегации:

 db.students.aggregate([
    { "$project": {
        "id": { "$ifNull": [ "$id", null ] },
        "name": { "$ifNull": [ "$name", null ] },
        "marks": { "$ifNull": [ "$marks", null ] },
        "standard": { "$ifNull": [ "$standard", null ] },
        "average": { "$ifNull": [ "$average", null ] },
        "fields": {
            "$add": [
                { "$cond": [ { "$ifNull": [ "$id", null ] }, 1, 0 ] },
                { "$cond": [ { "$ifNull": [ "$name", null ] }, 1, 0 ] },
                { "$cond": [ { "$ifNull": [ "$marks", null ] }, 1, 0 ] },
                { "$cond": [ { "$ifNull": [ "$standard", null ] }, 1, 0 ] },
                { "$cond": [ { "$ifNull": [ "$average", null ] }, 1, 0 ] },
            ]
        }
    }},
    { "$match": { "fields": { "$gte": 3 } } }
])
 

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

$ifNull Оператор выполняет «тяжелую работу», заменяя «несуществующие» null поля or null значением для оценки. Мы также надеемся, что вы могли бы «попробовать» фильтровать с помощью a $match на начальном этапе конвейера, как это было сделано в первом запросе, чтобы уменьшить ввод.

Последняя реальная проблема возникает, если у вас просто ** слишком много * различных комбинаций полей для указания в любой форме, и вам просто нужно знать, что «три» или более ваших полей по существу существуют или не являются нулевыми.

Этот подход сводится к использованию $where form of evaluation, который является наименее эффективным способом обработки общего запроса, но он наиболее гибкий, поскольку код JavaScript может обрабатывать такие ситуации:

 db.students.find(
    function() {
        var count = 0;
        for ( var k in this ) {
            if ( ( k != null) amp;amp; ( k != "_id") ) {
                count  ;
                if ( count >= 3 )
                    break;
            }
        }
        return ( count >= 3 );
    }
)
 

Итак, хотя последние формы «выглядят» просто, на самом деле это довольно ужасно, поскольку нет способа избежать того, что по сути заканчивается как «полное сканирование коллекции», поскольку все поля в каждом документе оцениваются на предмет условий в JavaScript. Ну, по крайней мере, до тех пор, пока не будет достигнуто число «три».

Это дает вам несколько подходов. Надеюсь, первое действительно подходит.