【问题标题】:Find duplicate inside array without $unwind在没有 $unwind 的数组中查找重复项
【发布时间】:2019-03-21 05:37:33
【问题描述】:

我有以下用户收藏

[{
    "_id": 1,
    "adds": ["111", "222", "333", "111"]
}, {
    "_id": 2,
    "adds": ["555", "666", "777", "555"]
}, {
    "_id": 3,
    "adds": ["888", "999", "000", "888"]
}]

我需要在 adds 数组中找到重复项

预期的输出应该是

[{
    "_id": 1,
    "adds": ["111"]
}, {
    "_id": 2,
    "adds": [ "555"]
}, {
    "_id": 3,
    "adds": ["888"]
}]

我尝试过使用许多运算符$setUnion$setDifference,但都没有成功。

请帮忙!!!

【问题讨论】:

  • 你为什么要避开$unwind? $unwind/$group/$match/$project 将是与迄今为止发布的答案进行比较的直接方法。另外,您使用的是哪个特定版本的 MongoDB 服务器?
  • @Stennie 我正在使用最新的 mongodb 4.0。因为$unwind 产生了一些性能问题。在数组中查找重复项并不是什么大问题。应该有一些聚合运算符吗?不是吗?顺便说一句,感谢您的回复。
  • @Stennie 有 cmets 吗? ;-)
  • 在 MongoDB 4.0 中,没有用于过滤数组以仅查找重复项的简写聚合运算符。有几种不同的方法可以使用现有的运算符来实现此结果,但您必须进行基准测试以比较您的用例的性能。如果这是一种常见需求,您可能需要考虑调整数据模型以提高查询效率。例如,您可以拥有一个对象数组,而不是一个值数组,这些对象的计数在添加到数组时会增加:[{"111": 2, "222": 1, "333": 1}]
  • 您还可以在MongoDB Jira issue tracker(项目:“SERVER”,组件:“Aggregation Framework”)中提出新运算符的功能建议。这在短期内不太可能对您有所帮助,但如果其他人对相同的功能感兴趣,它可能会出现在 MongoDB 的未来版本中。

标签: node.js mongodb mongoose aggregation-framework


【解决方案1】:

您可以使用$range 生成从1n 的数字数组,其中n$sizeadds。然后你可以“循环”通过这些数字并检查addsindex$arrayElemAt)是否存在于index之前的某个地方,如果是,那么它应该被视为重复。您可以使用$indexOfArray 检查指定0 和index 作为搜索范围的数组中是否存在元素。

那么你只需要使用$project$map 将索引替换为实际元素。您还可以添加$setUnion 以避免在最终结果集中出现重复的重复项。

db.users.aggregate([
    {
        $addFields: {
            duplicates: {
                $filter: {
                    input: { $range: [ 1, { $size: "$adds" } ] },
                    as: "index",
                    cond: {
                        $ne: [ { $indexOfArray: [ "$adds", { $arrayElemAt: [ "$adds", "$$index" ]  }, 0, "$$index" ] }, -1 ]
                    }
                }
            }
        }
    },
    {
        $project: {
            _id: 1,
            adds: {
                $setUnion: [ { $map: { input: "$duplicates", as: "d", in: { $arrayElemAt: [ "$adds", "$$d" ] } } }, [] ]
            }
        }
    }
])

打印:

{ "_id" : 1, "adds" : [ "111" ] }
{ "_id" : 2, "adds" : [ "555" ] }
{ "_id" : 3, "adds" : [ "888" ] }

【讨论】:

  • 很棒很棒很棒。很棒的米克尔
  • @mickl - 当您有多个重复值时,它似乎不起作用。例如“添加”:[“111”、“222”、“333”、“333”、“111”]
  • 请注意,您不需要$slice,因为$indexOfArray 支持startIndex参数。另外$addFields$project这两个stage可以合并为一个。
  • 修改了我的答案,谢谢@dnickless,这使它更短一些。我仍然会保留两个阶段,因为我相信它比嵌套映射/过滤器更具可读性
【解决方案2】:

以下是您可能想要比较性能的另一个版本:

db.users.aggregate({
  $project:{
    "adds":{
      $reduce:{
        "input":{$range:[0,{$size:"$adds"}]}, // loop variable from 0 to max. index of $adds array
      //"input":{$range:[0,{$subtract:[{$size:"$adds"},1]}]}, // this would be enough but looks more complicated
        "initialValue":[],
        "in":{
            $let:{
              "vars":{
                "curr": { $arrayElemAt: [ "$adds", "$$this"] } // the element we're looking at
              },
              "in":{
                // if there is another identical element after the current one then we have a duplicate
                $cond:[
                  {$ne:[{$indexOfArray:["$adds","$$curr",{$add:["$$this",1]}]},-1]},
                  {$setUnion:["$$value",["$$curr"]]}, // combine duplicates found so far with new duplicate
                  "$$value" // continue with current value
                ]
              }
            }
        }
      }
    }
  }
})

该逻辑基于我们通过$range 运算符获得的循环变量。此循环变量允许对adds 数组进行顺序访问。对于我们查看的每个项目,我们检查是否在当前索引之后 有另一个相同的项目。如果是,我们有一个副本,否则没有。

【讨论】:

    【解决方案3】:

    您可以尝试以下聚合。这个想法是收集不同的值并迭代值并检查该值是否存在于adds数组中;如果存在则保留该值,否则忽略该值。

    db.users.aggregate({
      "$project":{
        "adds":{
          "$reduce":{
            "input":{"$setUnion":["$adds",[]]},
            "initialValue":[],
            "in":{
              "$concatArrays":[
                "$$value",
                {"$let":{
                  "vars":{
                    "match":{
                      "$filter":{"input":"$adds","as":"a","cond":{"$eq":["$$a","$$this"]}}
                    }},
                    "in":{
                      "$cond":[{"$gt":[{"$size":"$$match"},1]},["$$this"],[]]
                    }
                }}
              ]
            }
          }
        }
      }
    })
    

    【讨论】:

    • 这可能会更快:db.users.aggregate({ "$project":{ "adds":{ "$reduce":{ "input":"$adds", "initialValue":[], "in":{ "$let":{ "vars":{ "match":{ "$filter":{"input":"$adds","as":"a","cond":{"$eq":["$$a","$$this"]}} }}, "in":{ "$cond":[{"$gt":[{"$size":"$$match"},1]},{"$setUnion":["$$value",["$$this"]]},"$$value"] } } } } } } })
    猜你喜欢
    • 2018-05-16
    • 2016-06-23
    • 2018-09-05
    • 2021-12-10
    相关资源
    最近更新 更多