Truy vấn tài liệu có kích thước mảng lớn hơn 1


663

Tôi có một bộ sưu tập MongoDB với các tài liệu ở định dạng sau:

{
  "_id" : ObjectId("4e8ae86d08101908e1000001"),
  "name" : ["Name"],
  "zipcode" : ["2223"]
}
{
  "_id" : ObjectId("4e8ae86d08101908e1000002"),
  "name" : ["Another ", "Name"],
  "zipcode" : ["2224"]
}

Hiện tại tôi có thể nhận tài liệu phù hợp với kích thước mảng cụ thể:

db.accommodations.find({ name : { $size : 2 }})

Điều này trả về chính xác các tài liệu với 2 phần tử trong namemảng. Tuy nhiên, tôi không thể thực hiện $gtlệnh trả về tất cả các tài liệu trong đó nametrường có kích thước mảng lớn hơn 2:

db.accommodations.find({ name : { $size: { $gt : 1 } }})

Làm cách nào tôi có thể chọn tất cả các tài liệu có một namemảng có kích thước lớn hơn một (tốt nhất là không phải sửa đổi cấu trúc dữ liệu hiện tại)?


3
Các phiên bản mới hơn của MongoDB có toán tử $ size; bạn nên xem câu trả lời của @ tobia
AlbertEngelB

4
Giải pháp thực tế: FooArray: {$ gt: {$ size: 'length'}} -> chiều dài có thể là bất kỳ số nào
Sergi Nadal

Câu trả lời:


489

Cập nhật:

Đối với phiên bản mongodb 2.2+ cách hiệu quả hơn để làm điều này được mô tả bởi @JohnnyHK trong một câu trả lời khác .


1.Sử dụng $ ở đâu

db.accommodations.find( { $where: "this.name.length > 1" } );

Nhưng...

Javascript thực thi chậm hơn các toán tử gốc được liệt kê trên trang này, nhưng rất linh hoạt. Xem trang xử lý phía máy chủ để biết thêm thông tin.

2. Tạo trường bổ sungNamesArrayLength , cập nhật nó với độ dài mảng tên và sau đó sử dụng trong các truy vấn:

db.accommodations.find({"NamesArrayLength": {$gt: 1} });

Nó sẽ là giải pháp tốt hơn, và sẽ hoạt động nhanh hơn nhiều (bạn có thể tạo chỉ mục trên đó).


4
Tuyệt vời, đó là hoàn hảo cảm ơn bạn. Mặc dù tôi thực sự có một số tài liệu không có tên nên đã phải sửa đổi truy vấn thành: db.acc Itemations.find ({$ where: "if (this.name && this.name.length> 1) {trả lại cái này ;} "});
emson

bạn được chào đón, vâng, bạn có thể sử dụng bất kỳ javascript nào $where, nó rất linh hoạt.
Andrew Orsich

8
@emson Tôi nghĩ sẽ nhanh hơn khi làm một cái gì đó như {"name": {$ tồn tại: 1}, $ trong đó: "this.name.lenght> 1"} ... giảm thiểu phần trong truy vấn javascript chậm hơn. Tôi giả định rằng hoạt động và $ tồn tại sẽ có quyền ưu tiên cao hơn.
nairbv

1
Tôi không biết bạn có thể nhúng javascript vào truy vấn, json có thể cồng kềnh. Nhiều truy vấn trong số này chỉ được nhập bằng tay một lần nên không yêu cầu tối ưu hóa. Tôi sẽ sử dụng thủ thuật này thường xuyên +1
pferrel 20/03/2016

3
Sau khi thêm / xóa các phần tử khỏi Mảng, chúng ta cần cập nhật số lượng "NamesArrayLpm". Điều này có thể được thực hiện trong một truy vấn không? Hoặc nó yêu cầu 2 truy vấn, một để cập nhật mảng và một truy vấn để cập nhật số lượng?
WarLord

1325

Có một cách hiệu quả hơn để làm điều này trong MongoDB 2.2+ bây giờ là bạn có thể sử dụng các chỉ mục mảng số trong các khóa đối tượng truy vấn.

// Find all docs that have at least two name array elements.
db.accommodations.find({'name.1': {$exists: true}})

Bạn có thể hỗ trợ truy vấn này với một chỉ mục sử dụng biểu thức bộ lọc một phần (yêu cầu 3.2+):

// index for at least two name array elements
db.accommodations.createIndex(
    {'name.1': 1},
    {partialFilterExpression: {'name.1': {$exists: true}}}
);

16
Ai đó có thể vui lòng giải thích làm thế nào để lập chỉ mục này.
Ben

26
Tôi thực sự ấn tượng với hiệu quả của nó và cũng như cách bạn "nghĩ ra" để tìm giải pháp này. Điều này cũng hoạt động trên 2.6.
earthmeLon

2
Hoạt động trên 3.0. Cảm ơn bạn rất nhiều vì đã tìm thấy điều này.
pikanezi

1
@ Không có sự khác biệt, thực sự : {'Name Field.1': {$exists: true}}.
JohnnyHK

9
@JoseRicardoBustosM. Điều đó sẽ tìm thấy các tài liệu namechứa ít nhất 1 phần tử, nhưng OP đã tìm kiếm nhiều hơn 1.
JohnnyHK

127

Tôi tin rằng đây là truy vấn nhanh nhất trả lời câu hỏi của bạn, vì nó không sử dụng $wheremệnh đề diễn giải :

{$nor: [
    {name: {$exists: false}},
    {name: {$size: 0}},
    {name: {$size: 1}}
]}

Nó có nghĩa là "tất cả các tài liệu ngoại trừ những tài liệu không có tên (không tồn tại hoặc mảng trống) hoặc chỉ có một tên."

Kiểm tra:

> db.test.save({})
> db.test.save({name: []})
> db.test.save({name: ['George']})
> db.test.save({name: ['George', 'Raymond']})
> db.test.save({name: ['George', 'Raymond', 'Richard']})
> db.test.save({name: ['George', 'Raymond', 'Richard', 'Martin']})
> db.test.find({$nor: [{name: {$exists: false}}, {name: {$size: 0}}, {name: {$size: 1}}]})
{ "_id" : ObjectId("511907e3fb13145a3d2e225b"), "name" : [ "George", "Raymond" ] }
{ "_id" : ObjectId("511907e3fb13145a3d2e225c"), "name" : [ "George", "Raymond", "Richard" ] }
{ "_id" : ObjectId("511907e3fb13145a3d2e225d"), "name" : [ "George", "Raymond", "Richard", "Martin" ] }
>

9
@viren Tôi không biết. Điều này chắc chắn tốt hơn các giải pháp Javascript, nhưng đối với MongoDB mới hơn, có lẽ bạn nên sử dụng{'name.1': {$exists: true}}
Tobia

@Tobia lần sử dụng đầu tiên của tôi là $ chỉ tồn tại nhưng nó thực sự sử dụng quét toàn bộ bảng nên rất chậm. db.test.find ({"name": "abc", "d.5": {$ tồn tại: true}, "d.6": {$ tồn tại: true}}) "nReturned": 46525, "execTimeMillis ": 167289," TotalKeysExamined ": 10990840," TotalDocsExamined ": 10990840," inputStage ": {" giai đoạn ":" IXSCAN "," keyPotype ": {" name ": 1," d ": 1}," indexName " : "name_1_d_1", "direction": "về phía trước", "indexBound": {"name": ["[\" abc \ ", \" abc \ "]"], "d": ["[MinKey, MaxKey ] "]}} Nếu bạn thấy nó quét toàn bộ bảng.

Sẽ rất tuyệt nếu cập nhật câu trả lời để đề xuất các lựa chọn thay thế khác (như 'name.1': {$exists: true}}, và cũng bởi vì đây là mã hóa cứng cho "1" và không mở rộng theo chiều dài mảng tối thiểu tùy ý hoặc tham số.
Dan Dascalescu 20/07/18

1
Điều này có thể nhanh nhưng sụp đổ nếu bạn đang tìm kiếm danh sách> N, trong đó N không nhỏ.
Đồi Brandon

62

Bạn cũng có thể sử dụng tổng hợp:

db.accommodations.aggregate(
[
     {$project: {_id:1, name:1, zipcode:1, 
                 size_of_name: {$size: "$name"}
                }
     },
     {$match: {"size_of_name": {$gt: 1}}}
])

// bạn thêm "size_of_name" vào tài liệu chuyển tuyến và sử dụng nó để lọc kích thước của tên


Giải pháp này là chung nhất, cùng với @ JohnnyHK vì nó có thể được sử dụng cho bất kỳ kích thước mảng nào.
arun

Nếu tôi muốn sử dụng "size_of_name" bên trong phép chiếu thì tôi phải làm thế nào ?? Trên thực tế tôi muốn sử dụng $ lát trong phép chiếu trong đó giá trị của nó bằng $ lát: [0, "size_of_name" - bỏ qua] ??
Bò tót Sudhanshu

44

Hãy thử làm một cái gì đó như thế này:

db.getCollection('collectionName').find({'ArrayName.1': {$exists: true}})

1 là số, nếu bạn muốn tìm nạp bản ghi lớn hơn 50 thì hãy thực hiện ArrayName.50 Cảm ơn.


2
Câu trả lời tương tự đã được đưa ra ba năm trước đó .
Dan Dascalescu

Tôi đến từ tương lai và sẽ đánh giá cao điều này: Giải pháp này hoạt động bằng cách kiểm tra xem một yếu tố có tồn tại trên vị trí đã nói hay không. Do đó, bộ sưu tập phải lớn hơn | bằng số đó.
MarAvFe

chúng ta có thể đặt một số số động như "ArrayName. <some_num>" trong truy vấn không?
Sahil Mahajan

Có bạn có thể sử dụng bất kỳ số nào. Nếu bạn muốn tìm nạp bản ghi lớn hơn N thì truyền n.
Aman Goel


26

Bạn có thể sử dụng $ expr (toán tử phiên bản 3.6 mongo) để sử dụng các hàm tổng hợp trong truy vấn thông thường.

So sánh query operatorsvới aggregation comparison operators.

db.accommodations.find({$expr:{$gt:[{$size:"$name"}, 1]}})

Làm thế nào bạn sẽ vượt qua thay vì $namemột mảng là một văn bản con, ví dụ như trong một bản ghi "người" , passport.stamps? Tôi đã thử các kết hợp trích dẫn khác nhau nhưng tôi nhận được "The argument to $size must be an array, but was of type: string/missing".
Dan Dascalescu

3
@DanDascalescu Có vẻ như tem không có trong tất cả các tài liệu. Bạn có thể sử dụng ifNull để xuất mảng trống khi không có tem. Một cái gì đó giống nhưdb.col.find({$expr:{$gt:[{$size:{$ifNull:["$passport.stamps", []]}}, 1]}})
Sagar Veeram 20/07/18

22
db.accommodations.find({"name":{"$exists":true, "$ne":[], "$not":{"$size":1}}})

1
Điều này không mở rộng tốt cho các kích thước tối thiểu khác (giả sử, 10).
Dan Dascalescu

giống như câu trả lời đầu tiên
arianpress

22

MongoDB 3.6 bao gồm $ expr https://docs.mongodb.com/manual/reference/operator/query/expr/

Bạn có thể sử dụng $ expr để đánh giá biểu thức bên trong khớp $ hoặc tìm.

{ $match: {
           $expr: {$gt: [{$size: "$yourArrayField"}, 0]}
         }
}

hoặc tìm

collection.find({$expr: {$gte: [{$size: "$yourArrayField"}, 0]}});

1
Trong khi chính xác, đây là một câu trả lời trùng lặp. Xem stackoverflow.com/a/48410837/2424641 bởi @ user2683814
SteveB

13

Tôi đã tìm thấy giải pháp này, để tìm các mục có trường mảng lớn hơn độ dài nhất định

db.allusers.aggregate([
  {$match:{username:{$exists:true}}},
  {$project: { count: { $size:"$locations.lat" }}},
  {$match:{count:{$gt:20}}}
])

Tổng hợp $ khớp đầu tiên sử dụng một đối số đúng với tất cả các tài liệu. Nếu trống, tôi sẽ nhận được

"errmsg" : "exception: The argument to $size must be an Array, but was of type: EOO"

Đây thực chất là câu trả lời giống như câu trả lời này , được cung cấp 2 năm trước đó.
Dan Dascalescu

1

Tôi biết câu hỏi cũ của nó, nhưng tôi đang thử câu hỏi này với $ gte và $ size tìm thấy. Tôi nghĩ rằng để tìm () là nhanh hơn.

db.getCollection('collectionName').find({ name : { $gte : {  $size : 1 } }})

-5

Mặc dù tất cả các câu trả lời ở trên đều hoạt động, những gì ban đầu bạn cố gắng làm là cách chính xác, tuy nhiên bạn chỉ cần có cú pháp ngược (chuyển đổi "$ size" và "$ gt") ..

Chính xác:

db.collection.find({items: {$gt: {$size: 1}}})

Sai:

db.collection.find({items: {$size: {$gt: 1}}})

1
Tôi không thấy lý do tại sao rất nhiều downvote - điều này hoạt động hoàn hảo cho tôi!
Jake Stokes

Tôi đã không downvote, nhưng nó không hoạt động (v4.2).
Evgeni Nabokov

Hoạt động hoàn toàn tốt, v 4.2.5
27 phút
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.