I have two schema defined in mongoose as follows,
Model: Article
var articleSchema = new mongoose.Schema({
sequenceId: String
title: String,
abstract: String,
authors: [String],
date: Date,
words: [{
selectedWord: String,
details: { type: Schema.Types.ObjectId, ref: 'Word' }
}],
otherTags: [String]
})
Model: Word
var wordSchema = new mongoose.Schema({
preferredWord: String,
synonyms: [String],
category: String
})
Now I am trying to get 2 sets of results for the following scenarios,
Get all the articles that have 'wordAbc' and/or 'wordXyz' in either selectedWord, preferredWord or synonyms
Get all the unique words in selectedWord, preferredWord and synonyms across all the articles in the database
What would be the best/efficient way perform query using mongoose?
For the first result I tried that partial query but getting the CastError message,
Article.find({})
.populate( 'words', null, { 'details': {$in: ['wordAbc', 'wordXyz']}} )
.exec(function(err, docs) {});
I think you can achieve both the things you want using aggregation pipeline.
Get all the articles that have 'wordAbc' and/or 'wordXyz' in either
selectedWord, preferredWord or synonyms
First you will need to populate all the words in details field of words array, and then match the articles based on selectedWord, preferredWord or synonyms.
This can be achieved like this:
Article.aggregate([{
$unwind : {
path :"$words",
preserveNullAndEmptyArrays :true
}
},{
$lookup : {
from : "words",
localField : "words.details",
foreignField : "_id",
as : "words.details"
}
},{
$unwind : {
path : "$words.details",
preserveNullAndEmptyArrays : true
}
},{
$match : {
$or : [{
"words.selectedWord" : {$in: ['wordAbc', 'wordXyz']}
},{
"words.details.preferredWord" : {$in: ['wordAbc', 'wordXyz']}
},{
"words.details.synonyms" : {$in: ['wordAbc', 'wordXyz']}
}]
}
},{
$group : {
_id : "$_id",
title : {$first : "$title"},
abstract : {$first : "$abstract"},
authors : {$first : "$authors"},
date : {$first : "$date"},
words: {$push : "$words"},
otherTags: {$first : "$otherTags"}
}
}])
Get all the unique words in selectedWord, preferredWord and synonyms
across all the articles in the database
In this case you will have to unwind words array and then populate words.details from words collection, and then unwind synonyms array, so that we can create a set of selectedWord, preferredWord and synonyms across all the articles, and then finally make a whole set of all the unique words in the last stage of aggregation pipeline.
This can be achieved like this:
Article.aggregate([{
$project : {
words : "$words"
}
},{
$unwind : "$words"
},{
$lookup : {
from : "words",
localField : "words.details",
foreignField : "_id",
as : "words.details"
}
},{
$unwind : "$words.details"
},{
$unwind : "$words.details.synonyms"
},{
$project : {
selectedWord : "$words.selectedWord",
preferredWord : "$words.details.preferredWord",
synonyms : "$words.details.synonyms"
}
},{
$group : {
_id : "null",
selectedWord : {$addToSet :"$selectedWord"},
preferredWord : {$addToSet :"$preferredWord"},
synonyms : {$addToSet :"$synonyms"}
}
},{
$project : {
commonWords : {$setUnion : ["$selectedWord","$preferredWord","$synonys"]}
}
}])
Explanation of 2nd aggregation.
$project : We want only words, so i carried on words field of all the articles and removed all other unnecessary fields from the pipeline.
$unwind : we need to unwind the words array, so that we can $lookup words.details from words collection in the next stage of pipeline
$lookup : populate details from words collection.
$unwind : Since $lookup returns an array, we need to unwind it to make it an object
$unwind : unwind words.details.synonyms, so that we can group them and create array of unique words in next stage of pipeline, At this stage, individual documents in aggregation pipeline will look something like this:
{
words : {
selectedWord :"someword",
details : {
preferredWord : "otherword",
synonym : "synonymword"
}
}
}
$project : we needed this flatten the object structure. After this stage individual document in the pipeline would look like this:
{
selectedWord :"someword",
preferredWord : "otherword",
synonym : "synonymword"
}
$group : combine all the selectedWord into one array, preferredWord into one array, and synonyms in one array, $addToSet is used to remove duplicate objects
$project : combine all the 3 arrays and create one array of unique Words
For details information on all the used operators of mongoDB, read respective documentations.
$setUnion documentation
$addToSet documentation
$project documentation
$unwind documentation
Documentation of all the operators of mongodb aggregation pipeline
I hope this helps you out
Related
I have a MongoDB question. I have a search in an aggregation with $match.
Search should check an array if one of the values matches a value of the array inside the documents.
As an example:
var stringList = 'general,online,offline'; //--> should check each value of this list
and two documents as an example
{
"_id" : ObjectId("5e8f3a64ec717a0013d2f1f9"),
"category" : [
"general",
"online",
"internal",
"miscellaneous"
]},
{
"_id" : ObjectId("5e8f3afeec717a0013d2f1fa"),
"category" : [
"offline"
]
}
I´ve tried a lot but I don´t found out how it is possible to check each value of the string list with each value in the category array. My example should show both documents, but if I use $in I don´t get any result.
What I tried is:
Split the list by comma and map
use of $elemMatch
use if $in
use combination of $elemMatch and $in
I hope I could explain my problem with my aggregation.
Thx everyone for his help.
You should be able to split the string on , with .split function, then pass this in to a $in query.
var stringList = 'general,online,offline';
db.documents.find( { "category" : { $in : stringList.split(",") } } );
{ "_id" : ObjectId("5e8f3a64ec717a0013d2f1f9"), "category" : [ "general", "online", "internal", "miscellaneous" ] }
{ "_id" : ObjectId("5e8f3afeec717a0013d2f1fa"), "category" : [ "offline" ] }
You can also do this in a $match in an aggregation query.
> db.documents.aggregate([
{ $match : { category: { $in : stringList.split(",") } }}
])
Here is an example, where we can search the array name from by using the regex method in the query.
var x = ["sai","test","jacob","justin"],
regex = x.join("|");
db.documents.find({
"firstName": {
"$regex": regex,
"$options": "i"
}
});
I have document structure like this:
{
"_id" : ObjectId("5a3a77d9d274eb44bc85d7c8"),
"b_id" : 3,
"b_display_tag" : "Veg Curry, Non Veg Curry"
}
I am able to get the first element from the string in b_display_tag using split and aggregate query:
db.blogs.aggregate([
{$project: {"b_display_tag": {$arrayElemAt:[{$split: ["$b_display_tag" , ","]}, 0]}}}
])
Result:
/* 1 */
{
"_id" : ObjectId("5a3a77d9d274eb44bc85d7c8"),
"b_display_tag" : "Veg Curry"
}
How can I update b_display_tag in the whole document with the first element from comma separated string?
Use below query to update b_display_tag. It will split b_tags using separator(') and update b_display_tag with first index.
db.blogs.find().forEach(function (blog)
{
if (blog.b_tags)
{
blog.b_display_tag=blog.b_tags.split(',');
blog.b_display_tag=blog.b_display_tag[0];
db.blogs.save(blog);
}
});
You can use $out in the aggregation pipeline to write it into a new collection, since here we need to update the existing collection blogs use the same collection name in $out.
db.blogs.aggregate([
{$project:
{
_id:1,
b_id:1,
b_display_tag:{$arrayElemAt:[{$split:["$b_display_tag", ","]}, 0]}
}
},
{$out:"blogs"}
]);
I need to get all documents that match an array of objects or an object with many fields.
Example 1 (array of objects)
If the document match the country_code than he must have one of postal_codes too
var locations = [
{
country_code : 'IT',
postal_codes : [21052, 21053, 21054, 21055]
},
{
country_code : 'GER',
postal_codes : [41052, 41053, 41054, 41055]
}
]
Example 2 (object with fields)
If the document match the key than it must have one of the values of that key
var location = {
'IT' : [21052, 21053, 21054, 21055],
'GER' : [41052, 41053, 41054, 41055]
}
I like the first type of document to match(array of objects) but how can i use to get all documents that match?
The documents to find have this structure:
{
"_id" : ObjectId("587f6f57ed6b9df409db7370"),
"description" : "Test description",
"address" : {
"postal_code" : "21052",
"country_code" : "IT"
}
}
You can use $in to find such collections.
db.collection_name.find(
{ address.postal_code: { $in: [your values] } },
)
Check this link for querying child objects.
Check this link for mongoDB $in
One way is to use the $or operator. This will help you limit the combinations of country_code and postal_code.
Your query should look something like this.
db.locations.find({
$or: [{
"country_code": "IT",
"postal_code": {
$in: [21052, 21053, 21054, 21055]
}
}, {
"country_code": "GER",
"postal_code": {
$in: [41052, 41053, 41054, 41055]
}
}]
})
Let me use following pseudo example to better explain my need.
I have a group of people and some images which belong to group schema
{
images:['ARRAY OF IMAGES'],
members:[{
age:NUMBER,
name:STRING,
email:EMAIL_OF_MEMBER
}],
groupName:String
....etc
}
Now i have a aggregation query
group.aggregate([
{
$project:{
//some code happening to support query in the below $match
'original':$$ROOT
}
},
{
$match:{//some query code happening and all params from $project above will be available here. the $original field contains all fields of document}
},
{
//Now i want to extract 1st image from Images array, Min and Max age of members in group.
}
],function(err,results){//dosomething with results})
If I don't use 3rd pipeline I am getting the whole document which is not required for me. I just need 1 image and min-max age of people in group to display a web page. I don't need other details.
Below part of the query will gives you the first image as well as min and max age of the members.
db.[collection].aggregate([
{
$unwind : "$members"
},
{
$group :
{ _id: "$_id" ,
images : { $first: "$images"},
minAge : {$min : "$members.age"},
maxAge : {$max: "$members.age"}
}
}
]).pretty();
For the mongoDb Version 3.1.6 and above, you can use $Slice in aggregation pipeline to limit the contents of array.
db.[collection].aggregate([
{
$unwind : "$members"
},
{
$group :
{ _id: "$_id" ,
images : {$push : "$images"},
minAge : {$min : "$members.age"},
maxAge : {$max: "$members.age"}
}
},
{
$project :
{ images :
{ images :{$slice:1} }
},
minAge : 1 ,
maxAge : 1
}
]).pretty();
I'm trying to get a specific field from a subdocument array
I'm not gonna include any of the fields in the parent doc
Here is the sample document
{
"_id" : ObjectId("5409dd36b71997726532012d"),
"hierarchies" : [
{
"rank" : 1,
"_id" : ObjectId("5409df85b719977265320137"),
"name" : "CTO",
"userId" : [
ObjectId("53a47a639c52c9d83a2d71db")
]
}
]
}
I would like to return the rank of the hierarchy if the a userId is in the userId array
here's what I have so far in my query
collectionName.find({{hierarchies:
{$elemMatch : {userId: ObjectId("53a47a639c52c9d83a2d71db")}}}
, "hierarchies.$.rank", function(err,data){}
so far it returns the entire object in the hierarchies array I want, but I would like to limit it to just the rank property of the object.
The projection available to .find() queries generally in MongoDB does not do this sort of projection for internal elements of an array. All you can generally do is return the "matched" element of the array entirely.
For what you want, you use the aggregation framework instead, which gives you more control over matching and projection:
Model.aggregate([
{ "$match": {
"hierarchies.userId": ObjectId("53a47a639c52c9d83a2d71db")
}},
{ "$unwind": "$hierarchies" },
{ "$match": {
"hierarchies.userId": ObjectId("53a47a639c52c9d83a2d71db")
}},
{ "$project": {
"rank": "$hierarchies.rank"
}}
],function(err,result) {
})
That basically matches the documents, filters the array content of the document to just the match and then projects only the required field.