suppose my product collection look like this,
[
{
"id":1,
"name":"laptop",
"offer":false,
},
{
"id":2,
"name":"mobile",
"offer":true,
"offerEnd":"2-4-2022",
},
{
"id":3,
"name":"washing machine",
"offer":false
},
{
"id":4,
"name":"t.v",
"offer":false
},
{
"id":5,
"name":"refrigenerator",
"offer":true,
"offerEnd":"2-4-2025",
},
{
"id":6,
"name":"drone",
"offer":false
},
....
....
]
There you can see some product have offer(true) and other don't (false) here I want the offered documents (i.e. flag true) come first and then rest in a descending orders.
please let me know the query ?
Mongodb has the $orderby operator, with go you can use the findOptions to sort the results in ascending or descending order.
Here is an example:
findOptions := options.Find()
// Sort by `offer` field descending
findOptions.SetSort(bson.D{{"offer", true}})
db.Collection("myCollection").Find(nil, bson.D{}, findOptions)
Related
My collection looks something like this:
{
{
_id: 'some value',
'product/productId': 'some value',
'product/title': 'some value',
'product/price': 'unknown'
},
{
_id: 'some value',
'product/productId': 'some value',
'product/title': 'some value',
'product/price': '12.57'
}
}
My goal is to find if there are any products that have more than one price. Values of the key "product/price" can be "unknown" or numerical (e.g. "12.75"). Is there a way to write an aggregation pipeline for that or do I need to use a map-reduce algorithm? I tried both options but didn't find the solution.
If I've understood correctly you can try this aggregation pipeline:
First of all, the _id field is (or should be) unique, so I think you mean another field like id.
So the trick is to group by that id and get all prices into an array. Then filter using $match to get only documents where the total of prices is greater than 1.
db.collection.aggregate([
{
"$group": {
"_id": "$id",
"price": {
"$push": "$product/price"
}
}
},
{
"$match": {
"$expr": {
"$gt": [ { "$size": "$price" }, 1 ]
}
}
}
])
Example here
As added into comments for Joe if you want consider identical values as the same you have to use $addToSet
Example here
I need to check if an ObjectId exists in a non nested array and in multiple nested arrays, I've managed to get very close using the aggregation framework, but got stuck in the very last step.
My documents have this structure:
{
"_id" : ObjectId("605ce5f063b1c2eb384c2b7f"),
"name" : "Test",
"attrs" : [
ObjectId("6058e94c3994d04d28639616"),
ObjectId("6058e94c3994d04d28639627"),
ObjectId("6058e94c3994d04d28639622"),
ObjectId("6058e94c3994d04d2863962e")
],
"variations" : [
{
"varName" : "Var1",
"attrs" : [
ObjectId("6058e94c3994d04d28639616"),
ObjectId("6058e94c3994d04d28639627"),
ObjectId("6058e94c3994d04d28639622"),
ObjectId("60591791d4d41d0a6817d23f")
],
},
{
"varName" : "Var2",
"attrs" : [
ObjectId("60591791d4d41d0a6817d22a"),
ObjectId("60591791d4d41d0a6817d255"),
ObjectId("6058e94c3994d04d28639622"),
ObjectId("60591791d4d41d0a6817d23f")
],
},
],
"storeId" : "9acdq9zgke49pw85"
}
Let´s say I need to check if this if this _id exists "6058e94c3994d04d28639616" in all arrays named attrs.
My aggregation query goes like this:
db.product.aggregate([
{
$match: {
storeId,
},
},
{
$project: {
_id: 0,
attrs: 1,
'variations.attrs': 1,
},
},
{
$project: {
attrs: 1,
vars: '$variations.attrs',
},
},
{
$unwind: '$vars',
},
{
$project: {
attr: {
$concatArrays: ['$vars', '$attrs'],
},
},
},
]);
which results in this:
[
{
attr: [
6058e94c3994d04d28639616,
6058e94c3994d04d28639627,
6058e94c3994d04d28639622,
6058e94c3994d04d2863962e,
6058e94c3994d04d28639616,
6058e94c3994d04d28639627,
6058e94c3994d04d28639622,
60591791d4d41d0a6817d23f,
60591791d4d41d0a6817d22a,
60591791d4d41d0a6817d255,
6058e94c3994d04d28639622,
60591791d4d41d0a6817d23f
]
},
{
attr: [
60591791d4d41d0a6817d22a,
60591791d4d41d0a6817d255,
6058e94c3994d04d28639622,
60591791d4d41d0a6817d23f,
6058e94c3994d04d28639624,
6058e94c3994d04d28639627,
6058e94c3994d04d28639628,
6058e94c3994d04d2863963e
]
}
]
Assuming I have two products in my DB, I get this result. Each element in the outermost array is a different product.
The last bit, which is checking for this key "6058e94c3994d04d28639616", I could not find a way to do it with $group, since I dont have keys to group on.
Or with $match, adding this to the end of the aggregation:
{
$match: {
attr: "6058e94c3994d04d28639616",
},
},
But that results in an empty array. I know that $match does not query arrays like this, but could not find a way to do it with $in as well.
Is this too complicated of a Schema? I cannot have the original data embedded, since it is mutable and I would not be happy to change all products if something changed.
Will this be very expensive if I had like 10000 products?
Thanks in advance
You are trying to compare string 6058e94c3994d04d28639616 with ObjectId. Convert the string to ObjectId using $toObjectId operator when perform $match operation like this:
{
$match: {
$expr: {
$in: [{ $toObjectId: "6058e94c3994d04d28639616" }, "$attr"]
}
}
}
My documents have the following structure:
{
_id: ObjectId("59303aa1bad1081d4b98d636"),
clear_number: "83490",
items: [
{
name: "83490_1",
file_id: "e7209bbb",
hash: "2f568bb196f74263c64b7cf273f8ceaa",
},
{
name: "83490_2",
file_id: "9a56a935",
hash: "9c6230f7bf19d3f3186c6c3231ac2055",
},
{
name: "83490_2",
file_id: "ce5f6773",
hash: "9c6230f7bf19d3f3186c6c3231ac2055",
}
],
group_id: null
}
How to remove one of two subdocuments with the same items hash?
The following should do the trick if I understand you question correctly:
collection.aggregate({
$unwind: "$items" // flatten the items array
}, {
$group: {
"_id": { "_id": "$_id", "clear_number": "$clear_number", "group_id": "$group_id", "hash": "$items.hash" }, // per each document group by hash value
"items": { $first: "$items" } // keep only the first of all matching ones per group
}
}, {
$group: {
"_id": { "_id": "$_id._id", "clear_number": "$_id.clear_number", "group_id": "$_id.group_id" }, // now let's group everything again without the hashes
"items": { $push: "$items" } // push all single items into the "items" array
}
}, {
$project: { // this is just to restore the original document layout
"_id": "$_id._id",
"clear_number": "$_id.clear_number",
"group_id": "$_id.group_id",
"items": "$items"
}
})
In response to your comment I would suggest the following query to get the list of all document ids that contain duplicate hashes:
collection.aggregate({
$addFields: {
"hashes": {
$setUnion: [
[ { $size: "$items.hash" } ], // total number of hashes
[ { $size: { $setUnion: "$items.hash" } } ] // number of distinct hashes
]
}
}
}, {
$match:
{
"hashes.1": { $exists: true } // find all documents with a different value for distinct vs total number of hashes
}
}, {
$project: { _id: 1 } // only return _id field
})
There might be different approaches but this one seems pretty straight forward:
Basically, in the $addFields part, for each document, we first create an array consisting of two numbers:
the total number of hashes
the number of distinct hashes
Then we drive this array of two numbers through a $setUnion. After this step there can
either be two different numbers left in the array in which case the hash field does contain duplicates
or there is only one element left, in which case the number of distinct hashes equals the total number of hashes (so there are no duplicates).
We can check if there are two items in the array by testing if the element at position 1 (arrays are zero-based!) exists. That's what the $match stage does.
And the final $project stage is just to limit the output to the _id field only.
Suppose I have following data:
articles[{
_id:1,
flag1:true,
date:2016-09-09,
title:"...",
flag2:false
},
{
_id:2,
flag1:true,
date:2016-09-10,
title:"...",
flag2:false
},
{
_id:3,
flag1:false,
date:2016-09-11,
title:"...",
flag2:true
},
{
_id:4,
flag1:false,
date:2016-09-13,
title:"...",
flag2:true
}
]
I want individual sorting [basically I have to select two list one sorted list with flag1:true and flag2:true finally merge them into one list]
and flag1:true records list on top.
I want to get output in following order:
[
{
_id:2,
flag1:true,
date:2016-09-10,
title:"...",
flag2:false
},
{
_id:1,
flag1:true,
date:2016-09-09,
title:"...",
flag2:false
},
{
_id:4,
flag1:false,
date:2016-09-13,
title:"...",
flag2:true
},
{
_id:3,
flag1:false,
date:2016-09-11,
title:"...",
flag2:true
}
]
How do I write this SQL query in mongoose/mongodb?
select * from articles
where _id in
(select _id from articles where Flag1=true
order by date desc)
or
_id in (select _id from articles where Flag2=true
order by date desc)
I want to write individual sorting, so that I will get Flag1 based records in first priority with the sorted order.
> db.articles.find({ $or: [ { Flag1: true }, { Flag2: true } ] }).sort({date:-1})
However I am unclear with your requirements..still hope this will help you.
UPDATE:
Okais...then you just need to add sort by those two fields :-
db.articles.find({ $or: [ { Flag1: true }, { Flag2: true } ] })
.sort({Flag1:-1,Flag2:-1,date:-1})
I got the answer for my question. I used aggregate function with $project for sorting I used virtual field with $project.
We can create subqueries by using $project.
I have a document that looks something like this
{
name : james,
books : [
{
title: title1,
year: 1990
},
{
title: title2,
year: 1990
},
{
title: title3,
year: 1991
}
]
}
Say if I want to count how many books james owns with the year of 1990, how would I go about doing that? I've tried the following. But I realized it doesn't work because 'books' is an array.
db.collection(collectionName).find({name:james, books: {year: 1990}}).count(function(book_count){
console.log(book_count);
}
Any pointers would be much appreciated. Thanks!
EDIT:
I did see on another answer than you can use this code below to get the size of the whole array. But I am wondering how to get a count of items in the array with a particular parameter. ie. instead of seeing how many books james owns. I want to know how many of james' book are published in 1990.
db.mycollection.aggregate({$project: { count: { $size:"$foo" }}})
The aggregation framework is ideal for such. Consider running the following pipeline to get the desired result.
pipeline = [
{
"$match": {
"name": "james",
"books.year": 1990
}
},
{
"$project": {
"numberOfBooks": {
"$size": {
"$filter": {
"input": "$books",
"as": "el",
"cond": { "$eq": [ "$$el.year", 1990 ] }
}
}
}
}
}
];
db.collection.pipeline(pipeline);
The above pipeline uses the new $filter operator available for MongoDB 3.2 to produce an array which meets the specified condition i.e. it filters outer elements that do not satisfy the criteria. The initial $match pipeline is necessary to filter out documents getting into the aggregation pipeline early as a pipeline optimization strategy.
The $size operator which accepts a single expression as argument then gives you the number of elements in the resulting array, thus you have your desired book count.
For an alternative solution which does not use the $filter operator not found in earlier versions, consider the following pipeline operation:
pipeline = [
{
"$match": {
"name": "james",
"books.year": 1990
}
},
{
"$project": {
"numberOfBooks": {
"$size": {
"$setDifference": [
{
"$map": {
"input": "$books",
"as": "el",
"in": {
"$cond": [
{ "$eq": [ "$$el.year", 1990 ] },
"$$el",
false
]
}
}
},
[false]
]
}
}
}
}
];
db.collection.pipeline(pipeline);
The $project pipeline stage involves fittering the books array so that you remove the documents which do not have the year 1990. This is made possible through the $setDifference and $map operators.
The $map operator in essence creates a new array field that holds values as a result of the evaluated logic in a subexpression to each element of an array. The $setDifference operator then returns a set with elements that appear in the first set but not in the second set; i.e. performs a relative complement of the second set relative to the first. In this case it will return the final books array that has elements with year 1990 and subsequently the $size calculates the number of elements in the resulting array, thus giving you the book count.
For a solution that uses the $unwind operator, bearing in mind that (thanks to this insightful response from #BlakesSeven in the comments):
Since there is only a single document returned with nothing but a null
key and a count, there is no more chance for this breaking that limit
than the previous operation with the same output. It's not that
$unwind "breaks the limit", it's that it "produces a copy of each
document per array entry", which uses more memory ( possible memory
cap on aggregation pipelines of 10% total memory ) and therefore also
takes "time" to produce as well as "time" to process.
and as a last resort, run the following pipeline:
pipeline = [
{
"$match": {
"name": "james",
"books.year": 1990
}
},
{ "$unwind": "$books" },
{
"$match": { "books.year": 1990 }
},
{
"$group": {
"_id": null
"count": { "$sum": 1 }
}
}
]
db.collection.pipeline(pipeline)
You can use $elemMatch in projection to retrieve the document with only the matching books.
db.collection(collectionName).findOne({name:james, books: {year: 1990}}, { books: { $elemMatch: { year: 1990 } } }). // returned document will only contains books having the year 1990.
If you want only the count then you need to use aggregation framework. First match the documents, then unwind the books array, then match against year field. Something like following should work:
db.collection(collectionName).aggregate([{$match: {name: "james"}}, {$unwind:"$books"}, {$match:{"books.year":1990}}]