Hello everyone I want to group mongodb query results as;
var obj = {dynamicKey: value}
For example collection is like;
{name: "alex" , age: 21},
{name: "felix" , age: 81},
{name: "hannah" , age: 12},
{name: "tom" , age: 18}
I want to get the collection as;
{alex: 21},
{felix: 81},
{hannah: 12},
{tom: 18}
Is it possible to do this with $group in mongodb?
db.collection.aggregate([
{
$group : {
_id: "",
"$name" : { $first: "$age"}
}
}
]).pretty()
But this throws an error as; "errmsg" : "The field name '$name' cannot be an operator name",
How can I do this?
Related
How am i supposed to build my pipeline in MongoDB if, lets say i got this collection:
[
{_id: 1, field: "somevalue", array: ["123", "abc", "456", "def"]},
{_id: 2, field: "somevalue", array: ["zxc", "abc", "vbn", "jkl"]},
{_id: 3, field: "somevalue", array: ["fgh", "asd", "456", "def"]}
]
And I want receive in a new field, new array of values that matches values from my current array?
Lets say I got this array which i want to campare with:
[ "abc", "456"]
So I'm expecting to receive this:
[
{_id: 1, field: "somevalue", array: ["123", "abc", "456", "def"], myNewArray: ["abc", "456"]},
{_id: 2, field: "somevalue", array: ["zxc", "abc", "vbn", "jkl"], myNewArray: ["abc"]},
{_id: 3, field: "somevalue", array: ["fgh", "asd", "456", "def"], myNewArray: ["456"]}
]
How exactly can I build my pipeline to receive this?
You can simply use $setIntersection operator to keep only elements present in your array parameter.
db.collection.aggregate([
{
"$addFields": {
"newArray": {
$setIntersection: [
[
"abc",
"456"
],
"$array"
]
}
}
}
])
You can test it here
I have the following documents stored in my MongoDB database (let's say collection products):
{
a: "test1",
c: "data1"
},
{
a: "test2",
c: "data2"
},
{
a: "test3",
c: "data1"
},
{
a: "test4",
c: "data3"
},
{
a: "test5",
c: "data1"
},
{
a: "test6",
c: "data3"
},
How can I query all documents that have attribute c duplicated (or triplicated...)? In the example data, the query should return test1, test3, test4, test5 and test6 documents.
You can do this by grouping on c and then getting the groups with more than one in the group:
db.test.aggregate([
{$group: {_id: '$c', count: {$sum: 1}, docs: {$push: '$$ROOT'}}},
{$match: {count: {$gt: 1}}}
])
As described in the mongo documentation:
https://docs.mongodb.com/manual/reference/sql-aggregation-comparison/
There is a query for the following SQL query:
SELECT cust_id,
SUM(li.qty) as qty
FROM orders o,
order_lineitem li
WHERE li.order_id = o.id
GROUP BY cust_id
And the equivalent mongo aggregation query is as follows:
db.orders.aggregate( [
{ $unwind: "$items" },
{
$group: {
_id: "$cust_id",
qty: { $sum: "$items.qty" }
}
}
] )
However, the query is workinf fine as expected. My question, why is there no $match clause for the corresponding WHERE clause in SQL? And how is $unwind compensating the $match clause?
The comment by #Veeram is correct. The where clause in the SQL is unnecessary because the items list is embedded in the orders collection, where in a relational database you would have both an orders table and an orders_lineitem table (names taken from the description at https://docs.mongodb.com/manual/reference/sql-aggregation-comparison/)
Per the example data, you start with documents like this:
{
cust_id: "abc123",
ord_date: ISODate("2012-11-02T17:04:11.102Z"),
status: 'A',
price: 50,
items: [ { sku: "xxx", qty: 25, price: 1 },
{ sku: "yyy", qty: 25, price: 1 } ]
}
When you $unwind, the items are unwound but the rest of the data is projected. If you run a query like
db.orders.aggregate([ {"$unwind": "$items"} ])
you get the output
{
cust_id: "abc123",
ord_date: ISODate("2012-11-02T17:04:11.102Z"),
status: 'A',
price: 50,
items: { sku: "xxx", qty: 25, price: 1 }
},
{
cust_id: "abc123",
ord_date: ISODate("2012-11-02T17:04:11.102Z"),
status: 'A',
price: 50,
items: { sku: "yyy", qty: 25, price: 1 }
}
That has flattened the items array, allowing the $group to add the items.qty field:
db.orders.aggregate([
{"$unwind": "$items"},
{"$group": {
"_id": "$cust_id",
"qty": {"$sum": "$items.qty"}
}
}])
With the output:
{ "_id": "abc123",
"qty": 50
}
For a query into the embedded documents I'd love to return the matching element, plus a pre-designated set of elements.For example, with
Say I have a collection like below:
{_id: 1,
data:[
{who: 'Alice', score: 20},
{who: 'Brad', score: 25},
{who: 'Charlie', score: 30},
{who: 'Dave', score: 40},
{who: 'Elaine', score: 50},
{who: 'Frank', score: 20}
]},
{_id: 2,
data:[
{who: 'Alice', score: 30},
{who: 'Brad', score: 50},
{who: 'Charlie', score: 68},
{who: 'Dave', score: 20},
{who: 'Elaine', score: 50},
{who: 'Frank', score: 20}
]},
...
If I need to always return Alice and Brad, and anyone with score equals 50, I tend to use
find({data.score: 50}, {'data.who': {'$in': ['Alice', 'Brad', '$']}})
or
aggregate([{'$match': {data.score: 50}},
{'$project': {'data': 1}},
{'$unwind': '$data'},
{'$match': {'data.who': {'$in': ['Alice', 'Brad', '$']}}}])
unfortunately, neither works.
What would be the correct way to achieve it or is it even possible?
You can aggregate the result using the $redact stage. This removes the need of the $unwind stage, which can prove to be quiet costly in case of large data sets.
Use the $redact stage operator, to $$PRUNE sub documents
that do not meet the criteria in the $cond expression.
Code:
db.t.aggregate([
{$redact:{$cond:[{$or:[{$eq:[{$ifNull:["$who","Alice"]},"Alice"]},
{$eq:[{$ifNull:["$who","Brad"]},"Brad"]},
{$eq:[{$ifNull:["$score",50]},50]}
]},"$$DESCEND","$$PRUNE"]}}
])
Or you can make slight modification to the existing code as below:
The $match condition should contain all the three conditions to
retain a sub document.
Then $unwind.
Then apply the same $match condition again to select the un-winded
documents.
$group by _id to consolidate the selected data sub documents for
each _id.
Code:
db.t.aggregate([
{$match:{$or:[{"data.who":{$in:["Alice","Brad"]}},
{"data.score":{$eq:50}}]}},
{$unwind:"$data"},
{$match:{$or:[{"data.who":{$in:["Alice","Brad"]}},
{"data.score":{$eq:50}}]}},
{$group:{"_id":"$_id","data":{$push:"$data"}}}
])
o/p:
{
"_id" : 1,
"data" : [
{
"who" : "Alice",
"score" : 20
},
{
"who" : "Brad",
"score" : 25
},
{
"who" : "Elaine",
"score" : 50
}
]
}
{
"_id" : 2,
"data" : [
{
"who" : "Alice",
"score" : 30
},
{
"who" : "Brad",
"score" : 50
},
{
"who" : "Elaine",
"score" : 50
}
]
}
I think as per my understanding following query will match your criteria
db.collectionName.aggregate({
"$unwind": "$data"
},
{
"$match": {
"$or": [
{
"data.score": 50
},
{
"data.who": "Alice"
},
{
"data.who": "Brad"
}
]
}
},
{
"$project": {
"_id": 0,
"data": "$data"
}
}).pretty()
Consider having these objects in db.invoices
{ "customer" : "john", "price" : 4, "weekday": "WED" }
{ "customer" : "john", "price" : 8, "weekday": "SUN" }
{ "customer" : "john", "price" : 6, "weekday": "SAT" }
{ "customer" : "john", "price" : 5, "weekday": "SUN" }
{ "customer" : "bob", "price" : 10, "weekday": "SAT" }
{ "customer" : "bob", "price" : 15, "weekday": "MON" }
How can I query for documents having the maximum price for each customer? For above sample:
[ {
"customer": "bob",
"price": 15,
"weekday": "MON"
}, {
"customer": "john",
"price": 8,
"weekday": "SUN"
} ]
I can't figure it out using aggregation framework.
Edit 1: Problem is getting weekdays along with customer names. I do not want the maximum price alone.
Because you want to include weekday you need to pre-sort the docs to put the doc you want from each group first and then use $group with $first:
db.invoices.aggregate([
{$sort: {customer: 1, price: -1}},
{$group: {
_id: '$customer',
price: {$first: '$price'},
weekday: {$first: '$weekday'}
}}
])
Here's a way to get the result you want, it's one of several:
db.invoices.aggregate([
{$project: {customer: 1, other:{ price: "$price", weekday: "$weekday"}}},
{$group: {
_id: '$customer',
max: {$max: '$other'}
}
])
You can use $group operator:
db.invoises.aggregate([
{ $group : { _id: '$customer', price: { $max: '$price' } } }
])