Aggregating filter for Key - mongodb

If I have a document as follows:
{
"_id" : ObjectId("54986d5531a011bb5fb8e0ee"),
"owner" : "54948a5d85f7a9527a002917",
"type" : "group",
"deleted" : false,
"participants" : {
"54948a5d85f7a9527a002917" : {
"last_message_id" : null
},
"5491234568f7a9527a002917" : {
"last_message_id" : null
}
"1234567aaaa7a9527a002917" : {
"last_message_id" : null
}
},
}
How do I do a simple filter for all documents this have participant "54948a5d85f7a9527a002917"?
Thanks

Trying to query structures like this does not work well. There are a whole whole host of problems with modelling like this, but the most clear problem is using "data" as the names for "keys".
Try to think a little RDBMS like, at least in the concepts of the limitations to what a database cannot or should not do. You wouldn't design a "table" in a schema that had something like "54948a5d85f7a9527a002917" as the "column" name now would you? But this is essentially what you are doing here.
MongoDB can query this, but not in an efficient way:
db.collection.find({
"participants.54948a5d85f7a9527a002917": { "$exists": true }
})
Naturally this looks for the "presence" of a key in the data. While the query form is available, it does not make efficient use of such things as indexes where available as indexes apply to "data" and not the "key" names.
A better structure and approach is this:
{
"_id" : ObjectId("54986d5531a011bb5fb8e0ee"),
"owner" : "54948a5d85f7a9527a002917",
"type" : "group",
"deleted" : false,
"participants" : [
{ "_id": "54948a5d85f7a9527a002917" },
{ "_id": "5491234568f7a9527a002918" },
{ "_id": "1234567aaaa7a9527a002917" }
]
}
Now the "data" you are looking for is actual "data" associated with a "key" ( possibly ) and inside an array for binding to the parent object. This is much more efficient to query:
db.collection.find({
"participants._id": "54948a5d85f7a9527a002917"
})
It's much better to model that way than what you are presently doing and it makes sense to the consumption of objects.
BTW. It's probably just cut and paste in your question but you cannot possibly duplicate keys such as "54948a5d85f7a9527a002917" as you have. That is a basic hash rule that is being broken there.

Related

How to set the value of a field if it doesn't exist in a document in mongodb and keep it as it is if it is already present?

So assume I have a document as shown below
{
"_id" : ObjectId("6336d94e0330f5d48e44fb0f"),
"systemId" : "5124301",
"userId" : "9876543210",
"tempId" : "123456da87sdafasdf",
"updatedAt" : ISODate("2022-09-30T12:01:11.714+0000"),
"receivedAt" : ISODate("2022-04-10T23:15:08.145+0000"),
}
Now I already have a temp ID assigned to the doc and sometimes that field might expire and not exist inside the document. I wanted to know if I'm updating the document with a different receivedAt parameter or any other parameter and it does not have a tempId then only assign it a tempId else let the tempId be as it is.
What should be the query for this to get the updated docs as given by two examples?
Case 1: If tempId exists:
{
"_id" : ObjectId("6336d94e0330f5d48e44fb0f"),
"systemId" : "5124301",
"userId" : "1234567890",
"tempId" : "123456da87sdafasdf",
"updatedAt" : ISODate("2022-09-30T12:01:11.714+0000"),
"receivedAt" : ISODate("2022-04-10T23:15:08.145+0000"),
}
Case 2: If there is no temp Id and it is generated as 13qeqrwrqwtqrfsdfweqr in above lines and document needs to be updated with the generated tempId.
{
"_id" : ObjectId("6336d94e0330f5d48e44fb0f"),
"systemId" : "5124301",
"userId" : "1234567890",
"tempId" : "13qeqrwrqwtqrfsdfweqr",
"updatedAt" : ISODate("2022-09-30T12:01:11.714+0000"),
"receivedAt" : ISODate("2022-04-10T23:15:08.145+0000"),
}
Query would be something like this
findOneAndUpdate({
systemId: "5124301"
},
{
{
$set: {
userId: "1234567890",
receivedAt : ISODate("2022-04-10T23:15:08.145+0000"),
tempId: {
$exists: then leave it as is, else update it with 13qeqrwrqwtqrfsdfweqr
}
}
}
})
Work with update with aggregation pipeline. Use $cond operator to check whether tempId is not equal ($ne) to undefined.
If true, remain existing value $tempId.
If false, assign value: "13qeqrwrqwtqrfsdfweqr".
findOneAndUpdate({
systemId: "5124301"
},
[
{
$set: {
userId: "1234567890",
receivedAt: ISODate("2022-04-10T23:15:08.145Z"),
tempId: {
$cond: {
if: {
$ne: [
"$tempId",
undefined
]
},
then: "$tempId",
else: "13qeqrwrqwtqrfsdfweqr"
}
}
}
}
])
Demo # Mongo Playground
I agree with #Yong Shun that using an aggregation pipeline to describe the transformation is the correct way to approach this. I offer an alternative syntax below just for general reference though both will satisfy the request as stated in the question perfectly fine.
Mainly just leaving an additional answer as I'm curious about why this workflow around tempId exists. What do they represent, why can the coexist with userIds, and why is the application generating a new one for each of these write operations even if one may already exist? One thing that comes to mind is that you can construct your filter predicates for your update to include references to tempId (perhaps to only have the application generate a new one if needed). But more importantly I suspect that the entire workflow with tempId should be simplified, but that would require more specific knowledge about the application to say for sure.
With respect to the alternative syntax, the tempId portion of the pipeline can be simplified to use the $ifNull operator:
tempId: { $ifNull: [ "$tempId", "13qeqrwrqwtqrfsdfweqr" ]
Full demo playground here.

Query on all descendants of node

Given a collection of MongoDb documents with a property "myContacts" like this:
{
"_id": 123,
"myContacts" : {
"contacts" : {
"10" : {
"_id" : NumberLong(10),
"name" : "c1",
"prop" : true
},
"20" : {
"_id" : NumberLong(20),
"name" : "c2"
},
}
}
}
I want to select all documents, where at least one contact lacks the "prop" field.
I figured out a general query:
db.getCollection('xyz').find({ 'myContacts.contacts.???.prop': { $exists: false } })
The problem is that IDs of the contacts are part of the path and I cannot know them ahead. I want sth like 'myContacts.contacts.$anyChild.prop', but cannot find sth similar in the mongo docs.
Does it mean there is no way to do it?
PS: I cannot change the document structure, a live app uses it. I've spent some time with Google and my bet it's not possible. I however would like an opinion from people who have experience with Mongo.
Thank you guys for helpful comments, this got me going! I could get the results I wanted with:
db.getCollection('xyz').aggregate([{$project: {_id:1, contacts:{$objectToArray: "$myContacts.contacts"}}}, {$match: {"contacts.v.prop" : null}}])

How do I query a hash sub-object that is dynamic in mongodb?

I currently have a Question object and am not sure how to query for it?
{ "title" : "Do you eat fast food?"
"answers" : [
{
"_id" : "506b422ff42c95000e00000d",
"title" : "Yes",
"trait_score_modifiers" : {
"hungry" : 1
}
},
{
"_id" : "506b422ff42c95000e00000e",
"title" : "No",
"trait_score_modifiers" : {
"not-hungry" : -1
}
}]
}
I am trying to find questions where the trait_score_modifieres is queried (sometimes it exists, sometimes not)
I have the following but it is not dynamic:
db.questions.find({"answers.trait_score_modifiers.not-hungry":{$exists: true}})
How could i do something like this?
db.questions.find({"answers.trait_score_modifiers.{}.size":{$gt: 0}})
You should modify the schema so you have consistent key names to query on. I ran into a similar problem using the aggregation framework, see question: Total values from all keys in subdocument
Something like this should work (not tested):
{
"title" : "Do you eat fast food?"
"answers" : [
{
"title" : "Yes",
"trait_score_modifiers" : [
{"dimension": "hungry", "value": 1}
]
},
{
"title" : "No",
"trait_score_modifiers" : [
{"dimension": "not-hungry", "value": -1}
]
}]
}
You can return all questions that have a dynamic dimension (e.g. "my new dimension") with:
db.questions.find("answers.trait_score_modifiers.dimension": "my new dimension")
Or limit the returned set to questions that have a specific value on that dimension (e.g. > 0):
db.questions.find(
"answers.trait_score_modifiers": {
"$elemMatch": {
"dimension": "my new dimension",
"value": {"$gt": 0}
}
}
)
Querying nested arrays can be a bit tricky, be sure to read up on the documentation In this case, $elemMatch is needed because otherwise you return a document that has some trait_score_modifier my new dimension but the matching value is in the dimension key of a different array element.
You need $elemMatch criteria in your query.
Refer to: http://docs.mongodb.org/manual/reference/projection/elemMatch/
Let me know if you need the query.

query the value of a sub document in mongodb

I'm using the Java driver withe document that looks like this (a real test example):
{
"_id" : ObjectId("5207fe359b88bfa6f90a82b0"),
"meta_id" : "d6eb1b13-50c7-473f-8348-b5a638a542a0",
"name" : "Fake Name Inc.",
"created" : ISODate("2013-08-11T21:12:21.533Z"),
"members" : {
"5207fe359b88bfa6f90a82af" : [
"Admin",
"User"
]
}
}
I want to select the string array at the path "members.5207fe359b88bfa6f90a82af" (which is a list of roles).
I'm at a loss as to how to do that. It looks like a projection would work here, but I'm new enough to Mongo that the way the projection is written is not obvious.
I can of course load the whole object or maybe even just the "members" field, but I think I should be able to select just exactly the data I'm after.
So, does anyone have an idea of how such a query would be written?
Note: This question suggests that maybe I need to change the structure of the document to make things easier: MongoDB - Query by sub-tree
You can use dot notation in the projection parameter of find to do this. In the shell:
db.test.find(
{_id : ObjectId("5207fe359b88bfa6f90a82b0")},
{'members.5207fe359b88bfa6f90a82af': 1, _id: 0})
Returns:
{
"members": {
"5207fe359b88bfa6f90a82af": [
"Admin",
"User"
]
}
}

Retrieving only the relevant part of a stored document

I'm a newbie with MongoDB, and am trying to store user activity performed on a site. My data is currently structured as:
{ "_id" : ObjectId("4decfb0fc7c6ff7ff77d615e"),
"activity" : [
{
"action" : "added",
"item_name" : "iPhone",
"item_id" : 6140,
},
{
"action" : "added",
"item_name" : "iPad",
"item_id" : 7220,
}
],
"name" : "Smith,
"user_id" : 2
}
If I want to retrieve, for example, all the activity concerning item_id 7220, I would use a query like:
db.find( { "activity.item_id" : 7220 } );
However, this seems to return the entire document, including the record for item 6140.
Can anyone suggest how this might be done correctly? I'm not sure if it's a problem with my query, or with the structure of the data itself.
Many thanks.
You have to wait the following dev: https://jira.mongodb.org/browse/SERVER-828
You can use $slice only if you know insertion order and position of your element.
Standard queries on MongoDb always return all document.
(question also available here: MongoDB query to return only embedded document)