MongoDB查找精确数组匹配但顺序无关

25

我正在查询精确匹配的数组并成功检索到它,但当我尝试查找值顺序不同的精确数组时,它会失败。

示例

db.coll.insert({"user":"harsh","hobbies":["1","2","3"]})
db.coll.insert({"user":"kaushik","hobbies":["1","2"]})
db.coll.find({"hobbies":["1","2"]})

第二份文件成功检索

db.coll.find({"hobbies":["2","1"]})

没有显示

请帮忙

7个回答

51
当前被接受的答案并不能保证数组完全匹配,只是保证数组大小相同并且至少有一个项与查询数组共享。

例如,查询如下:

db.coll.find({ "hobbies": { "$size" : 2, "$in": [ "2", "1", "5", "hamburger" ] }  });

在这种情况下,仍会返回用户kaushik。

要进行精确匹配,需要将$size$all结合起来使用,如下所示:

db.coll.find({ "hobbies": { "$size" : 2, "$all": [ "2", "1" ] }  });

请注意,这可能是一个非常昂贵的操作,这取决于您的数据量和结构。 由于MongoDB保持插入数组的顺序稳定,因此在向数据库插入时确保数组按排序顺序排列可能更好,这样在查询时就可以依靠静态顺序。


18
为了精确匹配数组字段,Mongo提供了$eq操作符,它可以像值一样作用于数组。

为了精确匹配数组字段,Mongo提供了$eq操作符,它可以像值一样作用于数组。

db.collection.find({ "hobbies": {$eq: [ "singing", "Music" ] }});

此外,$eq 还检查您指定元素的顺序。

如果使用以下查询:

db.coll.find({ "hobbies": { "$size" : 2, "$all": [ "2", "1" ] }  });

那么将不会返回完全匹配的结果。假设您进行以下查询:

db.coll.find({ "hobbies": { "$size" : 2, "$all": [ "2", "2" ] }  });

该查询将返回所有包含元素2且大小为2的文档(例如,它还将返回具有hobies :[2,1]的文档)。


嗯,你说得对,我不应该在我的查询示例中使用“完全匹配”这个词。你知道如何查询一个确切的值子集(就像使用$eq一样),但不检查顺序的解决方案吗? - kasoban

2
MongoDB可以按照数组元素的顺序或指定顺序精确过滤,而不考虑顺序。来源:https://savecode.net/code/javascript/mongodb+filter+by+exactly+array+elements+without+regard+to+order+or+specified+order
// Insert data
db.inventory.insertMany([
   { item: "journal", qty: 25, tags: ["blank", "red"], dim_cm: [ 14, 21 ] },
   { item: "notebook", qty: 50, tags: ["red", "blank"], dim_cm: [ 14, 21 ] },
   { item: "paper", qty: 100, tags: ["red", "blank", "plain"], dim_cm: [ 14, 21 ] },
   { item: "planner", qty: 75, tags: ["blank", "red"], dim_cm: [ 22.85, 30 ] },
   { item: "postcard", qty: 45, tags: ["blue"], dim_cm: [ 10, 15.25 ] }
]);

// Query 1: filter by exactly array elements without regard to order
db.inventory.find({ "tags": { "$size" : 2, "$all": [ "red", "blank" ] }  });
// result:
[
  {
    _id: ObjectId("6179333c97a0f2eeb98a6e02"),
    item: 'journal',
    qty: 25,
    tags: [ 'blank', 'red' ],
    dim_cm: [ 14, 21 ]
  },
  {
    _id: ObjectId("6179333c97a0f2eeb98a6e03"),
    item: 'notebook',
    qty: 50,
    tags: [ 'red', 'blank' ],
    dim_cm: [ 14, 21 ]
  },
  {
    _id: ObjectId("6179333c97a0f2eeb98a6e05"),
    item: 'planner',
    qty: 75,
    tags: [ 'blank', 'red' ],
    dim_cm: [ 22.85, 30 ]
  }
]

// Query 2: filter by exactly array elements in the specified order
db.inventory.find( { tags: ["blank", "red"] } )
// result:
[
  {
    _id: ObjectId("6179333c97a0f2eeb98a6e02"),
    item: 'journal',
    qty: 25,
    tags: [ 'blank', 'red' ],
    dim_cm: [ 14, 21 ]
  },
  {
    _id: ObjectId("6179333c97a0f2eeb98a6e05"),
    item: 'planner',
    qty: 75,
    tags: [ 'blank', 'red' ],
    dim_cm: [ 22.85, 30 ]
  }
]

// Query 3: filter by an array that contains both the elements without regard to order or other elements in the array
db.inventory.find( { tags: { $all: ["red", "blank"] } } )
// result:
[
  {
    _id: ObjectId("6179333c97a0f2eeb98a6e02"),
    item: 'journal',
    qty: 25,
    tags: [ 'blank', 'red' ],
    dim_cm: [ 14, 21 ]
  },
  {
    _id: ObjectId("6179333c97a0f2eeb98a6e03"),
    item: 'notebook',
    qty: 50,
    tags: [ 'red', 'blank' ],
    dim_cm: [ 14, 21 ]
  },
  {
    _id: ObjectId("6179333c97a0f2eeb98a6e05"),
    item: 'planner',
    qty: 75,
    tags: [ 'blank', 'red' ],
    dim_cm: [ 22.85, 30 ]
  }
]

0

通过使用$all,我们可以实现这一点。 查询:{cast:{$all:["James J. Corbett","George Bickel"]}}

输出:cast:["George Bickel","Emma Carus","George M. Cohan","James J. Corbett"]


0

这个问题比较老了,但是我被提醒了,因为另一个答案表明接受的答案对于包含重复值的数组不足够,所以让我们来解决这个问题。

由于我们在查询方面有根本性的局限性,我们需要避免这些容易出错的数组交集。检查两个数组是否包含相同的一组值的最佳方法是对要比较的两个数组进行排序,然后比较这些数组的排序版本。由于据我所知,MongoDB不支持数组排序,因此我们将需要依靠聚合来模拟我们想要的行为:

// Note: make sure the target_hobbies array is sorted!
var target_hobbies = [1, 2];

db.coll.aggregate([
  { // Limits the initial pipeline size to only possible candidates.
    $match: {
      hobbies: {
        $size: target_hobbies.length,
        $all: target_hobbies
      }
    }
  },
  { // Split the hobbies array into individual array elements.
    $unwind: "$hobbies"
  },
  { // Sort the elements into ascending order (do 'hobbies: -1' for descending).
    $sort: {
      _id: 1,
      hobbies: 1
    }
  },
  { // Insert all of the elements back into their respective arrays.
    $group: {
      _id: "$_id",
      __MY_ROOT: { $first: "$$ROOT" }, // Aids in preserving the other fields.
      hobbies: {
        $push: "$hobbies"
      }
    }
  },
  { // Replaces the root document in the pipeline with the original stored in __MY_ROOT, with the sorted hobbies array applied on top of it.
    // Not strictly necessary, but helpful to have available if desired and much easier than a bunch of 'fieldName: {$first: "$fieldName"}' entries in our $group operation.
    $replaceRoot: {
      newRoot: {
        $mergeObjects: [
          "$__MY_ROOT",
          {
            hobbies: "$hobbies"
          }
        ]
      }
    }
  }
  { // Now that the pipeline contains documents with hobbies arrays in ascending sort order, we can simply perform an exact match using the sorted target_hobbies.
    $match: {
      hobbies: target_hobbies
    }
  }
]);

我无法保证这个查询的性能,如果有太多的初始候选文档,它可能会导致管道变得过大。如果您正在处理大型数据集,则应像当前接受的答案所述那样按排序顺序插入数组元素。通过这样做,您可以执行静态数组匹配,这将更加高效,因为它们可以被正确地索引,并且不会受到聚合框架管道大小限制的限制。但作为权宜之计,这应该确保更高的准确性。


0

使用aggregate,这是我如何使我的代码更加高效和快速的方法:

 db.collection.aggregate([
 {$unwind: "$array"},
 
  {
        
    $match: {
      
      "array.field" : "value"
      
    }
  },

然后,您可以将其展开为平面数组,然后对其进行分组。


0

这个查询将找到任意顺序的精确数组。

let query = {$or: [
{hobbies:{$eq:["1","2"]}},
{hobbies:{$eq:["2","1"]}}
]};

db.coll.find(query)

2
这将不是一个理想的解决方案。使用 $or 子句的组合需要子句数量以 n!(n 的阶乘)的速率增长。即使对于小数组,这也会快速增长(仅 5 个数组元素就需要 120 个单独的子句,而仅 6 个数组元素就需要 720 个!)。这里的旧示例更有效地处理了这个问题,最多只需要运行时复杂度为 O(n^2),而不是 O(n!) - B. Fleming
这与上面的评论中提到的不相同。 - Surendra Babu Parchuru
@B.Fleming 我同意这可能不是最理想的解决方案。但是如果你看看其他答案,这是符合问题要求的一个。你能否提供一个更理想的解决方案? - YulePale
@YulePale 我已经添加了一个适当的答案来回答这个问题,利用MongoDB的聚合框架来确保准确的结果。请考虑使用我概述的聚合框架解决方案,避免生成一个O(n!)$or子句数组。为了方便起见,它可以在这里找到:https://dev59.com/A10a5IYBdhLWcg3w88o0#63915722 - B. Fleming
@B.Fleming 我明白你的意思。唯一的问题是我的代码会查找匹配项,如果不存在就添加一个。所以我正在使用 model.update() 方法。因此,我想我将不得不使用两个查询来避免使用 '$or 方法'。谢谢你的回答。 - YulePale

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接