I have a collection "product_reviews" with this document structure
{
_id: 'B000000OE4',
'product/title': 'Working Class Hero',
'product/price': '16.99',
reviews: [
{
'review/userId': 'unknown',
'review/profileName': 'unknown',
'review/helpfulness': '2/3',
'review/score': '4.0',
'review/time': '27/05/1999/00:00:00',
'review/summary': 'Worth it for one song',
'review/text': "I really like Joan Baez'..."
},
{
'review/userId': 'A1W0RKM6J6J73L',
'review/profileName': 'Aaron Woodin (purchagent#aol.com)',
'review/helpfulness': '1/1',
'review/score': '3.0',
'review/time': '09/02/1999/00:00:00',
'review/summary': 'The critical lambasting on the Amazon Page Missed one thing.',
'review/text': "They forgot to mention Mary Chapin..."
},
...
]
}
My goal is to add object for each product (each product has unique _id) that will have following structure:
{
avgReviewScore: 4.5
reviewsCount: 105
reviewScoreDistrib: {
1: 15
2: 0
3: 30
4: 40
5: 20
}
}
I tried numerous aggregation pipelines but couldn't find a solution.
You can try this code:
db.product_reviews.aggregate([{
$unwind: "$reviews"
},
{
$group: {
_id: "$_id",
avgReviewScore: {
$avg: "$reviews.review/score"
},
reviewsCount: {
$sum: 1
},
scores: {
$push: "$reviews.review/score"
}
}
},
{
$project: {
avgReviewScore: 1,
reviewsCount: 1,
reviewScoreDistrib: {
$arrayToObject: {
$map: {
input: [1, 2, 3, 4, 5],
as: "num",
in: {
k: {$toString: "$$num"},
v: {
$size: {
$filter: {
input: "$scores",
as: "s",
cond: {
$eq: ["$$s", "$$num"]
}
}
}
}
}
}
}
}
}
},
{
$merge: {
into: "product_reviews",
on: "_id"
}
}
])
If you have any issue, you can ask
No need to $unwind and $group again (which can be very inefficient). You can use a simple updateMany:
db.collection.updateMany({},
[
{$set: {
reviewsData: {$map: {
input: "$reviews.review/score",
in: {$toDouble: "$$this"}
}}
}},
{$set: {
reviewScoreDistrib: {
$arrayToObject: {$map: {
input: {$range: [1, 6]},
as: "num",
in: {
k: {$toString: "$$num"},
v: {$size: {$filter: {
input: "$reviewsData",
cond: {$eq: ["$$this", "$$num"]}
}}}
}
}}
},
avgReviewScore: {$avg: "$reviewsData"},
reviewsCount: {$size: "$reviewsData"}
}}
])
See how it works on the playground example
Related
Hello I have this query result
{
sac: 1,
sac_db: 0,
kafka: 1,
platform: 13700,
}
now I just want to show the values in an array, but I can't find how to do it:
[1,0,1,13700]
You can get this done using $map and $objectToArray, like so:
db.collection.aggregate([
{
$project: {
_id: 0,
results: {
$map: {
input: {
$filter: {
input: {
"$objectToArray": "$$ROOT"
},
cond: {
$ne: [
"$$this.k",
"_id"
]
}
}
},
in: "$$this.v"
}
}
}
}
])
Mongo Playground
db.P2447653_reviews_c.aggregate([{
$group: {_id: {"reviewerID" : "reviewerID", count: {$sum: 1 }}},
$match:{"reviewTime":{$gt:1}},
$project : { "reviewerID":1, "reviewerName":1, "reviewTime":1}}
])
I don't understand the problem, I'm very new to MongoDB
Error: MongoServerError: A pipeline stage specification object must contain exactly one field.
I have no idea what else to try. I'm completely stuck.
Doing some formatting, your query is this:
db.P2447653_reviews_c.aggregate([
{
$group: { _id: { "reviewerID": "reviewerID", count: { $sum: 1 } } },
$match: { "reviewTime": { $gt: 1 } },
$project: { "reviewerID": 1, "reviewerName": 1, "reviewTime": 1 }
}
])
You missed some brackets, must be this:
db.P2447653_reviews_c.aggregate([
{
$group: {
_id: { "reviewerID": "$reviewerID" },
count: { $sum: 1 }
}
},
{ $match: { "reviewTime": { $gt: 1 } } },
{ $project: { "reviewerID": 1, "reviewerName": 1, "reviewTime": 1 } }
])
I have 2 collections, collection A has some documents like {'id':1,'field':'name'},{'id':1,'field':'age'},and collection B has some documents like
{'_id':1,'name':'alice','age':18,'phone':123},{'_id':2,'name':'bob','age':30,'phone':321}
and I want to find all the document whose '_id' is in collectionA, and just project the corresponding field.
for example:
collection A
{'id':1,'field':'name'},
{'id':1,'field':'age'}
collection B
{'_id':1,'name':'alice','age':18,'phone':123},
{'_id':2,'name':'bob','age':30,'phone':321}
the result is:
{'name':'alice','age':18},
I don't know if there is an easy way to do that?
You can use $lookup to join two collection
db.col1.aggregate([
{
$match: {
id: 1
}
},
{
"$lookup": {
"from": "col2",
"localField": "id",
"foreignField": "_id",
"as": "listNames"
}
},
{
$project: {
listNames: {
$first: "$listNames"
}
}
},
{
$project: {
_id: 0,
name: "$listNames.name",
age: "$listNames.age"
}
}
])
Mongo Playground: https://mongoplayground.net/p/E-0WvK_SUS_
So the idea is:
Convert the documents in to key, value pair for both the collections using $objectToArray.
Then perform a join operation based on key k and (id <-> _id) using $lookup.
Replace the result as root element using $replaceRoot.
Convert array to object using $arrayToObject and again $replaceRoot.
Query:
db.colB.aggregate([
{
$project: {
temp: { $objectToArray: "$$ROOT" }
}
},
{
$lookup: {
from: "colA",
let: { temp: "$temp", colB_id: "$_id" },
pipeline: [
{
$addFields: {
temp: { k: "$field", v: "$id" }
}
},
{
$match: {
$expr: {
$and: [
{ $in: ["$temp.k", "$$temp.k"] },
{ $eq: ["$temp.v", "$$colB_id"] }
]
}
}
},
{
$replaceRoot: {
newRoot: {
$first: {
$filter: {
input: "$$temp",
as: "item",
cond: { $eq: ["$field", "$$item.k"] }
}
}
}
}
}
],
as: "array"
}
},
{
$replaceRoot: {
newRoot: { $arrayToObject: "$array" }
}
}
]);
Output:
{
"name" : "alice",
"age" : 18
}
db.tickets.aggregate([
{$project:
{_id: 0, dayssince:
{$divide: [{ $subtract: [ 2020, {$convert:{input:{$substrCP:["$data.DATE_BIRTH", 6, 4]}, to: "int"}}]}, 45]}}},
{$match:{dayssince:{$gte: 1}}},
{$group:{_id:{day:"$dayssince"}},count:{$sum:1}}]);
Please, tell me whats wrong, i cant understend, i need to find count of all values
Please explain your problem. Just by indenting your code, its seems count property is outside of the $group operator.
here is your indented and fixed query:
db.tickets.aggregate([
{
$project: {
_id: 0,
dayssince: {
$divide: [
{
$subtract: [2020, { $convert: { input: { $substrCP: ["$data.DATE_BIRTH", 6, 4] }, to: "int" } }]
},
45]
}
}
},
{
$match: { dayssince: { $gte: 1 } }
},
{
$group: {
_id: {
day: "$dayssince"
},
count: {
$sum: 1
}
}
}
]);
I am trying to get first date from inner array in mongodb object and add it to it's parent with aggregation. Example:
car: {
"model": "Astra",
"productions": [
"modelOne": {
"dateOfCreation": "2019-09-30T10:15:25.026+00:00",
"dateOfEstimation": "2017-09-30T10:15:25.026+00:00",
"someOnterInfo": "whatever"
},
"modelTwo": {
"dateOfCreation": "2017-09-30T10:15:25.026+00:00",
"dateOfEstimation": "2019-09-30T10:15:25.026+00:00",
"someOnterInfo": "whatever"
}
]
}
to be turned in
car: {
"model": "Astra",
"earliestDateOfEstimation": "2017-09-30T10:15:25.026+00:00",
"earliestDateOfCreation": "2017-09-30T10:15:25.026+00:00"
}
How can I achieve that?
I'm assuming that modelOne and modelTwo are unknown when you start your aggregation. The key step is to run $map along with $objectToArray in order to get rid of those two values. Then you can just use $min to get "earliest" values:
db.collection.aggregate([
{
$addFields: {
dates: {
$map: {
input: "$car.productions",
in: {
$let: {
vars: { model: { $arrayElemAt: [ { $objectToArray: "$$this" }, 0 ] } },
in: "$$model.v"
}
}
}
}
}
},
{
$project: {
_id: 1,
"car.model": 1,
"car.earliestDateOfEstimation": { $min: "$dates.dateOfEstimation" },
"car.earliestDateOfCreation": { $min: "$dates.dateOfCreation" },
}
}
])
Mongo Playground
EDIT:
First step can be simplified if there's always modelOne, 'modelTwo'... (fixed number)
db.collection.aggregate([
{
$addFields: {
dates: { $concatArrays: [ "$car.productions.modelOne", "$car.productions.modelTwo" ] }
}
},
{
$project: {
_id: 1,
"car.model": 1,
"car.earliestDateOfEstimation": { $min: "$dates.dateOfEstimation" },
"car.earliestDateOfCreation": { $min: "$dates.dateOfCreation" },
}
}
])
Mongo Playground (2)