How to query based on nested object's key and value? - mongodb

"session": {
"number": 123,
"words": {
"1": {
"id": 10,
"name": "Hello"
},
"2": {
"id": 13,
"name": "Hi"
},
"3": {
"id": 135,
"name": "Hey"
}
}
}
We have data in MongoDB database in the given structure which I am not able to modify.
I can get the object based on query session.number is 123, but now the challenging is we know the nested name is either Hi, Hey or Hello but the incremental key inside object words are unpredictable, is it possible to query base on the "name" field?

You can use $objectToArray and convert the words property to an array then query it however you want, you can use the following:
collection.aggregate([
{
$project: {
words: { $objectToArray: "$session.words" }
}
},
{
$match: {
'words.v': 'hi'
}
}
]);
You can read the docs for more info.

Related

How to project sub-document values in an array using MongoDB?

I am creating an application that needs to support multiple languages in the content. I am using the following document structure; each field that needs translation is an object with each language and associated string.
{
"greeting": {
"en": "Hello",
"fr": "Bonjou"
},
"places": [
{
"name": {
"en": "CN Tower",
"fr": "La Tour CN"
}
},
{
"name": {
"en": "Skydome",
"fr": "Le Skydome"
}
}
]
}
I want to be able to aggregate the result into a single selected language to produce the following result.
{
"greeting": "Hello",
"places": [
{
"name": "CN Tower"
},
{
"name": "Skydome"
}
]
}
Mapping the greeting field was simple, requiring only a $greeting.en. However, sub-documents in an array are a lot trickier to perform the path syntax with. Instead, I've had to use a $map to achieve the result I needed.
db.collection.aggregate([{
$project: {
greeting: "$greeting.en",
places: {
$map: {
input: "$places",
as: "place",
in: { name: "$$place.name.en"
}
}
}
}
}])
Is there a way to achieve this without using $map and instead using the path syntax?

How to query nested heterogeneous document

I have an array that have this format:
data{
[sequentialId]{guid:value1,name:value2}
}
I need to do something like db.data.find("data.?.name":"value1")
All the solutions I've seen don't work because they are expecting me to know the name of the attribute, but in this case the ID is sequential.
I've looked at the standard way of querying nested documents described here
https://docs.mongodb.com/manual/tutorial/query-array-of-documents/
I also looked at some options like $unwind but I can't get anything to work
here's a small sample
{"41":{"b":453081600,"f":1,"h":171,"s":4,"w":4,"wr":[2,0]},
"80":{"b":337132800,"f":2,"h":169,"s":4,"w":4,"wr":[0,0]},
"388":{"b":148694400,"f":1,"h":188,"l":{"c":[{"e":2001,"g":13,"m":292,"s":1992,"t":18},{"e":2006,"g":11,"m":197,"s":2001,"t":1},{"e":2009,"g":2,"m":111,"s":2006,"t":1790},{"e":2009,"g":0,"m":1,"s":2009,"t":1937},{"e":2010,"g":1,"m":14,"s":2010,"t":1},{"e":2011,"g":0,"m":8,"s":2010,"t":13}],"n":[{"e":2007,"g":1,"m":73,"s":1996,"t":1318}]},"s":2,"w":3,"wr":[0,0]}}
for example in this set of data I might need to query all the docs where "f"=1
The following query can get us the expected output:
Note: We are fetching the documents which have name is equal to a
db.collection.aggregate([
{
$addFields:{
"filtered":{
$filter:{
"input":"$data",
"as":"info",
"cond":{
$let:{
"vars":{
"array":{
$objectToArray:"$$info"
}
},
"in":{
$in:["a","$$array.v.name"]
}
}
}
}
}
}
},
{
$match:{
"filtered.0":{
$exists:true
}
}
},
{
$project:{
"filtered":0
}
}
]).pretty()
Data set:
{
"data":[
{
"41": {
"b": 453081600,
"name": "a"
},
"80": {
"b": 337132800,
"name": "b"
},
"388": {
"b": 148694400,
"name": "c"
}
}
]
}
{
"data":[
{
"41": {
"b": 453081600,
"name": "b"
},
"80": {
"b": 337132800,
"name": "b"
},
"388": {
"b": 148694400,
"name": "c"
}
}
]
}
Output:
{
"data":[
{
"41": {
"b": 453081600,
"name": "a"
},
"80": {
"b": 337132800,
"name": "b"
},
"388": {
"b": 148694400,
"name": "c"
}
}
]
}
Query analysis:
We are creating a filtered array to hold only those records from data array which contains name equal to a inside any subdocument with an unknown key.
It's done by first converting each document of data into an array of key-value pairs. Now we can directly query the values without worrying about the keys.
Finally, filter those documents in which the size of filtered array is greater than zero.

Move data from inside nested array

I have inserted multiple documents in my Mongo database incorrectly. I have accidentally nested the data inside another data object:
{
"_id": "5cdfda8ddc5cf00031fd3949",
"payload": {
"timestamp": "2019-05-18T10:12:29.896Z",
"data": {
"data": {
"name": 10,
"age": 10,
}
}
},
"__v": 0
}
I would like the document to not have the extra data object. So I would like it to look like this:
{
"_id": "5cdfda8ddc5cf00031fd3949",
"payload": {
"timestamp": "2019-05-18T10:12:29.896Z",
"data": {
"name": 10,
"age": 10,
}
},
"__v": 0
}
Is there a way in Mongo for me to update all the documents that have 2 data objects to just have one like shown above?
Alas, you cannot do this with one database request. You have to loop over all documents programmatically, set the new data and update them in the database.
You could use the aggregation framework, which won't let you update in place, but you could use the $out operator to write the results to a new collection, if that's an option.
db.collection.aggregate([
{
$project: {
__v : 1,
"payload.timestamp" : 1,
"payload.data" : "$payload.data.data"
},
},
{
"$out": "newCollection"
}
])
Or if you have a mixture of docs with correct format and docs with incorrect format, you can use the $cond operator to determine the correct output:
db.collection.aggregate([
{
$project: {
__v : 1,
"payload.timestamp" : 1,
"payload.data" : {
$cond: [
{ $ne : [ "$payload.data.data", undefined]},
"$payload.data.data",
"$payload.data"
]}
}
},
{
"$out": "newCollection"
}
])

Criteria Morphia MongoDB

I have a collection like this:
{
"_id": {
"$oid": "53f34ef8ec10d6fa97dcc34b"
},
"editions": [
{
"number": 1,
...
},
{
"number": 2,
...
},
...
]
}
I want filter results of my query by some number.
I tried
criterias.add(query.criteria("editions.number").equal(paramNumber));
And
query.filter("editions.number =", paramNumber)
However I just received all collection, when I pass paramNumber equals 2. What I want is receive the following result:
{
"_id": {
"$oid": "53f34ef8ec10d6fa97dcc34b"
},
"editions": [
{
"number": 2,
...
}
]
}
What am I doing wrong?
You can't receive partial arrays like that. You'll get back with the full document/object or just the fields you've specified in a projection.

How to perform AND operation in Mongodb for a query on nested array of objects

I have the following records:
Record 1:
{
"status": "active",
"users": [
{
"name": "foo",
"status": "a"
},
{
"name": "foo",
"status": "b"
}
]
}
Record 2:
{
"status": "active",
"users": [
{
"name": "bar",
"status": "a"
},
{
"name": "foo",
"status": "b"
}
]
}
Now, I wish to return only those records in which the user.name is "foo" and the user.status is "a" - That is I need to get Record 1 back since it satisfies my requirements.
How do I do the AND operation on the array within the record to achieve this?
I tried the following query:
{
"$and": [
{
"users.name": "foo"
},
{
"users.status": "a"
}
]
}
but I get both the records - I need only the first record (since in the users array, it has an entry where the name is "foo" AND the status is "a").
Any clues?
Ok, I found the answer out here
The solution would be to have a query as follows:
{
"users": {
"$elemMatch": {
"name": "foo",
"status": "a"
}
}
}
That did the trick!
Instead of individual field values you can also use whole objects in find queries. Try:
db.collection.find( {
users: {
"name":"foo",
"status":"a"
}
});
This should return all documents where the users array includes an object (or is an object) which looks as described. But note that this doesn't work when the object has additional fields.