Подтвердить что ты не робот

Как использовать агенты агрегации в $match в MongoDB (например, $year или $dayOfMonth)?

У меня есть коллекция, полная документов с атрибутом created_date. Я бы хотел отправить эти документы через конвейер агрегации, чтобы выполнить некоторую работу над ними. В идеале я хотел бы отфильтровать их с помощью сопоставления $, прежде чем выполнять какую-либо другую работу над ними, чтобы я мог использовать индексы, но я не могу понять, как использовать новые операторы $year/$month/$dayOfMonth в моем $match.

Есть несколько примеров, связанных с использованием операторов в операции $project, но я обеспокоен тем, что, поставив $project в качестве первого шага в моем конвейере, я потерял доступ к моим индексам (MongoDB документация указывает, что первое выражение должно быть $match, чтобы использовать преимущества индексов).

Пример данных:

{
    post_body: 'This is the body of test post 1',
    created_date: ISODate('2012-09-29T05:23:41Z')
    comments: 48
}
{
    post_body: 'This is the body of test post 2',
    created_date: ISODate('2012-09-24T12:34:13Z')
    comments: 10
}
{
    post_body: 'This is the body of test post 3',
    created_date: ISODate('2012-08-16T12:34:13Z')
    comments: 10
}

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

{
    aggregate: 'posts',
    pipeline: [
         {$match:
             /*Can I use the $year/$month operators here to match Sept 2012?
             $year:created_date : 2012,
             $month:created_date : 9
             */
             /*or does this have to be 
             created_date : 
                  {$gte:{$date:'2012-09-01T04:00:00Z'}, 
                  $lt: {$date:'2012-10-01T04:00:00Z'} }
             */
         },
         {$group:
             {_id: '0',
              totalComments:{$sum:'$comments'}
             }
          }
    ]
 }

Это работает, но совпадение теряет доступ к любым индексам для более сложных запросов:

{
    aggregate: 'posts',
    pipeline: [
         {$project:
              {
                   month : {$month:'$created_date'},
                   year : {$year:'$created_date'}
              }
         },
         {$match:
              {
                   month:9,
                   year: 2012
               }
         },
         {$group:
             {_id: '0',
              totalComments:{$sum:'$comments'}
             }
          }
    ]
 }
4b9b3361

Ответ 1

Как вы уже нашли, вы не можете сопоставлять $в полях, которые не находятся в документе (он работает точно так же, как и поиск), и если вы сначала используете $project, вы потеряете возможность использовать индексы.

Вместо этого вы можете комбинировать свои усилия следующим образом:

{
    aggregate: 'posts',
    pipeline: [
         {$match: {
             created_date : 
                  {$gte:{$date:'2012-09-01T04:00:00Z'}, 
                  $lt:  {date:'2012-10-01T04:00:00Z'} 
                  }}
             }
         },
         {$group:
             {_id: '0',
              totalComments:{$sum:'$comments'}
             }
          }
    ]
 }

Вышеуказанное только дает вам агрегацию за сентябрь, если вы хотите агрегировать в течение нескольких месяцев, вы можете, например:

{
    aggregate: 'posts',
    pipeline: [
         {$match: {
             created_date : 
                  { $gte:'2012-07-01T04:00:00Z', 
                    $lt: '2012-10-01T04:00:00Z'
                  }
         },
         {$project: {
              comments: 1,
              new_created: {
                        "yr" : {"$year" : "$created_date"},
                        "mo" : {"$month" : "$created_date"}
                     }
              }
         },
         {$group:
             {_id: "$new_created",
              totalComments:{$sum:'$comments'}
             }
          }
    ]
 }

и вы получите что-то вроде:

{
    "result" : [
        {
            "_id" : {
                "yr" : 2012,
                "mo" : 7
            },
            "totalComments" : 5
        },
        {
            "_id" : {
                "yr" : 2012,
                "mo" : 8
            },
            "totalComments" : 19
        },
        {
            "_id" : {
                "yr" : 2012,
                "mo" : 9
            },
            "totalComments" : 21
        }
    ],
    "ok" : 1
}

Ответ 2

Попробуйте это;

db.createCollection("so");
db.so.remove();
db.so.insert([
{
    post_body: 'This is the body of test post 1',
    created_date: ISODate('2012-09-29T05:23:41Z'),
    comments: 48
},
{
    post_body: 'This is the body of test post 2',
    created_date: ISODate('2012-09-24T12:34:13Z'),
    comments: 10
},
{
    post_body: 'This is the body of test post 3',
    created_date: ISODate('2012-08-16T12:34:13Z'),
    comments: 10
}
]);
//db.so.find();

db.so.ensureIndex({"created_date":1});
db.runCommand({
    aggregate:"so",
    pipeline:[
        {
            $match: { // filter only those posts in september
                created_date: { $gte: ISODate('2012-09-01'), $lt: ISODate('2012-10-01') }
            }
        },
        {
            $group: {
                _id: null, // no shared key
                comments: { $sum: "$comments" } // total comments for all the posts in the pipeline
            }
        },
]
//,explain:true
});

Результат:

{ "result" : [ { "_id" : null, "comments" : 58 } ], "ok" : 1 }

Таким образом, вы также можете изменить свой предыдущий пример, хотя я не уверен, зачем вам это нужно, если вы не планируете делать что-то еще с месяцем и годом в конвейере;

{
    aggregate: 'posts',
    pipeline: [
     {$match: { created_date: { $gte: ISODate('2012-09-01'), $lt: ISODate('2012-10-01') } } },
     {$project:
          {
               month : {$month:'$created_date'},
               year : {$year:'$created_date'}
          }
     },
     {$match:
          {
               month:9,
               year: 2012
           }
     },
     {$group:
         {_id: '0',
          totalComments:{$sum:'$comments'}
         }
      }
    ]
 }

Ответ 3

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

  • match - это этап фильтрации, аналогичный find.
  • project
  • sort
  • skip
  • limit

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


db.companies.aggregate([{
  $match: {
    founded_year: 2004
  }
}, ])

Введем стадию проекта в этот конвейер агрегации:


db.companies.aggregate([{
  $match: {
    founded_year: 2004
  }
}, {
  $project: {
    _id: 0,
    name: 1,
    founded_year: 1
  }
}])

Мы используем метод aggregate для реализации структуры агрегации. Конвейеры агрегации - всего лишь массив документов. В каждом документе должен быть определен конкретный оператор этапа. Итак, в приведенном выше случае у нас есть конвейер агрегации с этапами два. Стадия $match передает документы по одному на этап $project.

Допустим до этапа limit:


db.companies.aggregate([{
  $match: {
    founded_year: 2004
  }
}, {
  $limit: 5
}, {
  $project: {
    _id: 0,
    name: 1
  }
}])

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


db.companies.aggregate([{
  $match: {
    founded_year: 2004
  }
}, {
  $project: {
    _id: 0,
    name: 1
  }
}, {
  $limit: 5
}])

Получает сопоставление документов и проектов с большим количеством документов и, наконец, ограничивает пять. Таким образом, проекция работает с большим количеством документов и, наконец, ограничивает 5. Это дает нам урок, что мы должны ограничить документы теми, которые являются абсолютно необходимыми для перехода на следующий этап. Теперь посмотрим на этап sort:


db.companies.aggregate([{
  $match: {
    founded_year: 2004
  }
}, {
  $sort: {
    name: 1
  }
}, {
  $limit: 5
}, {
  $project: {
    _id: 0,
    name: 1
  }
}])

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


db.companies.aggregate([{
  $match: {
    founded_year: 2004
  }
}, {
  $limit: 5
}, {
  $sort: {
    name: 1
  }
}, {
  $project: {
    _id: 0,
    name: 1
  }
}])

Это займет первые 5 документы и отсортирует их. Добавьте этап skip:


db.companies.aggregate([{
  $match: {
    founded_year: 2004
  }
}, {
  $sort: {
    name: 1
  }
}, {
  $skip: 10
}, {
  $limit: 5
}, {
  $project: {
    _id: 0,
    name: 1
  }
}, ])

Это отсортирует документы все и пропустит исходные документы 10 и вернется к нам. Мы должны попытаться включить стадии $match как можно раньше в конвейер. Чтобы фильтровать документы с использованием этапа $match, мы используем тот же синтаксис для построения документов запроса (фильтров), как и для find().