I have the following document:
{
"address": {
"building": "1007",
"coord": [ -73.856077, 40.848447 ],
"street": "Morris Park Ave",
"zipcode": "10462"
},
"borough": "Bronx",
"cuisine": "Bakery",
"grades": [
{ "date": { "$date": 1393804800000 }, "grade": "A", "score": 2 },
{ "date": { "$date": 1378857600000 }, "grade": "A", "score": 6 },
{ "date": { "$date": 1358985600000 }, "grade": "A", "score": 10 },
{ "date": { "$date": 1322006400000 }, "grade": "A", "score": 9 },
{ "date": { "$date": 1299715200000 }, "grade": "B", "score": 14 }
],
"name": "Morris Park Bake Shop",
"restaurant_id": "30075445"
}
I want a query that looks at the grades.grade and grades.score and doesn't return a this document if a grades object has grade == "A" AND score == 2. However, I want it to return a document if for example one grades object has grade == "A" and another grades object has score == 2.
This might also illustrate what I'm trying to do (although it doesn't work):
db.restaurants.find({
grades : {
$and: [
{"grade": {'$ne':"A"}},
{"score": {'$ne':2}}
]
}
});
You can try below query.
You basically require $anding $or and $nand. MongoDB doesn't have $nand operator, so I replaced it with the equivalent experession.
db.restaurants.find({
$and: [{
$or: [{
"grades.grade": "A"
}, {
"grades.score": 2
}]
}, {
$nor: [{
$and: [{
"grades.grade": "A"
}, {
"grades.score": 2
}]
}]
}]
});
$or operation will match all documents where any of the grade embedded document has either grade A or score is 2.
{ "grade": "A", "score": 3 } --- Just grade A
{ "grade": "A", "score": 2 } --- Both grade A and score 2
{ "grade": "B", "score": 2 } --- Just score 2
$nand operation will match all the documents where none of the grade embedded documents has both grade as A and score is 2
{ "grade": "A", "score": 3 } --- Just grade A
{ "grade": "B", "score": 2 } --- Just score 2
{ "grade": "C", "score": 4 } --- None
$and of above operations is the intersection.
{ "grade": "A", "score": 3 } --- Just grade A
{ "grade": "B", "score": 2 } --- Just score 2
Related
I'm trying to aggreate a collection of transactions into a running total of owners by day.
The initial collection looks like this:
[
{ "to": "A", "from": "0", "ts": 1 },
{ "to": "A", "from": "0", "ts": 1 },
{ "to": "B", "from": "0", "ts": 1 },
{ "to": "B", "from": "0", "ts": 2 },
{ "to": "C", "from": "0", "ts": 3 },
{ "to": "A", "from": "B", "ts": 4 }
]
What I would like to get is something like this:
[
{
"ts": 1,
"holdings": [
{ "owner": "0", "holdings": -3 },
{ "owner": "A", "holdings": 2 },
{ "owner": "B", "holdings": 1 }
]
},
{
"ts": 2,
"holdings": [
{ "owner": "0", "holdings": -4 },
{ "owner": "A", "holdings": 2 },
{ "owner": "B", "holdings": 2 }
]
},
{
"ts": 4,
"holdings": [
{ "owner": "0", "holdings": -5 },
{ "owner": "A", "holdings": 3 },
{ "owner": "B", "holdings": 1 },
{ "owner": "C", "holdings": 1 }
]
}
]
I've already understood how to generate this for a single ts that I'm setting, but I don't know how to do it across all ts.
The aggregation pipeline for a single ts looks like this:
db.collection.aggregate([
// start with: { "to": "A", "from": "0", "ts": 1 }
{
// create a doc with an array with subset of fields:
// { "_id": ObjectId("5a934e000102030405000000"),
// "data": [ { "change": 1, "owner": "A", "ts": "1" },
// { "change": -1, "owner": "0", "ts": "1" } ] }
$project: {
data: [
{
owner: '$to',
ts: '$ts',
change: 1,
},
{
owner: '$from',
ts: '$ts',
change: -1,
},
],
},
},
{
// unwind the array into 2 docs:
// { "_id": ObjectId("5a934e000102030405000000"), "data": { "change": 1, "owner": "A", "ts": "1" } },
// { "_id": ObjectId("5a934e000102030405000000"), "data": { "change": -1, "owner": "0", "ts": "1" } },
$unwind: '$data',
},
{
// use data as root:
// { "data": { "change": 1, "owner": "A", "ts": "1" } },
// { "data": { "change": -1, "owner": "0", "ts": "1" } }
$replaceRoot: {
newRoot: '$data',
},
},
{
// select day to calc totals
$match: {
ts: {
$lt: 6,
},
},
},
{
// sum totals, grouped by owner
$group: {
_id: '$owner',
//_id: null,
holdings: {
$sum: '$change',
},
},
},
])
This gives the correct result for a particular day (selected in the match stage). I don't understand how I can now generalize that to all days.
One way to do it is using $setWindowFields, which has a built-in accumulation:
db.collection.aggregate([
{
$project: {
ts: "$ts",
data: [{owner: "$to", change: 1}, {owner: "$from", change: -1}]
}
},
{$unwind: "$data"},
{
$group: {
_id: {ts: "$ts", owner: "$data.owner"},
holdings: {$sum: "$data.change"}
}
},
{
$setWindowFields: {
partitionBy: "$_id.owner",
sortBy: {"_id.ts": 1},
output: {
cumulativeHoldings: {
$sum: "$holdings",
window: {documents: ["unbounded", "current"]}
}
}
}
},
{
$group: {
_id: "$_id.ts",
holdings: {$push: {owner: "$_id.owner", holdings: "$cumulativeHoldings"}}
}
}
])
Playground
I would like to independently group the results of an or clause, including overlap. The data set is rather large so running 2 queries sequentially will result in an undesirable wait time. I am hoping I can somehow project which clause returned the corresponding data. Given this data set:
[
{
"_id": 1,
"item": "abc",
"name": "Michael",
"price": NumberDecimal("10"),
"quantity": NumberInt("2"),
"date": ISODate("2014-03-01T08:00:00Z")
},
{
"_id": 2,
"item": "jkl",
"name": "Toby",
"price": NumberDecimal("20"),
"quantity": NumberInt("1"),
"date": ISODate("2014-03-01T09:00:00Z")
},
{
"_id": 3,
"item": "xyz",
"name": "Keith",
"price": NumberDecimal("5"),
"quantity": NumberInt("10"),
"date": ISODate("2014-03-15T09:00:00Z")
},
{
"_id": 4,
"item": "abc",
"name": "Dwight",
"price": NumberDecimal("5"),
"quantity": NumberInt("20"),
"date": ISODate("2014-04-04T11:21:39.736Z")
},
{
"_id": 5,
"item": "abc",
"name": "Ryan",
"price": NumberDecimal("10"),
"quantity": NumberInt("10"),
"date": ISODate("2014-04-04T21:23:13.331Z")
},
{
"_id": 6,
"item": "def",
"name": "Jim",
"price": NumberDecimal("7.5"),
"quantity": NumberInt("5"),
"date": ISODate("2015-06-04T05:08:13Z")
},
{
"_id": 7,
"item": "abc",
"name": "Keith",
"price": NumberDecimal("7.5"),
"quantity": NumberInt("10"),
"date": ISODate("2015-09-10T08:43:00Z")
},
{
"_id": 8,
"item": "abc",
"name": "Michael",
"price": NumberDecimal("10"),
"quantity": NumberInt("5"),
"date": ISODate("2016-02-06T20:20:13Z")
},
]
I would like to receive this result:
[{
"_id": {
"name": "Keith"
},
"count": 2
},
{
"_id": {
"item": "abc",
},
"count": 5
}]
Here is what I have tried so far:
db.collection.aggregate([
{
$match: {
$or: [
{
item: "abc"
},
{
name: "Keith"
}
]
}
},
{
$group: {
_id: {
item: "$item",
name: "$name"
},
count: {
$sum: 1
}
}
}
])
You can use $facet to get multiple aggregation pipelines into the same stage in this way:
Using $facet there are two "outputs" one group by name and other by item.
In each one there are multiple stages:
First $match to process only documents you want.
Then $group with _id name or item, and $count to get the total.
db.collection.aggregate([
{
"$facet": {
"groupByName": [
{
"$match": {"name": "Keith"}
},
{
"$group": {"_id": "$name","count": {"$sum": 1}}
}
],
"groupByItem": [
{
"$match": {"item": "abc"}
},
{
"$group": {"_id": "$item","count": {"$sum": 1}}
}
]
}
}
])
Example here
The output is:
{
"groupByItem": [
{
"_id": "abc",
"count": 5
}
],
"groupByName": [
{
"_id": "Keith",
"count": 2
}
]
}
Here it is:
mongos> db.n.aggregate([ { $facet:{ names:[ {$match:{name:"Keith"}} , {$group:{_id:{name:"$name"}, count:{$sum:1}}} ] , items:[ {$match:{item:"abc"}},{ $group:{_id:{item:"$item"}, count:{$sum:1}} } ] } } , {$project:{ "namesANDitems":{$concatArrays:[ "$names","$items" ]} }} ,{$unwind:"$namesANDitems"} ,{$replaceRoot:{newRoot:"$namesANDitems"} } ]).pretty()
{ "_id" : { "name" : "Keith" }, "count" : 2 }
{ "_id" : { "item" : "abc" }, "count" : 5 }
mongos>
explained:
You create two pipes via $facet
Match in every facet pipe what you need to group pipe1=names , pipe2=items
Join the arrays from the two pipes in single array named "namesANDitems"
Convert the array to object with $unwind
Remove the temporary object name namesANDitems so you have only the two objects as requested
How do I find the documents with with average score greater than 5. The collection looks like this:
Collection restaurants:
{"grades": [{"grade": "A", "score": 2}, {"grade": "A", "score": 6}], "name": "Morris Park Bake Shop", "restaurant_id": "30075445"}
{"grades": [{"grade": "A", "score": 8}, {"grade": "B", "score": 23}], "name": "Wendy'S", "restaurant_id": "30112340"}
{"grades": [{"grade": "A", "score": 2}, {"grade": "A", "score": 11}], "name": "Dj Reynolds Pub And Restaurant", "restaurant_id": "30191841"}
I tried
db.restaurants.find({average_socre: {$gt: 5}}, {average_socre:{$avg: "$grades.score"}})
but it doesn't work.
Using this query:
db.collection.find({
$expr: {
$gt: [
{
"$avg": "$grades.score"
},
5
]
}
},
{
average_score: {
$avg: "$grades.score"
}
})
Live
Documents which I have in my MongoDB collection are like below and the length of the array grades of JSON objects is varying:
{
"address": {
"building": "1007",
"coord": [-73.856077, 40.848447],
"street": "Morris Park Ave",
"zipcode": "10462"
},
"borough": "Bronx",
"cuisine": "Bakery",
"grades": [{
"date": {
"$date": 1393804800000
},
"grade": "A",
"score": 2
}, {
"date": {
"$date": 1378857600000
},
"grade": "A",
"score": 6
}, {
"date": {
"$date": 1358985600000
},
"grade": "A",
"score": 10
}, {
"date": {
"$date": 1322006400000
},
"grade": "A",
"score": 9
}, {
"date": {
"$date": 1299715200000
},
"grade": "B",
"score": 14
}],
"name": "Morris Park Bake Shop",
"restaurant_id": "30075445"
}
{
"address": {
"building": "469",
"coord": [-73.961704, 40.662942],
"street": "Flatbush Avenue",
"zipcode": "11225"
},
"borough": "Brooklyn",
"cuisine": "Hamburgers",
"grades": [{
"date": {
"$date": 1419897600000
},
"grade": "A",
"score": 8
}, {
"date": {
"$date": 1404172800000
},
"grade": "B",
"score": 23
}, {
"date": {
"$date": 1367280000000
},
"grade": "A",
"score": 12
}, {
"date": {
"$date": 1336435200000
},
"grade": "A",
"score": 12
}],
"name": "Wendy'S",
"restaurant_id": "30112340"
}
The task is to write a MongoDB query to return the collections which are having the sum of scores greater than 60.
Here is how this can be done:
db.collection.aggregate({
$unwind: "$grades" // flatten the "grades" array
}, {
$group: {
"_id": "$_id",
"sumOfScores": { // calculate the sum of all grades for all documents with the same "_id"
$sum: "$grades.score"
},
"docs": {
$push: "$$ROOT" // remember all affected documents per group
}
}
}, {
$match: {
"sumOfScores": {
$gt: 60 // filter out everything that we don't care about
}
}
}, {
$unwind: "$docs" // flatten the "docs" array
}, {
$group: { // restore the original document structure
"_id": "$docs._id",
"address": { $first: "$docs.address" },
"borough": { $first: "$docs.borough" },
"cuisine": { $first: "$docs.cuisine" },
"grades": {
$push: "$docs.grades"
},
"name": { $first: "$docs.name" },
"restaurant_id": { $first: "$docs.restaurant_id" }
}
})
Question: Write a MongoDB query to find the restaurants which locates in latitude value less than -95.754168.
Structure of 'restaurants' collection
{
"address": {
"building": "1007",
"coord": [ -73.856077, 40.848447 ],
"street": "Morris Park Ave",
"zipcode": "10462"
},
"borough": "Bronx",
"cuisine": "Bakery",
"grades": [
{ "date": { "$date": 1393804800000 }, "grade": "A", "score": 2 },
{ "date": { "$date": 1378857600000 }, "grade": "A", "score": 6 },
{ "date": { "$date": 1358985600000 }, "grade": "A", "score": 10 },
{ "date": { "$date": 1322006400000 }, "grade": "A", "score": 9 },
{ "date": { "$date": 1299715200000 }, "grade": "B", "score": 14 }
],
"name": "Morris Park Bake Shop",
"restaurant_id": "30075445"
}
Author's Answer is : db.restaurants.find({"address.coord" : {$lt : -95.754168}});
Is this the correct answer?
If no then what is the correct answer?
Resource: http://www.w3resource.com/mongodb-exercises/#PracticeOnline
You can go into your coord index like this
db.restaurants.find({"address.coord.0" : {$lt: -95.754168}});
According to the documentation here,
MongoDB uses the dot notation to access the elements of an array and to access the fields of an embedded document.
E.g. <array>.<index>
Note that the index is 0-based.