search query on array elements in mongodb - mongodb

I am new to mongodb and still learning it so my question can be naive so please bear with it :)
I have only one json object in mongodb which looks like this.
json object
{
"URLStore": [
{
"description": "adf description",
"url": "www.adf.com"
},
{
"description": "pqr description",
"url": "www.pqr.com"
},
{
"description": "adf description",
"url": "www.adf.com"
}
]
}
I need to query description for url which matches given input. e.g here www.adf.com . I have a code which queries mongodb
mongodb query
BasicDBObject whereQuery = new BasicDBObject();
whereQuery.put("URLStore.url","www.pqr.com");
BasicDBObject fields=new BasicDBObject("URLStore.description", "");
cursor = collection.find(whereQuery,fields);
but the result is something like
{
"_id": {
"$oid": "554b4046e4b072dd9deaf277"
},
"URLStore": [
{
"description": "pqr description"
},
{
"description": "adf description"
},
{
"description": "adf description"
}
]
}
Actually only 1 description should have returned as matching objects with key www.pqr.com is only one. What is wrong with my query? m I missing something here ?
I have already tried question Retrieve only the queried element in an object array in MongoDB collection but using solution mentioned there will return only one object / first match

Use the following aggregation pipeline, should give you the desired results:
db.collection.aggregate([
{
"$match": {
"URLStore.url": "www.adf.com"
}
},
{
"$unwind": "$URLStore"
},
{
"$match": {
"URLStore.url": "www.adf.com"
}
},
{
"$group": {
"_id": {
"url": "$URLStore.url",
"description": "$URLStore.description"
}
}
},
{
"$project": {
"_id": 0,
"description": "$_id.description"
}
}
])

Related

MongoDB use array field's element to $set a new field of the document

In the database, I have documents like the following
Ticket {
"eventHistory": [
{
"event": "CREATED",
"timestamp": "aa-bb-cccc"
},
{
"event": "ASSIGNED",
"timestamp": "ii-jj-kkkk"
},
...
{
"event": "CLOSED",
"timestamp": "xx-yy-zzzz"
}
]
}
I would like to add a closedAt field to the relevant Tickets, getting the value from the eventHistory array's last element. The resultant document would look like the following
Ticket {
"eventHistory": [
{
"event": "CREATED",
"timestamp": "aa-bb-cccc"
},
{
"event": "ASSIGNED",
"timestamp": "ii-jj-kkkk"
},
...
{
"event": "CLOSED",
"timestamp": "xx-yy-zzzz"
}
],
"closedAt": "xx-yy-zzzz"
}
The following pipeline allows me to use the entire object that's present as the eventHistory array's last element.
db.collection.updateMany(
<query>,
[
"$set": {
"closedAt": {
"$arrayElemAt": [
"$eventHistory",
-1
]
}
}
]
...
)
But I want to use only the timestamp field; not the entire object.
Please help me adjust (and/or improve) the pipeline.
One option to fix your query is:
db.collection.updateMany(
<query>,
[
{
$set: {
"Ticket.closedAt": {
$last: "$Ticket.eventHistory.timestamp"
}
}
}
])
See how it works on the playground example
But note that you assume that last item is a closing one. Is this necessarily the case? Otherwise you can validate it.

MongoDB: find if ID exist in array of objects

I was wondering, is there a way in MongoDB to get all the documents from one collection excluding those that exists in another, without using aggregation.
Let me explain myself more;
I have a collection of "Users", and another collection where there is an array of objects.
Something like this:
User
}
"_id": "61e6bbe49d7efc57f895ab50",
"name": "User 1"
},
{
"_id": "61e6b9239d7efc57f895ab02",
"name": "User 2"
},
{
"_id": "61cae6176d0d9a36efd8f190",
"name": "User 3"
},
{
"_id": "61cade886d0d9a36efd8f11a",
"name": "User 4"
},
The other collection looks like this:
{
users: [
{
user: {
"_id": "61e6b9239d7efc57f895ab02",
"name": "User 2",
},
...
},
{
user: {
"_id": "61cae6176d0d9a36efd8f190",
"name": "User 3",
},
...
},
],
},
I would like to get all the users in "array 1" excluding those in "array 2".
So the result should be:
[
{
"_id": "61e6b9239d7efc57f895ab02",
"name": "User 1"
},
{
"_id": "61cae6176d0d9a36efd8f190",
"name": "User 4"
},
],
So, is there a way to do that without the need to do aggregation.
you can use the $and like this:
const found = await User.find({
$and: [
{ _id: { $in: array1 } },
{ _id: { $nin: array2 } }
]
});
For anyone looking for a solution, if you want to add new element to an array of objects, where you, somehow, forgot to initialize the _id field, remember you have to, because mongo adds that field automatically (to use it for filtering/indexation).
All you have to do, is to write something like this;
const data = Data.create({
_id: mongoose.Types.ObjectId.createFromHexString(uid),
users: [{ user: userId, initiator: true, _id: userId}],
})
and for the searching, you write what you always write;
const found = await User.find({ _id: { $nin: data.users } });

MongoDB delete embedded documents through array of Ids

I am working on a Node.js application that is using a MongoDB database with Mongoose. I've been stuck in this thing and didn't come up with the right query.
Problem:
There is a collection named chats which contain embedded documents (rooms) as an array of objects. I want to delete these embedded documents (rooms) through Ids which are in the array.
{
"_id": "ObjectId(6138e2b55c175846ec1e38c5)",
"type": "bot",
"rooms": [
{
"_id": "ObjectId(6138e2b55c145846ec1e38c5)",
"genre": "action"
},
{
"_id": "ObjectId(6138e2b545c145846ec1e38c5)",
"genre": "adventure"
}
]
},
{
"_id": "ObjectId(6138e2b55c1765846ec1e38c5)",
"type": "person",
"rooms": [
{
"_id": "ObjectId(6138e2565c145846ec1e38c5)",
"genre": "food"
},
{
"_id": "ObjectId(6138e2b5645c145846ec1e38c5)",
"genre": "sport"
}
]
},
{
"_id": "ObjectId(6138e2b55c1765846ec1e38c5)",
"type": "duo",
"rooms": [
{
"_id": "ObjectId(6138e21c145846ec1e38c5)",
"genre": "travel"
},
{
"_id": "ObjectId(6138e35645c145846ec1e38c5)",
"genre": "news"
}
]
}
I am converting my array of ids into MongoDB ObjectId so I can use these ids as match criteria.
const idsRoom = [
'6138e21c145846ec1e38c5',
'6138e2565c145846ec1e38c5',
'6138e2b545c145846ec1e38c5',
];
const objectIdArray = idsRoom.map((s) => mongoose.Types.ObjectId(s));
and using this query for the chat collection. But it is deleting the whole document and I only want to delete the rooms embedded document because the ids array is only for the embedded documents.
Chat.deleteMany({ 'rooms._id': objectIdArray }, function (err) {
console.log('Delete successfully')
})
I really appreciate your help on this issue.
You have to use $pull operator in a update query like this:
This query look for documents where exists the _id into rooms array and use $pull to remove the object from the array.
yourModel.updateMany({
"rooms._id": {
"$in": [
"6138e21c145846ec1e38c5",
"6138e2565c145846ec1e38c5",
"6138e2b545c145846ec1e38c5"
]
}
},
{
"$pull": {
"rooms": {
"_id": {
"$in": [
"6138e21c145846ec1e38c5",
"6138e2565c145846ec1e38c5",
"6138e2b545c145846ec1e38c5"
]
}
}
}
})
Example here.
Also you can run your query without the query parameter (in update queries the first object is the query) like this and result is the same. But is better to indicate mongo the documents using this first object.

Cannot create an array of nested object using a project operation

I'm forced to use the aggregation framework and the project operation of Spring Data MongoDb.
What I'd like to do is creating an array of object as a result of a project operation.
For example, considering this intermediate aggregation result:
[
{
"content": {
"processes": [
{
"id": "101a",
"title": "delivery"
},
{
"id": "101b",
"title": "feedback"
}
]
}
}
]
What I want to obtain is this:
[
{
"results":
{
"titles": [
{
"id": "101a",
"value": "delivery"
},
{
"id": "101b",
"value": "feedback"
}
]
}
}
]
This was just an example, I don't want to simply "rename" some fields.
What I want is the possibility to create an array of objects.
If I try something like this:
projectionOperation
.and("$content.processes.id").as("results.titles.id")
.and("$content.processes.title").as("results.titles.value");
I obtain this:
[
{
"results":
{
"titles": {
"id": ["101a", "101b"]
"value": ["delivery", "feedback"]
}
}
}
}
]
With this projection the array is created, but not "in the proper position".
However, If I use the nested operator, I haven't figure out a way to specify that I want to create an array instead of an object.
With this projection:
projectionOperation.and("results.titles")
.nested(
bind("id", "process.id")
.and("value", "process.title")
);
I can create a proper nested object but not into an array:
"results.titles": {
"id": "101b",
"value": "feedback"
}
You can try below aggregation code.
ProjectionOperation po = Aggregation.project().and(
VariableOperators.mapItemsOf("content.processes").as("rt")
.andApply(
new AggregationExpression() {
#Override
public Document toDocument(AggregationOperationContext aggregationOperationContext) {
return new Document("id", "$$rt.id").append("value", "$$rt.title");
}
}
)
).as("result");

MongoDb aggregation project onto collection

I've a problem with a huge MongoDb aggregation pipeline. I've many constraint and I've simplified the problem a lot. Hence, don't discuss the goal for this query.
I've a mongo aggregation that gives something similar to this:
[
{
"content": {
"processes": [
{
"id": "101a",
"title": "delivery"
},
{
"id": "101b",
"title": "feedback"
}
]
}
}
]
To this intermediate result I'm forced to apply a project operation in order to obtain something similar to this:
[
{
"results":
{
"titles": [
{
"id": "101a",
"value": "delivery"
},
{
"id": "101b",
"value": "feedback"
}
]
}
}
]
enter code here
But applying this projections:
"results.titles.id": "$content.processes.id",
"results.titles.value": "$content.processes.title"
I obtain this:
[
{
"results":
{
"titles": {
"id": ["101a", "101b"]
"value": ["delivery", "feedback"]
}
}
}
}
]
Collection are created but not in the proper position.
Is it possible to exploit some operator inside the project operation in order to tell mongo to create an array in a parent position?
Something like this:
"results.titles.$[x].value" : "$content.processes.value"
You can use the dot notation to project entire array:
db.col.aggregate([
{
$project: {
"results.titles": "$content.processes"
}
}
])
and if you need to rename title to value then you have to apply $map operator:
db.col.aggregate([
{
$project: {
"results.titles": {
$map: {
input: "$content.processes",
as: "process",
in: {
id: "$$process.id",
value: "$$process.title"
}
}
}
}
}
])