I have some entries like:
{ "name":"a", "value":10 }
{ "name":"b", "value":20 }
{ "name":"c", "value":10 }
...
And I can select names from a query with db.collection.find({"value":10},{"name":1, _id: false}). It gives me the following:
{ "name" : "a" }
{ "name" : "c" }
...
However, I want it to return an array of values, not a set of { key : value } pairs. (like [ "a", "c", ... ]). Is there a way to achieve this with only MongoDB queries or should I select and put them in an array in my application?
Current Output:
{ "name" : "a" }
{ "name" : "c" }
...
Expected output:
["a", "c", ...]
If the above is not a possible output, it may be also
{ "result": ["a", "c", ...] }
db.collection.distinct( "name", { "value": 10 })
from #Sagar Reddy comment will return a set array:
["a", "c"]
This is probably what you need. Aniway, a similar more complex query can be achieved using aggregation, where you can use extra aggregation stages.
db.collection.aggregate([
{ $match: { value: 10 }},
{ $group: { _id: null, result: { $addToSet: '$name' }}},
{ $project: { _id: 0, result: 1 }}
])
In the group stage, addToSet can be replaced with push if you want the same value to appear multiple times.
Output using addToSet:
{ "result" : [ "c", "a" ] }
Ex output using push:
{ "result" : [ "a", "c", "c" ] }
Related
I am trying to perform an aggregate query in MongoDB. My target is to check if any values within an array exists in another array and conditionally introduce a third variable accordingly.
For example, I have an array A => ["a", "b"] and another array B => ["a", "c", "d"]
So in this case as "a" exists in both A & B, they should match.
aggregate.push({
"$addFields": {
"canClaim": {
$cond: [{
$in: [["a", "b"], ["a", "c", "d"]]
}, 1, 0]
}
}
})
Does this help you?
db.collection.aggregate({
"$addFields": {
"c": {
"$setIntersection": [
"$a",
"$b"
]
}
}
},
{
"$match": {
"c.1": {
"$exists": true
}
}
})
Mongo Playground
I have a collection C1 which looks like this (simplified):
[
// ID and other irrelevant fields omitted
{
"name": "A",
"related": "Y",
"special": "foo",
"created" : ISODate("2020-02-07T19:36:52.757+02:00")
},
{
"name": "B",
"related": "Z",
"special": "bar",
"created" : ISODate("2020-02-07T19:36:52.757-06:00")
},
{
"name": "C",
"related": "X",
"special": "baz",
"created" : ISODate("2020-02-07T19:36:52.757+01:00")
},
{
"name": "D",
"related": "Z",
"special": "quux",
"created" : ISODate("2020-02-07T19:36:52.757+01:00")
},
// ...more records omitted...
]
And a collection C2 which looks like this (again, simplified):
[
// ID and other irrelevant fields omitted
{
"name": "X",
"total": 500
},
{
"name": "Y",
"total": 200
},
{
"name": "Z",
"total": 10
},
// ...more records omitted...
]
How can I, in a single query, retrieve a filtered set of records in C1 (e.g. { "special": "foo" }) with each record having a field c2 containing the matching records from C2 (C1.related being equal to C2.name) in addition to:
a field lowCount being the count of all matching records from C2 where { total: { $lte: 100 } }
a field midCount being the count of all matching records from C2 where { total: { $lte: 500, $gt: 100 } }
a field highCount being the count of all matching records from C2 where { total: { $gt: 500 } }
I realize that the database structure is awkward for what needs to be done, but I came in long after that was finalized and it can’t be overhauled at this point. The actual code is written in Java using Spring.
You need to use MongoDb aggregation.
db.c1.aggregate([
{
$match: {
"special": "foo"
}
},
{
$lookup: {
from: "c2",
localField: "related",
foreignField: "name",
as: "c2"
}
},
{
$addFields: {
lowCount: {
$size: {
$filter: {
input: "$c2",
cond: {
$lte: [
"$$this.total",
100
]
}
}
}
},
midCount: {
$size: {
$filter: {
input: "$c2",
cond: {
$lte: [
"$$this.total",
500
]
}
}
}
},
highCount: {
$size: {
$filter: {
input: "$c2",
cond: {
$gte: [
"$$this.total",
500
]
}
}
}
}
}
}
])
MongoPlayground
Spring Data allows to aggregate with MongoTemplate class (implements MongoOperations). Take a look how to transform this pipeline into Spring syntax here
I have nested array database records styled as such:
{
"_id" : "A",
"foo" : [
{
"_id" : "a",
"date" : ISODate("2017-07-13T23:27:13.522Z")
},
{
"_id" : "b",
"date" : ISODate("2017-08-04T22:36:36.381Z")
},
{
"_id" : "c",
"date" : ISODate("2017-08-23T23:59:40.202Z")
}
]
},
{
"_id" : "B",
"foo" : [
{
"_id" : "d",
"date" : ISODate("2017-07-17T23:27:13.522Z")
},
{
"_id" : "e",
"date" : ISODate("2017-01-06T22:36:36.381Z")
},
{
"_id" : "f",
"date" : ISODate("2017-09-14T23:59:40.202Z")
}
]
},
{
"_id" : "C",
"foo" : [
{
"_id" : "g",
"date" : ISODate("2017-11-17T23:27:13.522Z")
},
{
"_id" : "h",
"date" : ISODate("2017-06-06T22:36:36.381Z")
},
{
"_id" : "i",
"date" : ISODate("2017-10-14T23:59:40.202Z")
}
]
}
When I run the query:
db.bar.find(
{
$and: [
{"foo.date": {$lte: new Date(2017,8,1)}},
{"foo.date": {$gte: new Date(2017,7,1)}}
]
},
{
"_id":1
}
)
I'm returned
{
_id: "A"
},
{
_id: "B"
},
{
_id: "C"
}
Logically I'm asking for only the records where at least one date is between Aug-1 and Sept-1 (Record A), but am getting all records.
I'm thinking it might be referencing different dates on the subdocuments i.e. where foo.1.date > Aug-1 and foo.0.date < Sept-1.
Has anyone else had issue and found a resolution to this?
Your filters are evaluated separately against each subdocument in your array and that's why you're getting all results. For instance for C
element with _id g is gte 1st of August
element with _id h is lte 1st of September
You should use $elemMatch to find date in specified range
db.bar.find(
{ "foo":
{
"$elemMatch":
{ "date":
{
"$gte": new Date(2017,7,1),
"$lte": new Date(2017,8,1)
}
}
}
})
Only A will be returned for this query.
The way you are doing doensn't work as you want for array.
You have to unpack the array and after you compare the values.
db.bar.aggregate(
// Unpack the assignments array
{ $unwind : "$foo" },
// Find the assignments ending after given date
{ $match : {
"foo.date": { $gte: new Date(2017,7,1),$lt: new Date(2017,8,1) }
}}
)
This should also work fine
db.bar.find({"foo.date": { $gte: new Date(2017,7,1),$lt: new Date(2017,8,1) }})
I want to push an object to specify name of fields rather than array. I tried $push but I lose informations about field's name inserted in the array.
My collection is :
/* 1 */
{
"_id" : ObjectId("57614a7bd75df17df3013903"),
"O":"aa",
"D":"bb",
"month":1,
"year":2015,
"freq":5
}
/* 2 */
{
"_id" : ObjectId("57614a7bd75df17df3013904"),
"O":"aa",
"D":"bb",
"month":2,
"year":2015,
"freq":5
}
/* 3 */
{
"_id" : ObjectId("57614a7bd75df17df3013905"),
"O":"aa",
"D":"bb",
"month":1,
"year":2016,
"freq":5
}
I want to store all freq corresponding to fields : O and D.
Here is my expected output :
"_id" : ...,
"O" : "aa",
"D" : "bb",
"freq" : {
"2015" : {
"1" : 5,
"2":5
},
"2016" : {
"1" : 5
}
}
}
I tried this :
db.collection.aggregate([
{
'$group':
{
_id:{"O":"$O","D":"$D","Y":"$year"},
"freq" :{$push: "$freq"}
}
},
{
'$group':
{
_id:{"O":"$O","D":"$D"},
"freq" :{$push: "$freq"}
}
})]
but I got an array without informations of year or month.
Thank you
You have used two $group in your query
Your First group query is enough to build the data which you are expecting.
If we are executing the first query
db.stackoverflow.aggregate([
{
'$group':
{
_id:{"O":"$O","D":"$D","Y":"$year"},
"freq" :{$push: "$freq"}
}
}]);
then the result is
{ "_id" : { "O" : "aa", "D" : "bb", "Y" : 2016 }, "freq" : [ 5 ] }
{ "_id" : { "O" : "aa", "D" : "bb", "Y" : 2015 }, "freq" : [ 5, 5 ] }
Now if you execute your second $group query
db.stackoverflow.aggregate([
{
'$group':
{
_id:{"O":"$O","D":"$D"},
"freq" :{$push: "$freq"}
}
}])
then the result is
{ "_id" : { "O" : "aa", "D" : "bb" }, "freq" : [ 5, 5, 5 ] }
Reason:
The values fetched in the first $group query is not passed to the second $group query.
Solution:
Use $project available in the aggregation pipeline which passes along the documents with only the specified fields to the next stage in the aggregation pipeline. The specified fields can be existing fields from the input documents or newly computed fields.
https://docs.mongodb.com/manual/reference/operator/aggregation/project/
Here is the query to get your expected result
db.collection.aggregate([
{
'$group': {
_id: {
"o": "$o",
"d": "$d",
"year": "$year"
},
myArr: {
$push: {
year: "$year",
month: "$month",
freq: "$freq"
}
}
}
},
{
'$group': {
_id: {
"o": "$o",
"d": "$d"
},
myArr1: {
$push: {
year: "$year",
freq: "$myArr"
}
}
}
},
],
{
allowDiskUse: true
})
I have a Collection in my database where most documents have an array-field. These arrays contain exactly 2 elements. Now i want to find all documents where all of those array elements are elements of my query array.
Example Documents:
{ a:["1","2"] },
{ a:["2","3"] },
{ a:["1","3"] },
{ a:["1","4"] }
Query array:
["1","2","3"]
The query should find the first 3 documents, but not the last one, since there is no "4" in my query array.
Expected Result:
{ a:["1","2"] },
{ a:["2","3"] },
{ a:["1","3"] }
Looking forward to a helpful answer :).
Since the size is static, you can just check that both elements are in [1,2,3];
db.test.find(
{ $and: [ { "a.0": {$in: ["1","2","3"] } },
{ "a.1": {$in: ["1","2","3"] } } ] },
{ _id: 0, a: 1 }
)
>>> { "a" : [ "1", "2" ] }
>>> { "a" : [ "2", "3" ] }
>>> { "a" : [ "1", "3" ] }
EDIT: Doing it dynamically is a bit more hairy, I can't think of a way without the aggregation framework. Just count matches as 0 and non matches as 1, and finally remove all groups that have a sum != 0;
db.test.aggregate(
{ $unwind: "$a" },
{ $group: { _id: "$_id",
a: { $push: "$a" },
fail: { $sum: {$cond: { if: { $or: [ { $eq:["$a", "1"] },
{ $eq:["$a", "2"] },
{ $eq:["$a", "3"] }]
},
then: 0,
else: 1 } } } } },
{ $match: { fail: 0 } },
{ $project:{ _id: 0, a: 1 } }
)
>>> { "a" : [ "1", "3" ] }
>>> { "a" : [ "2", "3" ] }
>>> { "a" : [ "1", "2" ] }
I also think, that it's impossible without the aggregation framework (if elements count is dynamic).
But I found out more universal way of doing that:
db.tests.aggregate({
$redact: {
$cond: {
if: {$eq: [ {$setIsSubset: [ '$a', [ "1", "2", "3" ] ]}]},
then: '$$KEEP',
else: '$$PRUNE'
}
}
})
I believe the answer to your problem is to use
$in
(from the docs:)
Consider the following example:
db.inventory.find( { qty: { $in: [ 5, 15 ] } } )
This query selects all documents in the inventory collection where the qty field value is either 5 or 15. Although you can express this query using the $or operator, choose the $in operator rather than the $or operator when performing equality checks on the same field.
You can also do more complex stuff using arrays. Checkout:
http://docs.mongodb.org/manual/reference/operator/query/in/