2015-05-11 3 views
1

У меня есть коллекция MongoDB со структурой типаMongoDB: найти поддокумент экземпляры с аналогичной датой

[ 
    { 
    name: "name1", 
    instances: [{value:1, score:2, date:<ISODate>}, 
       {value:2, score:5, date:<ISODate>}, 
       {value:2.5, score:9, date:<ISODate>}, 
       ...] 
    }, 
    { 
    name: "name2", 
    instances: [{value:6, score:3, date:<ISODate>}, 
       {value:1, score:6, date:<ISODate>}, 
       {value:3.7, score:5.2, date:<ISODate>}, 
       ...] 
    }, 
    ... 
] 

Я хочу, чтобы найти, если есть два (или более) экземпляров одного и те же name, где даты экземпляра становятся от в тот же день, и вернуть эти экземпляры.

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

Я пробовал агрегирование и группировку по дате, но не мог понять, как сравнивать только день (а не всю дату).

ответ

4

Предположим, у вас есть следующие тестовые документы, вставленные в тестовой коллекции для демонстрационных целей:

db.test.insert([ 
{ 
    "name" : "name1", 
    "instances" : [ 
     { 
      "value" : 1, 
      "score" : 2, 
      "date" : ISODate("2015-03-04T00:00:00.000Z") 
     }, 
     { 
      "value" : 2, 
      "score" : 5, 
      "date" : ISODate("2015-04-01T00:00:00.000Z") 
     }, 
     { 
      "value" : 2.5, 
      "score" : 9, 
      "date" : ISODate("2015-03-05T00:00:00.000Z") 
     } 
    ] 
}, 
{ 
    "name" : "name2", 
    "instances" : [ 
     { 
      "value" : 6, 
      "score" : 3, 
      "date" : ISODate("2015-03-05T00:00:00.000Z") 
     }, 
     { 
      "value" : 1, 
      "score" : 6, 
      "date" : ISODate("2015-03-04T00:00:00.000Z") 
     }, 
     { 
      "value" : 3.7, 
      "score" : 5.2, 
      "date" : ISODate("2015-02-04T00:00:00.000Z") 
     } 
    ] 
}, 
{ 
    "name" : "name1", 
    "instances" : [ 
     { 
      "value" : 6, 
      "score" : 3, 
      "date" : ISODate("2015-03-05T00:00:00.000Z") 
     }, 
     { 
      "value" : 1, 
      "score" : 6, 
      "date" : ISODate("2015-03-04T00:00:00.000Z") 
     }, 
     { 
      "value" : 3.7, 
      "score" : 5.2, 
      "date" : ISODate("2015-02-04T00:00:00.000Z") 
     } 
    ] 
} 
]) 

тогда следующая агрегация будет делать работу:

var pipeline = aggregate([ 
    { 
     "$unwind": "$instances" 
    }, 
    { 
     "$group": { 
      "_id": { 
       "name": "$name", 
       "year": { 
        "$year": "$instances.date" 
       }, 
       "month": { 
        "$month": "$instances.date" 
       }, 
       "day": { 
        "$dayOfYear": "$instances.date" 
       } 
      }, 
      "count": { 
       "$sum": 1 
      }, 
      "data": { 
       "$addToSet": "$$ROOT" 
      } 
     } 
    }, 
    { 
     "$match": { 
      "count": { 
       "$gt": 1 
      } 
     } 
    }, 
    { 
     "$unwind": "$data" 
    }, 
    { 
     "$group": { 
      "_id": { 
       "name": "$data.name", 
       "_id": "$data._id" 
      } 
     } 
    }, 
    { 
     "$project": { 
      "_id": "$_id._id", 
      "name": "$_id.name" 
     } 
    } 
]); 
db.test.aggregate(pipeline); 

Выход:

/* 0 */ 
{ 
    "result" : [ 
     { 
      "_id" : ObjectId("55506d0a180e849972939056"), 
      "name" : "name1" 
     }, 
     { 
      "_id" : ObjectId("55506d0a180e849972939058"), 
      "name" : "name1" 
     } 
    ], 
    "ok" : 1 
} 

выше трубопровод агрегации имеет $unwind операцию в качестве первого шага, который разбирает поле instances массива из входных документов для вывода документа для каждого элемента. Каждый выходной документ заменяет массив значением элемента.

Следующий этап трубопроводы $group группы документов по "name", "instances.date" полеям (поле даты разделяются на три поля, используя Date Aggregation Operators), вычисляет count поля для каждой группы, и выводит документ для каждого уникального name и date (до дня часть). В группе data имеется дополнительное поле массива, которое использует системную переменную $$ROOT для хранения исходного корневого документа, то есть документа верхнего уровня, который в настоящее время обрабатывается на этапе конвейерной агрегирования. Этот корневой документ добавляется в массив с помощью оператора массива $addToSet.

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

Другой $unwind операция затем наносят на data поле, чтобы извлечь реальную _id и name дубликатов, которые будут сгруппированы снова для дальнейшего упорядочения документов.

Дополнительный этап $project будет необходим для формирования вашей окончательной структуры документов путем изменения полей.

использовать результат агрегации курсор на то итерации по результатам с помощью метода forEach() и удалить другие повторяющиеся документы:

var cur = db.test.aggregate(pipeline); 
cur.forEach(function (doc){ 
    var count = 0; 
    if (count != 0){ 
     db.test.remove({"_id": doc._id}); 
    } 
    count++; 
}); 

Другим вариантом является следующее: $out оператора в качестве конечной стадии трубопровода, который записывает документы возвращенный конвейером агрегации в указанную коллекцию, которую вы затем можете запросить и выполните удаления:

var cur = db.outputcollection.find(); 
cur.forEach(function (doc){ 
    var count = 0; 
    if (count != 0){ 
     db.test.remove({"_id": doc._id}); 
    } 
    count++; 
}); 
+2

Претензии для обозначения '$ addToSet': я просто пропустил эту часть. Но почему бы просто не использовать '' $ addToSet ":" $ _id "'? Есть ли что-то очевидное, что я тоже там пропустил? –

+1

@SylvainLeroux Это отличное предложение, хотя я не буду испытывать соблазн включить только поле '$ _id' еще в том случае, если OP может понадобиться другие поля, а также дальше по конвейеру, но да, просто для получения дубликатов, которые я делаю согласитесь, достаточно только поля '_id'. Спасибо, что заметили! – chridam

+1

Большое спасибо вам обоим. Я многому научился. – yarons

3

Если я это хорошо понимаю, вы должны $unwind, а затем $group по дате и инстанции, filtering out группы только одного документа. Нечто подобное (я не имею доступа к MongoDB прямо сейчас - остерегайтесь опечаток):

db.coll.aggregate([ 
    {$unwind: "$instances"}, 
    {$group: { _id: { name:"$name", day:{$dayOfYear:"$date"}, year:{$year:"$date"}}, count: {$sum: 1} }}, 
    {$match: {count: {$gt: 1}}} 
]) 
Смежные вопросы