【问题标题】:MongoDb: $elemMatch with convert string to numberMongoDb:$elemMatch,将字符串转换为数字
【发布时间】:2022-01-23 18:38:30
【问题描述】:

我使用 MongoDB,我有这样一个集合。首先,我选择 tableId = 1 的所有文档,然后按 itemId 对它们进行分组 - 我得到 2 组 2 个文档。总而言之,我需要将这些组留在有此类文件的地方:1)城市=伦敦,2)年份大于或等于某个值。为此,我需要将值从字符串转换为数字。如何做到这一点?

在我下面的查询中,比较年份的部分是错误的。

    [
      {
        "tableId": 1,
        "itemId": 10,
        "name": "City",
        "value": "London"
      },
      {
        "tableId": 1,
        "itemId": 10,
        "name": "StartYear",
        "value": "2017"
      },
      {
        "tableId": 1,
        "itemId": 20,
        "name": "City",
        "value": "Paris"
      },
      {
        "tableId": 1,
        "itemId": 20,
        "name": "StartYear",
        "value": "2018"
      },
      {
        "tableId": 2,
        "itemId": 30,
        "name": "City",
        "value": "Madrid"
      },
      {
        "tableId": 2,
        "itemId": 30,
        "name": "StartYear",
        "value": "2016"
      }
    ]

我的查询是:

    db.collection.aggregate([
      {
        "$match": {
          tableId: 1
        }
      },
      {
        "$group": {
          _id: {
            itemId: "$itemId"
          },
          result: {
            $push: "$$ROOT"
          }
        }
      },
      {
        "$match": {
          $and: [
            {
              "result": {
                $elemMatch: {
                  "name": "City",
                  "value": "London"
                }
              }
            },
            {
              "result": {
                $elemMatch: {
                  $and: [
                    {
                      "name": "StartYear"
                    },
                    {
                      $lte: [
                        {
                          $toDouble: "$value"
                        },
                        2018
                      ]
                    }
                  ]
                }
              }
            }
          ]
        }
      }
    ])

【问题讨论】:

    标签: mongodb mongodb-query


    【解决方案1】:

    查询

    • 如果找到name/value 并且如果找到startYear/value,则进行分组并计数
    • 保留找到的那些
    • 取消设置 2 个临时字段 cityFound,yearFound

    Test code here

    aggregate(
    [{"$match":{"$expr":{"$eq":["$tableId", 1]}}},
     {"$group":
      {"_id":"$itemId",
       "cityFound":
       {"$sum":
        {"$cond":
         [{"$and":
           [{"$eq":["$name", "City"]}, {"$eq":["$value", "London"]}]},
          1, 0]}},
       "yearFound":
       {"$sum":
        {"$cond":
         [{"$and":
           [{"$eq":["$name", "StartYear"]},
            {"$lte":[{"$toInt":"$value"}, 2018]}]},
          1, 0]}},
       "result":{"$push":"$$ROOT"}}},
     {"$match":
      {"$expr":
       {"$and":[{"$gt":["$cityFound", 0]}, {"$gt":["$yearFound", 0]}]}}},
     {"$unset":["cityFound", "yearFound"]}])
    

    上面的速度很快,因为它在分组时进行检查。

    要将字符串转换为数字,我们可以使用$toInttoDouble。 但这些是聚合运算符,匹配时它们应该在 $expr 之下。

    您的查询的问题是您在查询运算符中使用聚合表达式,这不起作用,例如$value 是聚合表达式,$toDouble 也不能同时使用。

    您可以使用允许内部聚合表达式的$filter,而不是elem-match,并检查过滤器数组是否为空。

    【讨论】:

      【解决方案2】:

      您可以先使用$map 进行条件映射,将年份转换为数值。然后执行您的$elemMatch 标准。

      db.collection.aggregate([
        {
          "$match": {
            tableId: 1
          }
        },
        {
          "$addFields": {
            "value": {
              "$cond": {
                "if": {
                  $eq: [
                    "$name",
                    "StartYear"
                  ]
                },
                "then": {
                  $toInt: "$value"
                },
                "else": "$value"
              }
            }
          }
        },
        {
          "$group": {
            _id: {
              itemId: "$itemId"
            },
            result: {
              $push: "$$ROOT"
            }
          }
        },
        {
          "$match": {
            $and: [
              {
                "result": {
                  $elemMatch: {
                    "name": "City",
                    "value": "London"
                  }
                }
              },
              {
                "result": {
                  $elemMatch: {
                    "name": "StartYear",
                    "value": {
                      $lte: 2018
                    }
                  }
                }
              }
            ]
          }
        }
      ])
      

      这是Mongo playground 供您参考。

      【讨论】:

      • 非常适合这个系列。但我的真实数据更复杂:许多不同的字段、层次结构。地图将如何运作?
      • @egeo 更新了解决方案,将条件转换放在更早的阶段,以避免构造大的$map 内容
      • 一个很好的改变。一个小缺点:在数据库中,值以字符串形式存储,并以数字形式返回。这对我来说很好,但其他人可能想查看原始数据类型。
      • @egeo 请参阅使用辅助字段进行转换的this version。但是,如果可能的话,我建议将值存储在适当的数据类型中,以避免查询中的这种转换开销。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-08-21
      • 1970-01-01
      • 2015-10-06
      • 1970-01-01
      • 1970-01-01
      • 2021-11-09
      相关资源
      最近更新 更多