MongoDB查询以选择具有所有元素都匹配某些条件的数组的文档

Posted

技术标签:

【中文标题】MongoDB查询以选择具有所有元素都匹配某些条件的数组的文档【英文标题】:MongoDB query to select documents with array with all of its elements matching some conditions 【发布时间】:2022-01-17 18:45:09 【问题描述】:

我正在尝试在 MongoDB 中提出一个查询,它允许我根据几级深度数组中子文档的内容来选择集合中的文档。

示例(简化)中的集合表示情况。查询的目的是在某个时刻了解当前活动的情况。 conditionGroups 数组表示情况变得活跃的不同条件,每个条件都有一个条件数组,所有这些条件都必须为真。

换句话说,conditionGroups 数组作为 OR 条件运行,其子数组“conditions”作为 AND 运行。因此,给定任何根文档“情况”,如果至少有一个 conditionGroup 满足其所有条件,则该情况将处于活动状态。

[
  
    "name": "Weekdays",
    "conditionGroups": [
      
        "conditions": [
          
            "type": "DayOfWeek",
            "values": [1, 2, 3, 4, 5]
          ,
          
            "type": "HourIni",
            "values": [8]
          ,
          
            "type": "HourEnd",
            "values": [19]
          
        ]
      
    ]
  ,
  
    "name": "Nights and weekends",
    "conditionGroups": [
      
        "conditions": [
          
            "type": "DayOfWeek",
            "values": [1, 2, 3, 4, 5]
          ,
          
            "type": "HourIni",
            "values": [20]
          ,
          
            "type": "HourEnd",
            "values": [23]
          
        ]
      ,
      
        "conditions": [
          
            "type": "DayOfWeek",
            "values": [6, 7]
          ,
          
            "type": "HourIni",
            "values": [8]
          ,
          
            "type": "HourEnd",
            "values": [19]
          
        ]
      
    ]
  ,
  
    "name": "Weekend night",
    "conditionGroups": [
      
        "conditions": [
          
            "type": "DayOfWeek",
            "values": [6, 7]
          ,
          
            "type": "HourIni",
            "values": [20]
          ,
          
            "type": "HourEnd",
            "values": [23]
          
        ]
      
    ]
  
]

另外需要注意的是,还有其他类型的条件,例如 DayOfMonth、Month、Year 和其他可能出现的条件,因此查询应该查找与类型和值匹配或根本不存在的条件。

给定这个示例数据,并假设 12 月的星期一午餐时间(因此 DayOfWeek 为 1,当前时间为 12,DayOfMonth 为 13,Month 为 12,Year 为 2021)只应选择第一个文档,因为它有“conditionGroup”所有条件都匹配当前参数,即使没有指定像 DayOfMonth/Year/Month 这样的参数。重要的是必须满足所有条件。

现在,我尝试了以下方法,但没有成功:

db.situations.find(
  'conditionGroups':  $all: [
    
      $elemMatch:  $nor: [
         'conditions.type': 'HourIni', 'conditions.values.0':  $gt: 12  ,
         'conditions.type': 'HourEnd', 'conditions.values.0':  $lte: 12  ,
         'conditions.type': 'DayOfWeek', 'conditions.values.0':  $nin: [1]  ,
         'conditions.type': 'DayOfMonth', 'conditions.values.0':  $nin: [13]  ,
         'conditions.type': 'Month', 'conditions.values.0':  $nin: [12]  ,
         'conditions.type': 'Year', 'conditions.values.0':  $nin: [2021]  ,
      ]
    
  ] 
)

此查询返回为空。

我尝试过的另一件事是首先使用聚合管道展开条件组,然后在条件上尝试 $elemMatch,但得到奇怪的结果。我的猜测是我不完全理解 $elemMatch 和其他数组运算符,我以某种方式混淆了它们......

这是一个相当棘手的问题......所以我已经简化了它,但一个非常值得赞赏的好处是考虑到除了“类型”和“值”之外的每个条件也可以有一个“逆”布尔属性就像一个“不”,所以这个条件必须被“逆转”。

我花了很多时间试图让它工作,但我现在有点迷失了。我知道这些信息可能还不够,所以如果有人能给我一个提示,我可以在需要时提供额外的信息......

任何提示将不胜感激,因为我很迷茫! ;)

【问题讨论】:

【参考方案1】:

您可以在聚合管道中执行以下操作:

    $unwindconditionGroups 用于未来的处理/过滤 使用$switchcondition 级别执行条件检查。如果条件匹配,则设置结果为true,否则设置结果为false。通过使用$map,您获得了condition 数组的映射布尔结果 $allElementsTrue 检查第2步的结果数组是否全部为真;如果为真,则表示一个条件通过了所有匹配项 使用_id找回所有原始文档的_id
db.collection.aggregate([
  
    "$addFields": 
      "dateInput": ISODate("2021-12-13T12:00:00Z")
    
  ,
  
    "$unwind": "$conditionGroups"
  ,
  
    "$addFields": 
      "matchedCondition": 
        "$map": 
          "input": "$conditionGroups.conditions",
          "as": "c",
          "in": 
            "$switch": 
              "branches": [
                
                  "case": 
                    $and: [
                      
                        $eq: [
                          "$$c.type",
                          "DayOfWeek"
                        ]
                      ,
                      
                        "$in": [
                          
                            "$dayOfWeek": "$dateInput"
                          ,
                          "$$c.values"
                        ]
                      
                    ]
                  ,
                  "then": true
                ,
                
                  "case": 
                    $and: [
                      
                        $eq: [
                          "$$c.type",
                          "HourIni"
                        ]
                      ,
                      
                        "$gt": [
                          
                            "$hour": "$dateInput"
                          ,
                          
                            "$arrayElemAt": [
                              "$$c.values",
                              0
                            ]
                          
                        ]
                      
                    ]
                  ,
                  "then": true
                ,
                
                  "case": 
                    $and: [
                      
                        $eq: [
                          "$$c.type",
                          "HourEnd"
                        ]
                      ,
                      
                        "$lte": [
                          
                            "$hour": "$dateInput"
                          ,
                          
                            "$arrayElemAt": [
                              "$$c.values",
                              0
                            ]
                          
                        ]
                      
                    ]
                  ,
                  "then": true
                
              ],
              default: false
            
          
        
      
    
  ,
  
    "$match": 
      $expr: 
        $eq: [
          true,
          
            "$allElementsTrue": "$matchedCondition"
          
        ]
      
    
  ,
  
    "$group": 
      "_id": "$_id"
    
  ,
  
    "$lookup": 
      "from": "collection",
      "localField": "_id",
      "foreignField": "_id",
      "as": "originalDocument"
    
  ,
  
    "$unwind": "$originalDocument"
  ,
  
    "$replaceRoot": 
      "newRoot": "$originalDocument"
    
  
])

这里是Mongo playground 供您参考。

【讨论】:

以上是关于MongoDB查询以选择具有所有元素都匹配某些条件的数组的文档的主要内容,如果未能解决你的问题,请参考以下文章

MongoDB查询以获取与具有多个值的键匹配的所有文档[重复]

在Java中查询具有完全匹配字段MongoDB的数组元素

如何查询具有基数的表以显示所有 Pk,同时还显示具有 FK 的表中的匹配元素

将 MongoDB 查询转换为雪花

mongodb 查询求助,嵌套数组里面查东西

Hive查询:根据条件选择一列,另一列值匹配某些特定值,然后将匹配结果创建为新列