I have a collection and each document in that collection has an array field countries. I want to select all documents which include any of below countries:
China, USA, Australia
And the output should show the number of above countries each document has.
I use below aggregate command:
db.movies.aggregate([
{
$match: { countries: { $in: ["USA", 'China', 'Australia'] } }
},
{
$project: {
countries: {$size: '$countries'}
}
}
]);
it doesn't work as expected. It shows the number of all countries in the document who has the above-listed country. For example, if a document has China, Japan in its countries field, I expected it return 1 (because only China is in the above country list) but it returns two. How can I do that in the aggregation command?
The $in operator just "queries" documents that contain one of the possible values, so it does not remove anything from the array.
If you want to count "only matches" then apply $setIntersection to the array before $size:
db.movies.aggregate([
{
$match: { countries: { $in: ["USA", 'China', 'Australia'] } }
},
{
$project: {
countries: {
$size: {
"$setIntersection": [["USA", 'China', 'Australia'], '$countries' ]
}
}
}
]);
That returns the "set" of "unique" matches to the array provided against the array in the document.
There is an alternate of $in as an aggregation operator in modern releases ( MongoDB 3.4 at least ). This works a bit differently in "testing" a "singular" value against an array of values. In array comparison you would apply with $filter:
db.movies.aggregate([
{
$match: { countries: { $in: ["USA", 'China', 'Australia'] } }
},
{
$project: {
countries: {
$size: {
$filter: {
input: '$countries',
cond: { '$in': [ '$$this', ["USA", 'China', 'Australia'] ] }
}
}
}
}
]);
That really should only be important to you where the array "within the document" contains entries that are not unique. i.e:
{ countries: [ "USA", "Japan", "USA" ] }
And you needed to count 2 for "USA", as opposed to 1 which would be the "set" result of $setIntersection
Related
{
id: 1,
name: "sree",
userId: "001",
paymentData: {
user_Id: "001",
amount: 200
}
},
{
id: 1,
name: "sree",
userId: "001",
paymentData: {
user_Id: "002",
amount: 200
}
}
I got this result after unwind in aggregation any way to check user_Id equal to userId
Are you looking to only retrieve the results when they are equal (meaning you want to filter out documents where the values are not the same) or are you looking to add a field indicating whether the two are equal?
In either case, you append subsequent stage(s) to the aggregation pipeline to achieve your desired result. If you want to filter the documents, the new stage may be:
{
$match: {
$expr: {
$eq: [
"$userId",
"$paymentData.user_Id"
]
}
}
}
See how it works in this playground example.
If instead you want to add a field that compares the two values, then this stage may be what you are looking for:
{
$addFields: {
isEqual: {
$eq: [
"$userId",
"$paymentData.user_Id"
]
}
}
}
See how it works in this playground example.
You could also combine the two as in:
{
$addFields: {
isEqual: {
$eq: [
"$userId",
"$paymentData.user_Id"
]
}
}
},
{
$match: {
isEqual: true
}
}
Playground demonstration here
I have a collection with documents like this one:
{
f1: {
firstArray: [
{
secondArray: [{status: "foo1"}, {status: "foo2"}, {status: "foo3"}]
}
]
}
}
My expected result includes documents that have at least one item in firstArray, which is last object status on the secondArray is included in an input array of values (eg. ["foo3"]).
I don't must use aggregate.
I tried:
{
"f1.firstArray": {
$elemMatch: {
"secondArray.status": {
$in: ["foo3"],
},
otherField: "bar",
},
},
}
You can use an aggregation pipeline with $match and $filter, to keep only documents that their size of matching last items are greater than zero:
db.collection.aggregate([
{$match: {
$expr: {
$gt: [
{$size: {
$filter: {
input: "$f1.firstArray",
cond: {$in: [{$last: "$$this.secondArray.status"}, ["foo3"]]}
}
}
},
0
]
}
}
}
])
See how it works on the playground example
If you know that the secondArray have always 3 items you can do:
db.collection.find({
"f1.firstArray": {
$elemMatch: {
"secondArray.2.status": {
$in: ["foo3"]
}
}
}
})
But otherwise I don't think you can check only the last item without an aggregaation. The idea is that a regular find allows you to write a query that do not use values that are specific for each document. If the size of the array can be different on each document or even on different items on the same document, you need to use an aggregation pipeline
I have an array in_cart which has product_id(s) and the amount of the individual items in the cart document
"in_cart":[
{
"product_id":"12345",
"amount":2
}
]
What I want is to do is insert the amount field into the details array. $lookup operator is done on the product_id so there will always be an equal amount of items in both arrays.
"details":[
{
"_id":"12345",
"name":"test",
"price":1110,
// ...more data...
}
]
$map to iterate over the details array.
$filter to filter out the document from in_cart array that has the same value of product_id field as the current item from details array have in _id field
$arrayElemAt to get the first element of the filtered array (since it will always have only one element)
$getField to get only amount property of filtered item
db.collection.aggregate([
{
$set: {
details: {
$map: {
input: "$details",
in: {
_id: "$$this._id",
name: "$$this.name",
price: "$$this.price",
amount: {
$getField: {
field: "amount",
input: {
$arrayElemAt: [
{
$filter: {
input: "$in_cart",
as: "cart_item",
cond: {
$eq: [
"$$cart_item.product_id",
"$$this._id"
]
}
}
},
0
]
}
}
}
}
}
}
}
}
])
Working example
I'm trying to get a grand total of all tags.
let topics = await ReadSchema.aggregate([{
$group: {
"_id": "$id",
count: { $size: { "$ifNull": [ "$summary.topics", [] ] } }
}
}]);
I get the error: server error MongoError: unknown group operator '$size'
Bonus points if you can remove duplicate "topics" in the total.
It can't allow $size as accumulator operator,
The $group's field Computed using the accumulator operators. The operator must be one of the following accumulator operators are: $accumulator, $addToSet, $avg, $first, $last, $max, $mergeObjects, $min, $push, $stdDevPop, $stdDevSamp, $sum, for more details refer $group,
use $sum before $size operator,
let topics = await ReadSchema.aggregate([
{
$group: {
"_id": "$id",
count: {
$sum: {
$size: {
"$ifNull": [ "$summary.topics", [] ]
}
}
}
}
}
]);
Remove duplicate topics in the total:
$addToSet to topics, make unique array of topics array
$reduce to iterate loop of topics array and get union of all topics tags
using $setUnion and, $size to get total count of unique topics
let topics = await ReadSchema.aggregate([
{
$group: {
_id: "$id",
topics: {
$addToSet: { $ifNull: [ "$summary.topics", [] ] }
}
}
},
{
$project: {
id: 1,
count: {
$size: {
$reduce: {
input: "$topics",
initialValue: [],
in: { $setUnion: ["$$this", "$$value"] }
}
}
}
}
}
])
Suggestions:
match topics is array condition in first stage in your query, $type: 4 indicates topics field has array data type or not, this will filter your documents before $group stage and you do not longer need to check $ifNull condition in $group stage, you can remove that condition.
for query optimization you can put index on summary.topics field.
how index works refer index
create index refer db.collection.createIndex
{
$match: {
$and: [
{ "summary.topics": { $type: 4 } },
{ "summary.topics": { $ne: [] } }
]
}
}
I found a faster way to remove duplicates and count size of an embedded array document.
let topics = await ReadSchema.aggregate([
{
$project: {
_id: '0',
topics: { $ifNull: ['$summary.topics', []] },
},
},
{ $unwind: '$topics' },
{ $group: { _id: '0', topics: { $addToSet: '$topics' } } },
{
$project: {
count: { $size: '$topics' },
},
},
]);
First we create a projection with just the topics. It will be an array of objects which contain the topics array for each document, we use $ifNull which will default to an empty array for documents where the embedded summary.topics array is missing.
We then $unwind that array of arrays into one flat array. Then $group the array using $addToSet which will implicitly remove duplicates by its nature.
We then $project a new document with a count property that takes the $size of the new array (as duplicates are now removed).
I have the below structure for my collection:
{
"price":123,
"totalPrices": [
{
"totPrice":123
}
]
}
I am trying to query for all the documents in my collection where price is not equals to totalPrice.totPrice (so above should not be returned).
But it keeps returning the documents which have equal prices as well (such as above sample).
This is the query I'm using:
{
$where : "this.price!== this.totalPrices.totPrice",
totalPrice:{$size:1}
}
What am I doing wrong :(
First, you need to match the size of the array totalPrices is equal to 1. Second, you need to unwind the totalPrices, since it's an array field. Last, you should match the equality of price and totalPrices.totPrice. Try the below code:
db.collection.aggregate([
{
$match: {
$expr: {
$eq: [
{
$size: "$totalPrices"
},
1
]
}
}
},
{
$unwind: "$totalPrices"
},
{
$match: {
$expr: {
$ne: [
"$price",
"$totalPrices.totPrice"
]
}
}
}
])
MongoPlayGroundLink