mongoDB - find a specific result, with a document nested in different ways - mongodb

I have a little problem with my command lines in MongoDB. Here is what one of my documents looks like :
{
"id": "6249c4eb85a7563e673b4360",
"collection": {
"id": "6244099cb9ebc40007ac2ce3"
},
"characters": [
{
"id": "6244074ab9ebc40007ac2cf9"
},
{
"id": "6244074ab9ebc40007ac2ce1"
}
],
}
Then, in the MongoDB shell, I try to retrieve some books according to their criteria, here is an example :
db.comic.find({
characters: {
$elemMatch:{
_id: {
$in: [
ObjectId("6244074ab9ebc40007ac2ce5")
]
}
}
},
collection: {
$elemMatch:{
_id: {
$in: [
ObjectId("6244099cb9ebc40007ac2cfb")
]
}
}
}
}).pretty()
If I remove the "collection" part, I find the books related to the characters. However, if I put back the "collection" part, I have no result. I think it's related to the fact that "collection" is not an array, but I don't know how to solve this problem, knowing that the goal is to have several collections in the search, hence the "$elemMatch" and the "$in".
Thanks in advance for your help, have a nice day.

You need to use collection._id instead of collection:{$elemMatch:{_id since $elemMatch is for finding an object inside an array.
So your query should look like:
db.comic.find({
characters: {
$elemMatch:{
_id: {
$in: [
ObjectId("6244074ab9ebc40007ac2ce5")
]
}
}
},
"collection._id": {
$in: [ObjectId("6244099cb9ebc40007ac2cfb")]
}
}).pretty()

Related

How to remove same property name from sub object and push the sub objects as element of an array in mongo db

The coursesMarks property is present in every object. So I want to push the value inside coursesMarks property, to an array and return the array to the user.
[
{
"coursesMarks": {
"_id": "634a9be567a1f07be02f71d8",
"courseCode": "cse1201",
"courseTitle": "SP"
}
},
{
"coursesMarks": {
"_id": "634a9be567a1f07be02f71db",
"courseCode": "cse1203",
"courseTitle": "DS"
}
}
]
Then expected output is:
[
{
"courses":
{
"_id": "634a9be567a1f07be02f71d8",
"courseCode": "cse1201",
"courseTitle": "SP"
},
{
"_id": "634a9be567a1f07be02f71db",
"courseCode": "cse1203",
"courseTitle": "DS"
}
}
]
I've asked a clarifying question in the comments. But if we assume that the sample data provided is a single document where the array is stored in a field named arr, then a pipeline similar to the following may be what you are looking for:
[
{
$addFields: {
courses: {
$map: {
input: "$arr",
in: "$$this.coursesMarks"
}
}
}
},
{
$unset: "arr"
}
]
Playground example here
Edit
Based on the additional information about the structure of the data, you are looking to $group things in this particular case. Therefore the relevant addition to your pipeline should look something like this:
[
...
{
$group: {
_id: null,
courses: {
$push: "$coursesMarks"
}
}
},
{
$unset: "_id"
}
]
Playground demonstration here. It includes an empty $match stage at the beginning to represent whatever additional matching logic you currently have.

MongoDB: Can't update in nested arrays

I've been trying to modify a value in multiple arrays for a few arrays and I can't find documentation on how to do this.
My collection looks like this
"rates": [
{
"category": "Web",
"seniorityRates": [
{
"seniority": "junior",
"rate": 100
},
{
"seniority": "intermediate",
"rate": 135
},
{
"seniority": "senior",
"rate": 165
}
]
}
]
I'm just trying to modify "junior" to "beginner", this should be simple.
Thanks to these answers:
How can I update a multi level nested array in MongoDB?
MongoDB updating fields in nested array
I've manage to write that python code (pymongo), but it doesn't works...
result = my_coll.update_many({},
{
"$set":
{
"rates.$[].seniorityRates.$[j].seniority" : new
}
},
upsert=False,
array_filters= [
{
"j.seniority": old
}
]
)
The path 'rates' must exist in the document in order to apply array updates.
It correspond to this command that doesn't work either
db.projects.updateMany({},
{
$set:
{
"rates.$[].seniorityRates.$[j].seniority" : "debutant"
}
},
{ arrayFilters = [
{
"j.seniority": "junior"
}
]
}
)
clone(t={}){const r=t.loc||{};return e({loc:new Position("line"in r?r.line:this.loc.line,"column"in r?r.column:......)} could not be cloned
What am I doing wrong ?
Any help would be very appreciated
The other option could be Sample
db.collection.update({},
{
$set: {
"rates.$[].seniorityRates.$[j].seniority": "debutant"
}
},
{
arrayFilters: [
{
"j.rate": { //As per your data, you can apply the condition o rate field to modify the level
$lte: 100
}
}
]
})
Or
The actual query should work Sample
db.collection.update({},
{
$set: {
"rates.$[].seniorityRates.$[j].seniority": "debutant"
}
},
{
arrayFilters: [
{
"j.seniority": "junior"
}
]
})
The same should work in python, a sample question
So I was just dumb here, I inverted two parameters so I didn't have the correct collection in the python code...
Thanks Gibbs for pointing out where the mistake was in the mongo command.
I will not delete this post as it can help other to know how to do this kind of queries.

Mongodb aggregate match query with priority on full match

I am attempting to do a mongodb regex query on a field. I'd like the query to prioritize a full match if it finds one and then partials afterwards.
For instance if I have a database full of the following entries.
{
"username": "patrick"
},
{
"username": "robert"
},
{
"username": "patrice"
},
{
"username": "pat"
},
{
"username": "patter"
},
{
"username": "john_patrick"
}
And I query for the username 'pat' I'd like to get back the results with the direct match first, followed by the partials. So the results would be ordered ['pat', 'patrick', 'patrice', 'patter', 'john_patrick'].
Is it possible to do this with a mongo query alone? If so could someone point me towards a resource detailing how to accomplish it?
Here is the query that I am attempting to use to perform this.
db.accounts.aggregate({ $match :
{
$or : [
{ "usernameLowercase" : "pat" },
{ "usernameLowercase" : { $regex : "pat" } }
]
} })
Given your precise example, this could be accomplished in the following way - if your real world scenario is a little bit more complex you may hit problems, though:
db.accounts.aggregate([{
$match: {
"username": /pat/i // find all documents that somehow match "pat" in a case-insensitive fashion
}
}, {
$addFields: {
"exact": {
$eq: [ "$username", "pat" ] // add a field that indicates if a document matches exactly
},
"startswith": {
$eq: [ { $substr: [ "$username", 0, 3 ] }, "pat" ] // add a field that indicates if a document matches at the start
}
}
}, {
$sort: {
"exact": -1, // sort by our primary temporary field
"startswith": -1 // sort by our seconday temporary
}
}, {
$project: {
"exact": 0, // get rid of the "exact" field,
"startswith": 0 // same for "startswith"
}
}])
Another way would be using $facet which may prove a bit more powerful by enabling more complex scenarios but slower (several people here will hate me, though, for this proposal):
db.accounts.aggregate([{
$facet: { // run two pipelines against all documents
"exact": [{ // this one will capture all exact matches
$match: {
"username": "pat"
}
}],
"others": [{ // this one will capture all others
$match: {
"username": { $ne: "pat", $regex: /pat/i }
}
}]
}
}, {
$project: {
"result": { // merge the two arrays
$concatArrays: [ "$exact", "$others" ]
}
}
}, {
$unwind: "$result" // flatten the resulting array into separate documents
}, {
$replaceRoot: { // restore the original document structure
"newRoot": "$result"
}
}])

MongoDB daily data inside array

this might be a dumb question but I'm kinda new to MongoDB so let's give it a try.
I'm creating a database on MongoDB that will be storing inputs that will be entered every five minutes. This database in an SQL format should return something like this:
The main problem comes here, I don't know If I should create a document for each idvar with the entries of every year/month/day, will it hurt the performance if I make my JSON something like this?
{
"_id" : ObjectId("xxxxxxxxxxxxxxxxxxxx"),
"IdVar" : "60502",
"Years" : [
{
"2015" : [
{
"January" : [
{
"Date_Start" : "2015-01-01",
"Date_End" : "2015-01-02"
}
],
"February" : [
{
"Date_Start" : "2015-01-01",
"Date_End" : "2015-01-02"
}
]
}
]
}
]
}
Querying over nested arrays could always seems tedious in the first place.
Consider the following collection (as proposed in the comments of this post):
[
{
"IdVar": "60502",
"dates": [
{
"start": new Date("2017-03-01"),
"end": new Date("2017-04-01")
},
{
"start": new Date("2018-04-01"),
"end": new Date("2018-06-01")
}
]
},
{
"IdVar": "1337",
"dates": [
{
"start": new Date("2016-08-01"),
"end": new Date("2016-09-01")
},
{
"start": new Date("2015-04-01"),
"end": new Date("2015-06-01")
}
]
}
]
You only want to retrieve the documents which have dates in, let's say, 2017. You may use the  $elemMatch operator to do so:
db.collection.find({
dates: {
$elemMatch: {
start: {
$gte: ISODate("2017-01-01T00:00:00Z"),
$lte: ISODate("2017-12-31T00:00:00Z")
}
}
}
})
... but as you rightfully stated, this will return you the document in its entirety, unaltered. In many cases, this will suit your needs, by you may still want to project your document fields accordingly to your query: A simple way of putting this is to say the projection is the SQL-equivalent of SELECT and the query, WHERE.
As an example, The following will only return me the IdVar field of each document matching my query:
db.collection.find({
dates: {
$elemMatch: {
start: {
$gte: ISODate("2017-01-01T00:00:00Z"),
$lte: ISODate("2017-12-31T00:00:00Z")
}
}
}
},
// Project your document's fields here:
{
IdVar: true
})
... will return:
[
{
"IdVar": "60502",
"_id": ObjectId("5a934e000102030405000000")
}
]
Similarly to the query, you can use (nearly) all Mongo operators in the projection fields.
The $ operator is also pretty handy when handling nested arrays. The following code will return what you need, give it a try (MongoPlayground):
db.collection.find({
dates: {
$elemMatch: {
start: {
$gte: ISODate("2017-01-01T00:00:00Z"),
$lte: ISODate("2017-12-31T00:00:00Z")
}
}
}
},
{
"dates.$": 1
})

How to $concat two fields inside a subdocument in mongodb

WHAT I WANT TO ACHIEVE
Let us say I have this object:
{
"_id" : ObjectId("5aec063380a7490014e88792"),
"personal_info" : {
"dialing_code": "+44",
"phone_number": "67467885664"
}
}
I need to concat the two values personal_info.dialing_code (+44) and phone_number (67467885664) into one. +4467467885664 and compare it to a value. I need to retrieve a specific record from the database that will match the said value.
PROBLEM
I am having trouble concatinating two fields inside a subdocument and I am receiving this error:
{
"name": "MongoError",
"message": "$concat only supports strings, not object",
"ok": 0,
"errmsg": "$concat only supports strings, not object",
"code": 16702,
"codeName": "Location16702"
}
ATTEMPT #1
I have tried this:
UserModel.aggregate([
{ $unwind: '$personal_info' },
{$project: {
concat_p: {$concat: [
'$personal_info.dialing_code',
'$personal_info.phone_number'
]}
}}
])
It is giving me an error as mentioned above and in result I cannot do a $match right after.
ATTEMPT #2
I also tried this:
UserModel.aggregate([
{ $unwind: '$personal_info' },
{$project: {
p_dialing_code: '$personal_info.dialing_code',
p_phone_number: '$personal_info.phone_number',
concat_p: {$concat: [
'$p_dialing_code',
'$p_phone_number'
]}
}}
])
I have successfully took out the subdocument values one level however when I tried concatinating, it is producing me null values. This is the result I am getting:
{
"_id": "5af0998036daa90014129d6e",
"p_dialing_code": "+44",
"p_phone_number": "13231213213244",
"concat_p": null
}
I know how to do it on the $match pipeline but I have no luck concatinating the values inside the subdocument. Clearly, I need to do this first before I can compare. Thanks
It seems like you have different types under personal_info.dialing_code and personal_info.phone_number fields. In your example $concat is applied to every document in your collection and that's why you're getting an exception since $concat strictly expects its parameters to be strings.
So it will be working fine for document posted in your question but will throw an exception for something like this:
{
"_id" : ObjectId("5aec063380a7490014e88792"),
"personal_info" : {
"dialing_code": {},
"phone_number": "67467885664"
}
}
One way to fix this is to add $match condition before $project and use $type operator to get only documents having strings on those fields you want to concatenate.
db.UserModel.aggregate([
{
$match: {
$expr: {
$and: [
{ $eq: [ { $type: "$personal_info.dialing_code" }, "string" ] },
{ $eq: [ { $type: "$personal_info.phone_number" }, "string" ] }
]
}
}
},
{$project: {
concat_p: {$concat: [
"$personal_info.dialing_code",
"$personal_info.phone_number"
]}
}}
])