Nó vẫn không phải là một truy vấn hay để chạy, nhưng có một cách hiện đại hơn một chút để thực hiện nó qua $ objectToArray
và $ redact
db.collection.aggregate([
{ "$redact": {
"$cond": {
"if": {
"$eq": [
{ "$size": { "$objectToArray": "$value" } },
3
]
},
"then": "$$KEEP",
"else": "$$PRUNE"
}
}}
])
Trong đó $ objectToArray
về cơ bản ép đối tượng thành một dạng mảng, giống như sự kết hợp của Object.keys ()
và .map ()
sẽ bằng JavaScript.
Nó vẫn không phải là một ý tưởng tuyệt vời vì nó yêu cầu quét toàn bộ bộ sưu tập, nhưng ít nhất các hoạt động của khung tổng hợp sử dụng "mã gốc" thay vì diễn giải JavaScript như trường hợp sử dụng $ where
.
Vì vậy, bạn vẫn nên thay đổi cấu trúc dữ liệu và sử dụng mảng tự nhiên cũng như các thuộc tính "kích thước" được lưu trữ nếu có thể để thực hiện các hoạt động truy vấn hiệu quả nhất.
Có, nó có thể làm nhưng không phải theo cách tốt nhất. Lý do cho điều này là về cơ bản bạn đang sử dụng mã <> $ ở đâu truy vấn toán tử sử dụng đánh giá JavaScript để khớp với nội dung. Không phải là cách hiệu quả nhất vì cách này không bao giờ có thể sử dụng chỉ mục và cần phải kiểm tra tất cả các tài liệu:
db.collection.find({ "$where": "return Object.keys(this.value).length == 3" })
Điều này tìm kiếm điều kiện phù hợp với "ba phần tử", sau đó chỉ hai trong số các tài liệu được liệt kê của bạn sẽ được trả lại:
{ "_id" : "number1", "value" : { "a" : 1, "b" : 2, "f" : 5 } }
{ "_id" : "number2", "value" : { "e" : 2, "f" : 114, "h" : 12 } }
Hoặc đối với "năm" trường trở lên, bạn có thể thực hiện tương tự:
db.numbers.find({ "$where": "return Object.keys(this.value).length >= 5" })
Vì vậy, các đối số cho toán tử đó là các câu lệnh JavaScript hiệu quả được đánh giá trên máy chủ để trả về nơi true
.
Một cách hiệu quả hơn là lưu trữ "số lượng" của các phần tử trong chính tài liệu. Bằng cách này, bạn có thể "lập chỉ mục" trường này và các truy vấn hiệu quả hơn nhiều vì mỗi tài liệu trong bộ sưu tập được chọn theo các điều kiện khác không cần phải quét để xác định độ dài:
{_id:'number1', value:{'a':1, 'b':2, 'f':5} count: 3},
{_id:'number2', value:{'e':2, 'f':114, 'h':12}, count: 3},
{_id:'number3', value:{'i':2, 'j':22, 'z':12, 'za':111, 'zb':114}, count: 5}
Sau đó, để lấy các tài liệu có "năm" phần tử, bạn chỉ cần truy vấn đơn giản:
db.collection.find({ "count": 5 })
Đó nói chung là hình thức tối ưu nhất. Nhưng một điểm khác là cấu trúc "Đối tượng" nói chung mà bạn có thể hài lòng với thực tế chung không phải là thứ mà MongoDB "chơi tốt" nói chung. Vấn đề là "duyệt" các phần tử trong đối tượng, và theo cách này MongoDB vui hơn nhiều khi bạn sử dụng một "mảng". Và ngay cả ở dạng này:
{
'_id': 'number1',
'values':[
{ 'key': 'a', 'value': 1 },
{ 'key': 'b', 'value': 2 },
{ 'key': 'f', 'value': 5 }
],
},
{
'_id': 'number2',
'values':[
{ 'key': 'e', 'value': 2 },
{ 'key': 'f', 'value': 114 },
{ 'key': 'h', 'value': 12 }
],
},
{
'_id':'number3',
'values': [
{ 'key': 'i', 'values': 2 },
{ 'key': 'j', 'values': 22 },
{ 'key': 'z'' 'values': :12 },
{ 'key': 'za', 'values': 111 },
{ 'key': 'zb', 'values': 114 }
]
}
Vì vậy, nếu bạn thực sự chuyển sang định dạng "mảng" như vậy thì bạn có thể thực hiện chính xác độ dài của một mảng có một phiên bản của $ kích thước
nhà điều hành:
db.collection.find({ "values": { "$size": 5 } })
Toán tử đó có thể hoạt động cho một chính xác giá trị cho độ dài mảng vì đó là điều khoản cơ bản về những gì có thể được thực hiện với toán tử này. Những gì bạn không thể làm được ghi lại trong một trận đấu "trong bình đẳng". Để làm được điều đó, bạn cần "khung tổng hợp" cho MongoDB, là một thay thế tốt hơn cho JavaScript và các hoạt động mapReduce:
db.collection.aggregate([
// Project a size of the array
{ "$project": {
"values": 1,
"size": { "$size": "$values" }
}},
// Match on that size
{ "$match": { "size": { "$gte": 5 } } },
// Project just the same fields
{{ "$project": {
"values": 1
}}
])
Vì vậy, đó là những thay thế. Có một phương thức "gốc" có sẵn để tổng hợp và một kiểu mảng. Nhưng khá tranh luận rằng đánh giá JavaScript cũng là "bản địa" của MongoDB, do đó không được triển khai trong mã gốc.