MongoDB - сопоставление нескольких значений в массиве

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

   [{
       name: 'John',
       cars: [1, 2, 3, 4]
   },
   {
       name: 'Jane',
       cars: [1, 2, 3, 8]
   },
   {
       name: 'Smith',
       cars: [1, 8, 10]
   }]

И мы хотим найти документы, имеющие по крайней мере три значения (в автомобилях) в следующем массиве:

   [1, 2, 3, 4, 5, 6, 7]

Тогда результаты будут следующими:

   [{
       name: 'John',
       cars: [1, 2, 3, 4]
   },
   {
       name: 'Jane',
       cars: [1, 2, 3, 8]
   }]

Кто-нибудь знает, как достичь этого?

Ответ 1

Это хороший вопрос, и я не думаю, что есть простой способ сделать это с обычными операторами, которые дает вам MongoDB. Однако я могу придумать следующие способы для этого:

1. Новое поле

Рассчитайте это в коде приложения и сохраните результат в новом поле документа.

2. Грубая сила

db.Collection.find( { $or: [
    { cars: $all [ 1, 2, 3 ] },
    { cars: $all [ 2, 3, 4 ] },
    ... list out all 35 combinations
] } )

3. Используйте $where

db.Collection.find( { cars: { $in: [1,2,3,4,5,6,7] }, $where: function() {
    var numMatches = 0;
    for (var i = 1; i <= 7; i++)
        if (this.cars.indexOf(i) > -1) numMatches++;
    return numMatches >= 3;
} } );

Ответ 2

Вы можете получить запрос $in, а затем по коду фильтровать запись, содержащую 3 или более записей в нужном массиве. (Вот несколько простых python-кода)

def dennisQuestion():
    permissibleCars = [1,2,3,4,5,6,7]
    cursor = db.collection.find({"cars": {"$in": permissibleCars}})
    for record in cursor:
       if len(set(permissible) & set(record["cars"]))) >= 3
          yield record

Ответ 3

Мне пришлось немного изменить @Zaid Masud вариант 3, когда значения, где строки в Mongo 4.0.3:

db.Collection.find( { cars: { $in: ["s1", "s2", "s3" , "s4", "s5" , "s6" , "s7"] },
    $where: function() {
        var options = ["s1", "s2", "s3" , "s4", "s5" , "s6" , "s7"];
        var numMatches = 0;
        for (var i = 0; i < 7; i++)
            if (this.cars.indexOf(options[i]) > -1) 
                numMatches++;
        return numMatches >= 3;
    } 
} );

(Это казалось немного большим для комментария)