Hello how to change an id of document to diffrent when some ids are equal?
I want to change some of employees id_director to object id thats is equal to id_director. I mean for example, when some document have id_director = 100 then changed it to _id with value of employee where id_employee = 100.
I was trying like this:
var employes = db.employees.find({"id_director": {$ne: null}});
while (employes.hasNext()) {
emp = employes.next();
employe = db.employees.findOne({"id_director":emp.id_employe});
emp.id_director = employe._id
db.employees.save(emp)
}
For example
I have two documents in one collection:
employees
{
"_id" : ObjectId("6224a5767b9cdbdcb681b1ef"),
"id_employe" : 180,
"id_director" : 100,
"name" : "Mark"
}
{
"_id" : ObjectId("6224a5767b9cdbdcb681b1f0"),
"id_employe" : 100,
"id_director" : null,
"name" : "Peter"
}
Expected document:
{
"_id" : ObjectId("6224a5767b9cdbdcb681b1ef"),
"id_employe" : 180,
"id_director" : ObjectId("6224a5767b9cdbdcb681b1f0"),
"name" : "Mark"
}
first of all you need to have peter's id then update mark document:
var peter= db.employees.findOne({_id: peterId});
db.employees.updateOne({
_id: markId
}, {
$set: {
id_director: peter._id
}
})
You can perform a self $lookup to lookup for the director_id, then perform some wrangling and $merge back to the collection.
db.collection.aggregate([
{
"$match": {
"id_director": {
$ne: null
}
}
},
{
"$lookup": {
"from": "collection",
"localField": "id_director",
"foreignField": "id_employe",
pipeline: [
{
"$limit": 1
},
{
$project: {
_id: 1
}
}
],
"as": "id_director"
}
},
{
$unwind: "$id_director"
},
{
$project: {
_id: 1,
id_employe: 1,
id_director: "$id_director._id",
name: 1
}
},
{
"$merge": {
"into": "collection",
"on": "_id",
"whenMatched": "replace"
}
}
])
Here is the Mongo playground for your reference.
Related
I have a collection myCollection
{
name : String,
members: [{status : Number, memberId : {type: Schema.Types.ObjectId, ref: 'members'}]
}
with this data :
"_id" : ObjectId("5e8b0bac041a913bc608d69d")
"members" : [
{
"status" : 4,
"_id" : ObjectId("5e8b0bac041a913bc608d69e"),
"memberId" : ObjectId("5e7dbf5b257e6b18a62f2da9"),
"date" : ISODate("2020-04-06T10:59:56.997Z")
},
{
"status" : 1,
"_id" : ObjectId("5e8b0bf2041a913bc608d6a3"),
"memberId" : ObjectId("5e7e2f048f80b46d786bfd67"),
"date" : ISODate("2020-04-06T11:01:06.463Z")
}
],
and a collection members
{
firstname : String
lastname : String
}
with this data :
[{
"_id" : ObjectId("5e7dbf5b257e6b18a62f2da9"),
"firstname" : "raed",
"lastname" : "besbes"
},
{
"_id" : ObjectId("5e7e2f048f80b46d786bfd67"),
"firstname" : "sarra",
"lastname" : "besbes"
}]
I make a query with aggregate and $lookup, to have the data populated but I want to restrict returned
data on status 1 only, this is my query and result.
how can I get data populated with only status 1 members returned ? Thank you.
query
db.getCollection('myCollection').aggregate([
{ $match: { _id: ObjectId("5e8b0bac041a913bc608d69d")}},
{
"$lookup": {
"from": "members",
"localField": "members.memberId",
"foreignField": "_id",
"as": "Members"
}
},
{
$project: {
"Members.firstname" : 1,
"Members.lastname" : 1,
"Members._id" : 1,
},
}
])
result
{
"_id" : ObjectId("5e8b0bac041a913bc608d69d"),
"Members" : [
{
"_id" : ObjectId("5e7dbf5b257e6b18a62f2da9"),
"firstname" : "raed",
"lastname" : "besbes"
},
{
"_id" : ObjectId("5e7e2f048f80b46d786bfd67"),
"firstname" : "sarra",
"lastname" : "besbes"
}
]
}
Option 1
Filter members before $lookup
db.myCollection.aggregate([
{
$match: {
_id: ObjectId("5e8b0bac041a913bc608d69d")
}
},
{
$addFields: {
members: {
$filter: {
input: "$members",
cond: {
$eq: [
"$$this.status",
1
]
}
}
}
}
},
{
"$lookup": {
"from": "members",
"localField": "members.memberId",
"foreignField": "_id",
"as": "Members"
}
},
{
$project: {
"Members.firstname": 1,
"Members.lastname": 1,
"Members._id": 1
}
}
])
MongoPlayground
Option 2
(Similar to 1) We flatten member array, filter only status = 1 and then perform $lookup.
db.myCollection.aggregate([
{
$match: {
_id: ObjectId("5e8b0bac041a913bc608d69d")
}
},
{
"$unwind": "$members"
},
{
$match: {
"members.status": 1
}
},
{
"$lookup": {
"from": "members",
"localField": "members.memberId",
"foreignField": "_id",
"as": "Members"
}
},
{
"$unwind": "$Members"
},
{
$group: {
_id: "$_id",
Members: {
$push: "$Members"
}
}
}
])
MongoPlayground
Option 3
We can apply filter for Member array based on filtered values for member array.
db.myCollection.aggregate([
{
$match: {
_id: ObjectId("5e8b0bac041a913bc608d69d")
}
},
{
"$lookup": {
"from": "members",
"localField": "members.memberId",
"foreignField": "_id",
"as": "Members"
}
},
{
$project: {
Members: {
$filter: {
input: "$Members",
cond: {
$in: [
"$$this._id",
{
$let: {
vars: {
input: {
$filter: {
input: "$members",
cond: {
$eq: [
"$$this.status",
1
]
}
}
}
},
in: "$$input.memberId"
}
}
]
}
}
}
}
}
])
MongoPlayground
I am facing one issue where I need to find ObjectId which is basically a user Id and need to perform a lookup from other collection called "tb_user" from that user Id.
I tried many times but not found a optimised way.
below I is the detail what I tried.
first match the conditions.
perform projection which matches only one object from the list.
unwind subtask array
remove unwanted(only in this operation) keys form object.
again convert to array of object which contain key value pairs.
remove the values from object.
unwind again subtask.
In project take userId from keys.
convert it to objectId.
perform the lookup from tb_user collection.
at last get user information.
db.getCollection('tb_task').aggregate([
{
"$match": { "$and": [{ "_id" : ObjectId("5d766ac6f3195ed8e7361b62")},
{'sub_tasks.name': "task_1_19Z50DZYDV"}] },
},
{
"$project": {
"sub_tasks": {
"$filter": {
"input": "$sub_tasks",
"as": "sub_task",
"cond": {
"$and":{ "$eq": [
"$$sub_task.name","task_1_19Z50DZYDV"
]
}
}
}
}
}
},
{
"$unwind": {
"path": "$sub_tasks",
"preserveNullAndEmptyArrays": true
}
},
{
"$project": {
"_id":0,
"sub_tasks.name":0,
"sub_tasks.total_queries":0,
"sub_tasks.total_query_result_pairs":0,
"sub_tasks.total_assigned_to":0
}
},
{ $project: { "sub_tasks" : { $objectToArray: "$sub_tasks" } } },
{ $project: { "sub_tasks.v" : 0} },
{
"$unwind": {
"path": "$sub_tasks",
"preserveNullAndEmptyArrays": true
}
},
{ $project: { "user_id" : "$sub_tasks.k"}},
{
"$addFields": {
"user_id": {
"$toObjectId": "$user_id"
}
}
},
{ "$lookup": {
"from": "tb_user",
"localField": "user_id",
"foreignField": "_id",
"as": "user"
}
},
{
"$unwind": {
"path": "$user",
preserveNullAndEmptyArrays": true
}
},
{ $project: { "user_name" : "$user.first_name",
"is_active" : "$user.is_active",
"user_id" : "$user._id",
}
}
])
Actual collection schema
{
"_id" : ObjectId("5d766ac6f3195ed8e7361b62"),
"name" : "task_1",
"sub_tasks" : [
{
"name" : "task_1_19Z50DZYDV",
"total_queries" : 10,
"total_query_result_pairs" : 20,
"total_assigned_to" : 2,
"5d6e387524d8bd317909806a" : {
"assigned_time" : ISODate("2019-09-09T15:41:26.000Z"),
"finished_time" : null,
"is_finished" : false,
"total_queries_rated" : 0
},
"5d6e387524d8bd31790ab4" : {
"assigned_time" : ISODate("2019-09-09T15:41:26.000Z"),
"finished_time" : null,
"is_finished" : false,
"total_queries_rated" : 0
}
}
]
}
Excepted output
{
"user_name" : "Heey",
"is_active" : true,
"user_id" : ObjectId("5d6e387524d8bd317909806a")
},
{
"user_name" : "ram",
"is_active" : true,
"user_id" : ObjectId("5d6e387524d8bd31790ab4")
}
Since _id is unique for each document, you can apply just
{
"$match": {
"_id" : ObjectId("5d766ac6f3195ed8e7361b62")
}
}
And also it will return only one document, no need to use filter in $project instead, use :
{
$project:{
"sub_tasks":1
}
}
and after unwind match need to be added so that it will send you the documents which contain the "sub_task.name" = "task_1_19Z50DZYDV".
{
$match:{
"sub_task.name":"task_1_19Z50DZYDV"
}
}
Rest of the stages seem good to me.
So, the final query will look like this:
db.getCollection('tb_task').aggregate([
{
"$match": {
"_id" : ObjectId("5d766ac6f3195ed8e7361b62")
}
},
{
$project:{
"sub_tasks":1
}
},
{
"$unwind": {
"path": "$sub_tasks",
"preserveNullAndEmptyArrays": true
}
},
{
$match:{
"sub_task.name":"task_1_19Z50DZYDV"
}
},
{
"$project": {
"_id":0,
"sub_tasks.name":0,
"sub_tasks.total_queries":0,
"sub_tasks.total_query_result_pairs":0,
"sub_tasks.total_assigned_to":0
}
},
{ $project: { "sub_tasks" : { $objectToArray: "$sub_tasks" } } },
{ $project: { "sub_tasks.v" : 0} },
{
"$unwind": {
"path": "$sub_tasks",
"preserveNullAndEmptyArrays": true
}
},
{ $project: { "user_id" : "$sub_tasks.k"}},
{
"$addFields": {
"user_id": {
"$toObjectId": "$user_id"
}
}
},
{ "$lookup": {
"from": "tb_user",
"localField": "user_id",
"foreignField": "_id",
"as": "user"
}
},
{
"$unwind": {
"path": "$user",
preserveNullAndEmptyArrays": true
}
},
{ $project: { "user_name" : "$user.first_name",
"is_active" : "$user.is_active",
"user_id" : "$user._id",
}
}
])
And for understanding more tips of optimizing the aggregation pipelines refer here
The aggregate was executed.
I got the results using lookup, but I need a sort.
In addition, I want to assign an index to the result value.
CollectionA :
{
"_id" : ObjectId("5a6cf47415621604942386cd"),
"contents" : [
ObjectId("AAAAAAAAAAAAAAAAAAAAAAAA"),
ObjectId("BBBBBBBBBBBBBBBBBBBBBBBB")
],
"name" : "jason"
}
CollectionB :
{
"_id" : ObjectId("AAAAAAAAAAAAAAAAAAAAAAAA")
"title" : "a title",
"date" : 2018-01-02
},
{
"_id" : ObjectId("BBBBBBBBBBBBBBBBBBBBBBBB")
"title" : "a title",
"date" : 2018-01-01
}
Query:
db.getCollection('A').aggregate([
{
$match : { "_id" : ObjectId("5a6cf47415621604942386cd") }
},
{
$lookup : {
from: "B",
localField: "contents",
foreignField: "_id",
as: "item"
}
},
{ $sort: { "item.date" : -1 } }
]);
Want Result:
{
"_id" : ObjectId("5a6cf47415621604942386cd"),
"contents" : [
{
"_id" : ObjectId("BBBBBBBBBBBBBBBBBBBBBBBB")
"title" : "a title",
"date" : 2018-01-01,
"index" : 0
},
{
"_id" : ObjectId("AAAAAAAAAAAAAAAAAAAAAAAA")
"title" : "a title",
"date" : 2018-01-02,
"index" : 1
}],
"name" : "jason"
}
The current problem does not apply to the sort.
And I don't know how to designate an index.
Below Aggregation may you. For your desire result.
db.CollectionA.aggregate([
{
$match: { "_id": ObjectId("5a6cf47415621604942386cd") }
},
{
$lookup: {
from: "CollectionB",
let: { contents: "$contents" },
pipeline: [
{
$match: { $expr: { $in: ["$_id", "$$contents"] } }
},
{ $sort: { date: 1 } }
],
as: "contents"
}
},
{
$project: {
contents: {
$map: {
input: { $range: [0, { $size: "$contents" }, 1 ] },
as: "element",
in: {
$mergeObjects: [
{ index: "$$element" },
{ $arrayElemAt: [ "$contents", "$$element" ]}
]
}
}
}
}
}
])
One way to go about it would be to unwind the array, sort it and then group it back
db.A.aggregate([
{
$match: {
"_id": ObjectId("5a6cf47415621604942386cd")
}
},
{
$lookup: {
from: "B",
localField: "contents",
foreignField: "_id",
as: "item"
}
},
{
$unwind: "$item"
},
{
$sort: {
"item.date": -1
}
},
{
$group: {
_id: "$_id",
contents: {
$push: "$item"
}
}
}
])
Another method is, (this is applicable only if the date field corresponds to the document creation date),
db.A.aggregate([
{
$match: {
"_id": ObjectId("5a6cf47415621604942386cd")
}
},
{
$lookup: {
from: "B",
localField: "contents",
foreignField: "_id",
as: "item"
}
},
{
$sort: {
"item": -1
}
}
])
Basically, this sorts on the basis of _id, and since _id is created using the creation date, it should sort accordingly.
We have a DB structure similar to the following:
Pet owners:
/* 1 */
{
"_id" : ObjectId("5baa8b8ce70dcbe59d7f1a32"),
"name" : "bob"
}
/* 2 */
{
"_id" : ObjectId("5baa8b8ee70dcbe59d7f1a33"),
"name" : "mary"
}
Pets:
/* 1 */
{
"_id" : ObjectId("5baa8b4fe70dcbe59d7f1a2a"),
"name" : "max",
"owner" : ObjectId("5baa8b8ce70dcbe59d7f1a32")
}
/* 2 */
{
"_id" : ObjectId("5baa8b52e70dcbe59d7f1a2b"),
"name" : "charlie",
"owner" : ObjectId("5baa8b8ce70dcbe59d7f1a32")
}
/* 3 */
{
"_id" : ObjectId("5baa8b53e70dcbe59d7f1a2c"),
"name" : "buddy",
"owner" : ObjectId("5baa8b8ee70dcbe59d7f1a33")
}
I need a list of all pet owners and additionally the number of pets they own. Our current query looks similar to the following:
db.getCollection('owners').aggregate([
{ $lookup: { from: 'pets', localField: '_id', foreignField: 'owner', as: 'pets' } },
{ $project: { '_id': 1, name: 1, numPets: { $size: '$pets' } } }
]);
This works, however it's quite slow and I'm asking myself if there's a more efficient way to perform the query?
[update and feedback] Thanks for the answers. The solutions work, however I can unfortunately see no performance improvement compared to the query given above. Obviously, MongoDB still needs to scan the entire pet collection. My hope was, that the owner index (which is present) on the pets collection could somehow be exploited for getting just the counts (not needing to touch the pet documents), but this does not seem to be the case.
Are there any other ideas or solutions for a very fast retrieval of the 'pet count' beside explicitly storing the count within the owner documents?
In MongoDB 3.6 you can create custom $lookup pipeline and count instead of entire pets documents, try:
db.owners.aggregate([
{
$lookup: {
from: "pets",
let: { ownerId: "$_id" },
pipeline: [
{ $match: { $expr: { $eq: [ "$$ownerId", "$owner" ] } } },
{ $count: "count" }
],
as: "numPets"
}
},
{
$unwind: "$numPets"
}
])
You can try below aggregation
db.owners.aggregate([
{ "$lookup": {
"from": "pets",
"let": { "ownerId": "$_id" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$$ownerId", "$owner" ] }}},
{ "$count": "count" }
],
"as": "numPets"
}},
{ "$project": {
"_id": 1,
"name": 1,
"numPets": { "$ifNull": [{ "$arrayElemAt": ["$numPets.count", 0] }, 0]}
}}
])
I am puzzled as to why the code below doesn't work. Can anyone explain, please?
For some context: My goal is to get the score associated with an answer option for a survey database where answers are stored in a separate collection from the questions. The questions collection contains an array of answer options, and these answer options have a score.
Running this query:
db.answers.aggregate([
{
$match: {
userId: "abc",
questionId: ObjectId("598be01d4efd70a81c1c5ad4")
}
},
{
$lookup: {
from: "questions",
localField: "questionId",
foreignField: "_id",
as: "question"
}
},
{
$unwind: "$question"
},
{
$unwind: "$question.options"
},
{
$unwind: "$answers"
}
])
I get:
{
"_id" : ObjectId("598e588e0c5e24452c9ee769"),
"userId" : "abc",
"questionId" : ObjectId("598be01d4efd70a81c1c5ad4"),
"answers" : {
"id" : 20
},
"question" : {
"_id" : ObjectId("598be01d4efd70a81c1c5ad4"),
"options" : {
"id" : 10,
"score" : "12"
}
}
}
{
"_id" : ObjectId("598e588e0c5e24452c9ee769"),
"userId" : "abc",
"questionId" : ObjectId("598be01d4efd70a81c1c5ad4"),
"answers" : {
"id" : 20
},
"question" : {
"_id" : ObjectId("598be01d4efd70a81c1c5ad4"),
"options" : {
"id" : 20,
"score" : "4"
}
}
}
All great. If I now add to the original query a match that's supposed to find the answer option having the same id as the answer (e.g. questions.options.id == answers.id), things don't work as I would expect.
The final pipeline is:
db.answers.aggregate([
{
$match: {
userId: "abc",
questionId: ObjectId("598be01d4efd70a81c1c5ad4")
}
},
{
$lookup: {
from: "questions",
localField: "questionId",
foreignField: "_id",
as: "question"
}
},
{
$unwind: "$question"
},
{
$unwind: "$question.options"
},
{
$unwind: "$answers"
},
{
$match: {
"question.options.id": "$answers.id"
}
},
{
$project: {
_id: 0,
score: "$question.options.score"
}
}
])
This returns an empty result. But if I change the RHS of the $match from "$answers.id" to 20, it returns the expected score: 4. I tried everything I could think of, but couldn't get it to work and can't understand why it doesn't work.
I was able to get it to work with the following pipeline:
{
$match: {
userId: "abc",
questionId: ObjectId("598be01d4efd70a81c1c5ad4")
}
},
{
$lookup: {
from: "questions",
localField: "questionId",
foreignField: "_id",
as: "question"
}
},
{
$unwind: "$question"
},
{
$unwind: "$question.options"
},
{
$unwind: "$answers"
},
{
$addFields: {
areEqual: { $eq: [ "$question.options.id", "$answers.id" ] }
}
},
{
$match: {
areEqual: true
}
},
{
$project: {
_id: 0,
score: "$question.options.score"
}
}
I think the reason it didn't work with a direct match is the fact that questions.options.id doesn't actually reference the intended field... I needed to use $questions.options.id which wouldn't work as a LHS of a $match, hence the need to add an extra helper attribute.