2016-03-22 2 views
3

Я боролся с этим на некоторое время, так что в погоню:Объектов в Подмассивах запроса фильтра в MongoDB

У меня есть этот объект в базе данных

{ 
    topic: [ 
    { 
     topicName: "Reproduction in plants", 
     subTopic: ["Pollination", "Other Topic"] 
    }, 
    { 
     topicName: "Plant Cycle", 
     subTopic: ["Pollination", "Photosynthesis"] 
    }, 
    ] 
} 

Проблема, которую я пытаюсь решить здесь как сделать запрос, который удовлетворяет следующие условия:

  1. находит все темы, которая в запросе тем
  2. Для каждой темы, отфильтровать результаты с подтемы в теме

Так что давайте говорить, что я хочу, чтобы это из запроса:

  1. Получить темы на «Воспроизведение в растениях», из только «опыления» подтемы
  2. Получить темы на «цикл растений», из только «Фотосинтез» подтемы

В моем текущем растворе:

filterQueries['topic.topicName'] = { $in: ["Reproduction in plants", "Plant Cycle"] }; 
filterQueries['topic.subTopic'] = { $in: ["Photosynthesis", "Pollination"] }; 

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

Как сделать запрос, чтобы сделать то, что я хочу, от условий выше?

Буду признателен за любую помощь.

ответ

1

На самом деле существуют «две» проблемы, которые не рассматриваются при таком условии.

Первый заключается в том, что в качестве отдельных аргументов нет ничего, чтобы сказать, что значения для каждого свойства должны быть в пределах одного и того же элемента, или фактически, если конкретная комбинация действительно появляется в этом элементе. Чтобы исправить, что вы используете $elemMatch условия внутри $or выражения:

var query = { 
    "$or": [ 
     { "topic": { 
      "$elemMatch": { 
       "topicName": "Reproduction in plants", 
       "subTopic": "Pollination" 
      } 
     }}, 
     { "topic": { 
      "$elemMatch": { 
       "topicName": "Plant Cycle", 
       "subTopic": "Photosynthesis" 
      } 
     }} 
    ] 
} 

По крайней мере, выбирает документы с комбинацией в элементы, которые вы требуете.

Но ничего не может «фильтровать» несколько результатов, от внешнего "topic" или «внутреннего» "subTopic" массивов. Для выполнения этой задачи вам необходима рамка агрегации, поскольку такие функции не доступны для базовой проекции:

var pipeline = [ 
    // Still use the same match to filter possible documents 
    { "$match": { 
    "$or": [ 
     { "topic": { 
     "$elemMatch": { 
      "topicName": "Reproduction in plants", 
      "subTopic": "Pollination" 
     } 
     }}, 
     { "topic": { 
     "$elemMatch": { 
      "topicName": "Plant Cycle", 
      "subTopic": "Photosynthesis" 
     } 
     }} 
    ] 
    }}, 

    // Filter the arrays for matches 
    { "$project": { 
    "topics": { 
     "$filter": { 
     "input": { 
      "$map": { 
      "input": "$topic", 
      "as": "topic", 
      "in": { 
       "topicName": "$$topic.topicName", 
       "subTopic": { 
       "$filter": { 
        "input": "$$topic.subTopic", 
        "as": "subTopic", 
        "cond": { 
        "$or": [ 
         { "$and": [ 
         { "$eq": [ "$$topic.topicName", "Reproduction in plants" ] }, 
         { "$eq": [ "$$subTopic", "Pollination" ] } 
         ]}, 
         { "$and": [ 
         { "$eq": [ "$$topic.topicName", "Plant Cycle" ] }, 
         { "$eq": [ "$$subTopic", "Photosynthesis" ] } 
         ]} 
        ] 
        } 
       } 
       } 
      } 
      } 
     }, 
     "as": "topic", 
     "cond": { 
      "$and": [ 
      { "$or": [ 
       { "$eq": [ "$$topic.topicName", "Reproduction in plants" ] }, 
       { "$eq": [ "$$topic.topicName", "Plant Cycle" ] } 
      ]}, 
      { "$ne": [ "$$topic.subTopic", [] ] } 
      ] 
     } 
     } 
    } 
    }} 
]; 

// API call to aggregate 
Model.aggregate(pipeline,function(err,results) { 
    // results in here 
}); 

Thats наиболее оптимального подхода с MongoDB 3.2 с использованием $filter операции на массивах. Поэтому сначала обратите внимание, что внутренние элементы "subTopic" проверяются на соответствие условиям вместе с внешним элементом, чтобы решить, какие из них нужно вернуть.Это помещается в пределах $map, так что «фильтрованное» содержимое возвращается к свойству внешнего массива для дальнейшего изучения.

Внешний массив затем «фильтруется», так что возвращаются только соответствующие значения "topicName" и, конечно, только если массив "subTopic" не был «пустым» в результате фильтрации.

Можно сделать это в более ранних версиях, но типичный процесс с $unwind получает очень долго и дорого:

{ "$unwind": "$topic" }, 
{ "$unwind": "$topic.subTopic" }, 
{ "$match": { 
    "$or": [ 
     { 
      "topic.topicName": "Reproduction in plants", 
      "topic.subTopic": "Pollination" 
     }, 
     { 
      "topic.topicName": "Plant Cycle", 
      "topic.subTopic": "Photosynthesis" 
     } 
    ] 
}}, 
{ "$group": { 
    "_id": { 
     "_id": "$_id", 
     "topicName": "$topic.topicName", 
    }, 
    "subTopic": { "$push": "$topic.subTopic" } 
}}, 
{ "$group": { 
    "_id": "$_id._id", 
    "topic": { 
     "$push": { 
      "topicName": "$_id.topicName", 
      "subTopic": "$_id.subTopic" 
     } 
    } 
}} 

Хотя это выглядит проще следовать, это много дороже из-за природа чего $unwind. И, конечно, каждая добавленная стадия конвейера агрегации имеет свои собственные затраты на обработку, тогда как современная версия может сделать это простым $project.

Лучше всего, если у вас есть более ранняя версия, чтобы использовать начальный «запрос», как указано, используя как $or, так и $elemMatch, а затем выполните фильтрацию массива в коде.

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

В любом случае, результат вы получите:

{ 
    "topic": [ 
    { 
     "topicName": "Reproduction in plants", 
     "subTopic": ["Pollination"] 
    }, 
    { 
     "topicName": "Plant Cycle", 
     "subTopic": ["Photosynthesis"] 
    } 
    ] 
} 

который возвращает только те элементы, соответствующие «отфильтрованные» в любом документе.

+0

Точно ответ, на который я долгое время. Спасибо за разработанный ответ. Я приложу к фильтрации в коде, что также довольно сложно. – minheq

+0

@minheq Как сказано, фильтрация действительно является вспомогательной для фактического первоначального выбора запроса для правильного соответствия на ваших условиях. На самом деле ваши условия в значительной степени определяют, каково должно быть результирующее содержимое массива в любом случае, поэтому проще просто предположить, что это возвращаемые данные, а не манипулировать данными тем, что должно быть заранее заданным результатом. –

+0

Я думаю, что вы правы, я закончил писать алгоритм, но, во-первых, он не очень хорошо разбирается в коде. И я оцениваю, есть ли действительная ценность для бизнеса, чтобы принудительно реализовать такую ​​фильтрацию, которая могла бы замедлить все запросы для «вопросов». Кроме того, маловероятно, что существует одно и то же имя субтипа под двумя разными темами. Запрос должен быть правильным, но я рассматриваю вопрос, будет ли проще просто фильтровать «вычет» в интерфейсе. ** Еще раз спасибо за ваш вклад. ** – minheq

Смежные вопросы