Lưu ý nhanh, bạn cần thay đổi "value"
của mình trường bên trong "giá trị"
là số, vì nó hiện là một chuỗi. Nhưng về câu trả lời:
Nếu bạn có quyền truy cập vào $ giảm
từ MongoDB 3.4, sau đó bạn thực sự có thể làm điều gì đó như sau:
db.collection.aggregate([
{ "$addFields": {
"cities": {
"$reduce": {
"input": "$cities",
"initialValue": [],
"in": {
"$cond": {
"if": { "$ne": [{ "$indexOfArray": ["$$value._id", "$$this._id"] }, -1] },
"then": {
"$concatArrays": [
{ "$filter": {
"input": "$$value",
"as": "v",
"cond": { "$ne": [ "$$this._id", "$$v._id" ] }
}},
[{
"_id": "$$this._id",
"name": "$$this.name",
"visited": {
"$add": [
{ "$arrayElemAt": [
"$$value.visited",
{ "$indexOfArray": [ "$$value._id", "$$this._id" ] }
]},
1
]
}
}]
]
},
"else": {
"$concatArrays": [
"$$value",
[{
"_id": "$$this._id",
"name": "$$this.name",
"visited": 1
}]
]
}
}
}
}
},
"variables": {
"$map": {
"input": {
"$filter": {
"input": "$variables",
"cond": { "$eq": ["$$this.name", "Budget"] }
}
},
"in": {
"_id": "$$this._id",
"name": "$$this.name",
"defaultValue": "$$this.defaultValue",
"lastValue": "$$this.lastValue",
"value": { "$avg": "$$this.values.value" }
}
}
}
}}
])
Nếu bạn có MongoDB 3.6, bạn có thể xóa một chút bằng $ mergeObjects
:
db.collection.aggregate([
{ "$addFields": {
"cities": {
"$reduce": {
"input": "$cities",
"initialValue": [],
"in": {
"$cond": {
"if": { "$ne": [{ "$indexOfArray": ["$$value._id", "$$this._id"] }, -1] },
"then": {
"$concatArrays": [
{ "$filter": {
"input": "$$value",
"as": "v",
"cond": { "$ne": [ "$$this._id", "$$v._id" ] }
}},
[{
"_id": "$$this._id",
"name": "$$this.name",
"visited": {
"$add": [
{ "$arrayElemAt": [
"$$value.visited",
{ "$indexOfArray": [ "$$value._id", "$$this._id" ] }
]},
1
]
}
}]
]
},
"else": {
"$concatArrays": [
"$$value",
[{
"_id": "$$this._id",
"name": "$$this.name",
"visited": 1
}]
]
}
}
}
}
},
"variables": {
"$map": {
"input": {
"$filter": {
"input": "$variables",
"cond": { "$eq": ["$$this.name", "Budget"] }
}
},
"in": {
"$mergeObjects": [
"$$this",
{ "values": { "$avg": "$$this.values.value" } }
]
}
}
}
}}
])
Nhưng nó ít nhiều giống nhau ngoại trừ việc chúng tôi giữ lại Dữ liệu bổ sung
Quay lại trước đó một chút, sau đó bạn luôn có thể $ unwind
"thành phố"
để tích lũy:
db.collection.aggregate([
{ "$unwind": "$cities" },
{ "$group": {
"_id": {
"_id": "$_id",
"cities": {
"_id": "$cities._id",
"name": "$cities.name"
}
},
"_class": { "$first": "$class" },
"name": { "$first": "$name" },
"startTimestamp": { "$first": "$startTimestamp" },
"endTimestamp" : { "$first": "$endTimestamp" },
"source" : { "$first": "$source" },
"variables": { "$first": "$variables" },
"visited": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id._id",
"_class": { "$first": "$class" },
"name": { "$first": "$name" },
"startTimestamp": { "$first": "$startTimestamp" },
"endTimestamp" : { "$first": "$endTimestamp" },
"source" : { "$first": "$source" },
"cities": {
"$push": {
"_id": "$_id.cities._id",
"name": "$_id.cities.name",
"visited": "$visited"
}
},
"variables": { "$first": "$variables" },
}},
{ "$addFields": {
"variables": {
"$map": {
"input": {
"$filter": {
"input": "$variables",
"cond": { "$eq": ["$$this.name", "Budget"] }
}
},
"in": {
"_id": "$$this._id",
"name": "$$this.name",
"defaultValue": "$$this.defaultValue",
"lastValue": "$$this.lastValue",
"value": { "$avg": "$$this.values.value" }
}
}
}
}}
])
Tất cả đều trả về (gần như) cùng một thứ:
{
"_id" : ObjectId("5afc2f06e1da131c9802071e"),
"_class" : "Traveler",
"name" : "John Due",
"startTimestamp" : 1526476550933,
"endTimestamp" : 1526476554823,
"source" : "istanbul",
"cities" : [
{
"_id" : "ef8f6b26328f-0663202f94faeaeb-1122",
"name" : "Cairo",
"visited" : 1
},
{
"_id" : "ef8f6b26328f-0663202f94faeaeb-3981",
"name" : "Moscow",
"visited" : 2
}
],
"variables" : [
{
"_id" : "c8103687c1c8-97d749e349d785c8-9154",
"name" : "Budget",
"defaultValue" : "",
"lastValue" : "",
"value" : 3000
}
]
}
Tất nhiên, hai biểu mẫu đầu tiên là điều tối ưu nhất vì chúng chỉ đơn giản là luôn hoạt động "trong" cùng một tài liệu.
Các toán tử như $ Reduce
cho phép biểu thức "tích lũy" trên mảng, vì vậy chúng tôi có thể sử dụng nó ở đây để giữ một mảng "giảm" mà chúng tôi kiểm tra cho "_id"
duy nhất giá trị bằng cách sử dụng $ indexOfArray
để xem liệu đã có một vật phẩm tích lũy phù hợp hay chưa. Kết quả của -1
nghĩa là nó không có ở đó.
Để tạo một "mảng giảm", chúng tôi lấy "initialValue"
trong tổng số []
dưới dạng một mảng trống và sau đó thêm vào nó qua $ concatArrays
. Tất cả quá trình đó được quyết định thông qua "ternary" $ cond
toán tử coi "if"
điều kiện và "then"
"tham gia" đầu ra của $ filter
trên giá trị $$
hiện tại để loại trừ chỉ mục hiện tại _id
, tất nhiên là với một "mảng" khác đại diện cho đối tượng số ít.
Đối với "đối tượng" đó, chúng tôi lại sử dụng $ indexOfArray
để thực sự nhận được chỉ mục phù hợp vì chúng tôi biết rằng mục "ở đó" và sử dụng mục đó để trích xuất "đã truy cập"
hiện tại giá trị từ mục nhập đó qua $ arrayElemAt
và $ add
vào nó để tăng dần.
Trong "else"
trường hợp chúng ta chỉ cần thêm một "mảng" làm "đối tượng" chỉ có "đã thăm"
mặc định giá trị của 1
. Sử dụng cả hai trường hợp đó sẽ tích lũy hiệu quả các giá trị duy nhất trong mảng để xuất.
Trong phiên bản thứ hai, chúng tôi chỉ $ unwind
mảng và sử dụng liên tiếp $ group
để đầu tiên "đếm" các mục bên trong duy nhất, sau đó "xây dựng lại mảng" thành dạng tương tự.
Sử dụng $ unwind
trông đơn giản hơn nhiều, nhưng vì những gì nó thực sự làm là lấy một bản sao của tài liệu cho mọi mục nhập mảng, nên điều này thực sự thêm chi phí đáng kể cho quá trình xử lý. Trong các phiên bản hiện đại thường có các toán tử mảng có nghĩa là bạn không cần sử dụng điều này trừ khi ý định của bạn là "tích lũy trên các tài liệu". Vì vậy, nếu bạn thực sự cần $ group
trên một giá trị của khóa từ "bên trong" một mảng, thì đó là nơi bạn thực sự cần sử dụng nó.
Đối với "biến"
thì chúng ta có thể chỉ cần sử dụng $ filter
lại đây để nhận "Ngân sách"
phù hợp lối vào. Chúng tôi thực hiện việc này như là đầu vào cho mã $ map >
toán tử cho phép "định hình lại" nội dung mảng. Chúng tôi chủ yếu muốn điều đó để bạn có thể lấy nội dung của "giá trị"
(sau khi bạn biến tất cả thành số) và sử dụng $ avg
toán tử, được cung cấp biểu mẫu "ký hiệu đường dẫn trường" đó trực tiếp cho các giá trị mảng vì trên thực tế, nó có thể trả về kết quả từ một đầu vào như vậy.
Điều đó nói chung làm cho chuyến tham quan của gần như TẤT CẢ các "nhà khai thác mảng" chính cho đường ống tổng hợp (không bao gồm các nhà điều hành "tập hợp") tất cả trong một giai đoạn đường ống duy nhất.
Cũng đừng bao giờ quên rằng bạn luôn muốn $ match
với Toán tử truy vấn
thông thường là "giai đoạn đầu tiên" của bất kỳ quy trình tổng hợp nào để chỉ cần chọn tài liệu bạn cần. Lý tưởng nhất là sử dụng một chỉ mục.
Thay thế
Các phương pháp thay thế đang làm việc thông qua các tài liệu trong mã khách hàng. Nói chung sẽ không được khuyến nghị vì tất cả các phương pháp ở trên cho thấy chúng thực sự "giảm bớt" nội dung khi được trả về từ máy chủ, nói chung là điểm của "tổng hợp máy chủ".
"Có thể" có thể do tính chất "dựa trên tài liệu" mà các bộ kết quả lớn hơn có thể mất nhiều thời gian hơn đáng kể khi sử dụng $ unwind
và xử lý khách hàng có thể là một tùy chọn, nhưng tôi sẽ xem xét nó có nhiều khả năng hơn
Dưới đây là danh sách minh họa việc áp dụng một biến đổi cho luồng con trỏ khi kết quả được trả về thực hiện cùng một việc. Có ba phiên bản được chứng minh của phép biến đổi, hiển thị "chính xác" cùng một logic như trên, một cách triển khai với lodash
phương pháp tích lũy và tích lũy "tự nhiên" trên Bản đồ
thực hiện:
const { MongoClient } = require('mongodb');
const { chain } = require('lodash');
const uri = 'mongodb://localhost:27017';
const opts = { useNewUrlParser: true };
const log = data => console.log(JSON.stringify(data, undefined, 2));
const transform = ({ cities, variables, ...d }) => ({
...d,
cities: cities.reduce((o,{ _id, name }) =>
(o.map(i => i._id).indexOf(_id) != -1)
? [
...o.filter(i => i._id != _id),
{ _id, name, visited: o.find(e => e._id === _id).visited + 1 }
]
: [ ...o, { _id, name, visited: 1 } ]
, []).sort((a,b) => b.visited - a.visited),
variables: variables.filter(v => v.name === "Budget")
.map(({ values, additionalData, ...v }) => ({
...v,
values: (values != undefined)
? values.reduce((o,e) => o + e.value, 0) / values.length
: 0
}))
});
const alternate = ({ cities, variables, ...d }) => ({
...d,
cities: chain(cities)
.groupBy("_id")
.toPairs()
.map(([k,v]) =>
({
...v.reduce((o,{ _id, name }) => ({ ...o, _id, name }),{}),
visited: v.length
})
)
.sort((a,b) => b.visited - a.visited)
.value(),
variables: variables.filter(v => v.name === "Budget")
.map(({ values, additionalData, ...v }) => ({
...v,
values: (values != undefined)
? values.reduce((o,e) => o + e.value, 0) / values.length
: 0
}))
});
const natural = ({ cities, variables, ...d }) => ({
...d,
cities: [
...cities
.reduce((o,{ _id, name }) => o.set(_id,
[ ...(o.has(_id) ? o.get(_id) : []), { _id, name } ]), new Map())
.entries()
]
.map(([k,v]) =>
({
...v.reduce((o,{ _id, name }) => ({ ...o, _id, name }),{}),
visited: v.length
})
)
.sort((a,b) => b.visited - a.visited),
variables: variables.filter(v => v.name === "Budget")
.map(({ values, additionalData, ...v }) => ({
...v,
values: (values != undefined)
? values.reduce((o,e) => o + e.value, 0) / values.length
: 0
}))
});
(async function() {
try {
const client = await MongoClient.connect(uri, opts);
let db = client.db('test');
let coll = db.collection('junk');
let cursor = coll.find().map(natural);
while (await cursor.hasNext()) {
let doc = await cursor.next();
log(doc);
}
client.close();
} catch(e) {
console.error(e)
} finally {
process.exit()
}
})()