how to push object except duplicate object in mongoDB - mongodb

I want to add an object to an array.
But I want to remove duplicate objects.
here is mongoDB JSON
{
"_id" : ObjectId("5e61ef7e003307047c13329d"),
"subject" : "Grammar",
"deletedYn" : false,
"answerList" : [
{
"_id" : ObjectId("5e61ef8c003307047c1332a4"),
"sentence" : "hi gui, nice to meet you",
"answer" : [
{
"_id" : ObjectId("5e61f15cd863e104a8f66770"),
"word" : "nice",
"index" : 3
}
]
},
{
"_id" : ObjectId("5e61ef8c003307047c1332a5"),
"sentence" : "how to make a project.",
"answer" : []
}
],
"passageId" : "123",
}
So I want to push answer in answerList.
Below is the code I created, but it contains duplicate objects.
const update = await PassageGrammar.updateOne(
{
_id: passageGrammarId,
answerList: { $elemMatch: { _id: sentenceId } },
},
{
$addToSet: {
'answerList.$.answer': {
word: answerList.word,
index: answerList.index,
},
},
},
);
but It is not working (it has duplicate set) like that:
"answer" : [
{
"_id" : ObjectId("5e61f15cd863e104a8f66770"),
"word" : "nice",
"index" : 3
},
{
"_id" : ObjectId("5e61f15cd863e104a8f66770"),
"word" : "nice",
"index" : 3
}
]
How can I change it?

Try Creating a unique index each time and droping the duplicates if they exist
const update = await PassageGrammar.updateOne(
{
_id: passageGrammarId,
answerList: { $elemMatch: { _id: sentenceId } },
},
{
$addToSet: {
'answerList.$.answer': {
word: answerList.word,
index: answerList.index,
},
},
},
{ unique:true, dropDups:true }
);

Related

I'm having trouble with a mongodb function that should be finding duplicates

I have the following function that is supposed to be returning the _id value of duplicates based on the email key. However, no matter what I've tried, I can't get the function to return anything other than any empty object. What am I missing here? Or is there a better approach I should be considering?
var duplicates = [];
db.medicallists
.aggregate([
{
$group: {
_id: {
email: "$email"
},
duplicate_ids: { $addToSet: "$_id" },
count: { $sum: 1 }
}
},
{
$match: {
count: { $gt: 1 }
}
},
], { allowDiskUse: true })
.forEach(function(doc) {
doc.duplicate_ids.shift(); // First element skipped for deleting
doc.duplicate_ids.forEach(function(dupId) {
duplicates.push(dupId); // Getting all duplicate ids
});
});
printjson(duplicates);
EDIT:
Here is a sample document:
{
_id : 5a2fed0c8023cf7ea2346067,
primary_spec : "Addiction Medicine",
first_name : "John",
last_name : "Sample",
city : "Las Vegas",
state : "NV",
phone : "1111111111",
fax : "1111111111",
email : "sample#aol.com"
}
I have tested your specific query on a similar data set and it works fine. printjson(duplicates); prints out all the duplicate ids.
Also this will, in fact, remove all the duplicate entries based on email:
db.collection.aggregate([
{
$group: {
_id: {
email: "$email"
},
duplicate_ids: {
$push: "$_id"
},
count: {
$sum: 1
}
}
},
{
$match: {
count: {
$gt: 1
}
}
},
]).forEach(function(doc){
doc.duplicate_ids.shift();
db.collection.remove({
_id: {
$in: doc.duplicate_ids
}
});
})
My starting set was:
{
"_id" : ObjectId("6014331de1ef9ab1f708ddd9"),
"item" : "card",
"email" : "zzz#yahoo.com"
}
{
"_id" : ObjectId("6014331de1ef9ab1f708ddda"),
"item" : "card",
"email" : "eee#yahoo.com"
}
{
"_id" : ObjectId("6014331de1ef9ab1f708dddb"),
"item" : "card",
"email" : "zzz#yahoo.com"
}
{
"_id" : ObjectId("6014331de1ef9ab1f708dddc"),
"item" : "card",
"email" : "aaa#yahoo.com"
}
After running the query, it turned to:
{
"_id" : ObjectId("6014331de1ef9ab1f708ddd9"),
"item" : "card",
"email" : "zzz#yahoo.com"
}
{
"_id" : ObjectId("6014331de1ef9ab1f708ddda"),
"item" : "card",
"email" : "eee#yahoo.com"
}
{
"_id" : ObjectId("6014331de1ef9ab1f708dddc"),
"item" : "card",
"email" : "aaa#yahoo.com"
}
Tested on MongoDB server version: 4.2.7

How can I correctly output an array of objects in the reverse order from mongodb?

Comments are saved in an array of objects. How can I correctly output them in reverse order (comments from newest to oldest)?
My db:
{"_id":{"$oid":"5e3032f14b82d14604e7cfb7"},
"videoId":"zX6bZbsZ5sU",
"message":[
{"_id":{"$oid":"5e3032f14b82d14604e7cfb8"},
"user":{"$oid":"5e2571ba388ea01bcc26bc96"},"text":"1"
},
{"_id":{"$oid":"5e3032f14b82d14604e7cfb9"},
"user":{"$oid":"5e2571ba388ea01bcc26bc96"},"text":"2"
},
....
]
My sheme Mongoose:
const schema = new Schema({
videoId: { type: String, isRequired: true },
message: [
{
user: { type: Schema.Types.ObjectId, ref: 'User' },
text: { type: String }
},
]
});
My code:
const userComments = await Comment.find(
{ videoId: req.query.videoId },
{ message: { $slice: [skip * SIZE_COMMENT, SIZE_COMMENT] } }
)
.sort({ message: -1 })
.populate('message.user', ['avatar', 'firstName']);
but sort not working;
thanks in advance!
You can simply use $reverseArray to reverse content of an array.
db.collection.aggregate([
{
$addFields:
{
message: { $reverseArray: "$message" }
}
}
])
Collection Data :
/* 1 */
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb7"),
"videoId" : "zX6bZbsZ5sU",
"message" : [
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb8"),
"user" : ObjectId("5e2571ba388ea01bcc26bc96"),
"text" : "1"
},
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb9"),
"user" : ObjectId("5e2571ba388ea01bcc26bc96"),
"text" : "2"
}
]
}
/* 2 */
{
"_id" : ObjectId("5e309318d02e05b694b0b25f"),
"videoId" : "zX6bZbsZ5sUNEWWWW",
"message" : [
{
"_id" : ObjectId("5e3032f14b82d14604e7cfc9"),
"user" : ObjectId("5e2571ba388ea01bcc26bc87"),
"text" : "Old"
},
{
"_id" : ObjectId("5e3032f14b82d14604e7cfd0"),
"user" : ObjectId("5e2571ba388ea01bcc26bc87"),
"text" : "New"
}
]
}
Result :
/* 1 */
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb7"),
"videoId" : "zX6bZbsZ5sU",
"message" : [
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb9"),
"user" : ObjectId("5e2571ba388ea01bcc26bc96"),
"text" : "2"
},
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb8"),
"user" : ObjectId("5e2571ba388ea01bcc26bc96"),
"text" : "1"
}
]
}
/* 2 */
{
"_id" : ObjectId("5e309318d02e05b694b0b25f"),
"videoId" : "zX6bZbsZ5sUNEWWWW",
"message" : [
{
"_id" : ObjectId("5e3032f14b82d14604e7cfd0"),
"user" : ObjectId("5e2571ba388ea01bcc26bc87"),
"text" : "New"
},
{
"_id" : ObjectId("5e3032f14b82d14604e7cfc9"),
"user" : ObjectId("5e2571ba388ea01bcc26bc87"),
"text" : "Old"
}
]
}
Your Query : You can use native MongoDB's $lookup instead of .populate() , So try below :
Comments.aggregate([
{
$addFields:
{
message: { $reverseArray: "$message" }
}
}, {
$lookup: {
from: "User",
let: { ids: "$message.user" },
pipeline: [
{
$match: { $expr: { $in: ["$_id", "$$ids"] } }
},
{ $project: { avatar: 1, firstName: 1, _id: 0 } }
],
as: "userData"
}
}
])
You're going to want to use one of two things:
The MongoDB aggregation framework
Sorting within the application
If you choose to use the MongoDB aggregation framework, you'll likely want to use the $unwind operation to expand the array into separate documents, then sorting those documents with the $sort operation.
You can see more on how to do that in this ticket:
how to sort array inside collection record in mongoDB
You could also do this within your application by first executing a query, and then sorting each of the arrays in the result set.
Best,

Update nested array element in mongodb

In my case I have mongodb document with nested arrays and I need to update attributes array elements. My mongodb document as follows.
{
"_id" : ObjectId("5dca9c5ece5b91119746eece"),
"updatedAt" : ISODate("2019-11-20T11:48:55.339Z"),
"attributeSet" : [
{
"attributeSetName" : "test-0",
"type" : "Product",
"id" : "1574158032603",
"updatedAt" : ISODate("2019-11-19T10:10:53.783Z"),
"createdAt" : ISODate("2019-11-19T10:07:20.084Z"),
"attributes" : [
{
"attributeName" : "test-attribute",
"defaultValue" : 123,
"isRequired" : false,
"id" : "1574221129398"
},
{
"attributeName" : "test-attribute-02",
"defaultValue" : 456,
"isRequired" : false,
"id" : "1574250533840"
}
]
},
{
"attributeSetName" : "test-1",
"type" : "Product",
"id" : "1574158116355",
"updatedAt" : ISODate("2019-11-19T10:08:37.251Z"),
"createdAt" : ISODate("2019-11-19T10:08:37.251Z"),
"attributes" : []
}
]
}
I need to update element in attributes array and get the updates document into result object. This is what I tried so far.
const result = await this.model.findOneAndUpdate(
{
_id: settingsToBeUpdated._id,
"attributeSet": {
$elemMatch: {
"attributeSet.id": attributeSetId,
"attributes": {
$elemMatch: {
'attributes.id': id
}
}
}
}
},
{
$set: {
'attributeSet.$[outer].attributes.$[inner].attributeName': attributeDto.attributeName,
'attributeSet.$[outer].attributes.$[inner].defaultValue': attributeDto.defaultValue,
'attributeSet.$[outer].attributes.$[inner].isRequired': attributeDto.isRequired,
}
},
{
"arrayFilters": [
{ "outer.id": attributeSetId },
{ "inner.id": id }
]
}
);
It does not update the model. I refered to this link, but it does not help.
Any suggestion would be appreciated.
Couple of rectification required on the query, otherwise its almost there. The update is not working because $elemMatch for attributeSet (array of docs) field is to happen on id property of those docs to filter and not on attributeSet.id, it woudn't figure what it is. And nested elemMatch is not required, simply use dot notation.
To debug you can try it out with a find query.
Query (Shell):
db.collection.findOneAndUpdate(
{
_id: settingsToBeUpdated._id,
attributeSet: {
$elemMatch: {
id: attributeSetId,
"attributes.id": id
}
}
},
{
$set: {
"attributeSet.$[as].attributes.$[a].attributeName":
attributeDto.attributeName,
"attributeSet.$[as].attributes.$[a].defaultValue":
attributeDto.defaultValue,
"attributeSet.$[as].attributes.$[a].isRequired": attributeDto.isRequired
}
},
{
arrayFilters: [{ "as.id": attributeSetId }, { "a.id": id }],
returnNewDocument: true
}
);

MongoDB nested group?

I'm trying to implement a nested group query in mongodb and I'm getting stuck trying to add the outer group by. Given the below (simplified) data document:
{
"timestamp" : ISODate(),
"category" : "movies",
"term" : "my movie"
}
I'm trying to achieve a list of all categories and within the categories there should be the top number of terms. I would like my output something like this:
[
{ category: "movies",
terms: [ { term: "movie 1", total: 5000 }, { term: "movie 2", total: 200 } ... ]
},
{ category: "sports",
terms: [ { term: "football 1", total: 4000 }, { term: "tennis 2", total: 250 } ... ]
},
]
My 'inner group' is as shown below, and will get the top 5 for all categories:
db.collection.aggregate([
{ $match : { "timestamp": { $gt: ISODate("2014-08-27") } } },
{ $group : { _id : "$term", total : { $sum : 1 } } },
{ $sort : { total : -1 } },
{ $limit: 5 }
]);
// Outputs:
{ "_id" : "movie 1", "total" : 943 }
{ "_id" : "movie 2", "total" : 752 }
How would I go about implementing the 'outer group'?
Additionally sometimes the above aggregate]ion returns a null value (not all documents have a term value). How do I go about ignoring the null values?
thanks in advance
You will need two groups in this case. The first group generates a stream of documents with one document per term and category:
{ $group : {
_id : {
category: "$category",
term: "$term",
},
total: { $sum : 1 }
}
}
A second group will then merge all documents with the same term into one, using the $push operator to merge the categories into an array:
{ $group : {
_id : "$_id.category",
terms: {
$push: {
term:"$_id.term",
total:"$total"
}
}
}
}
Query:
db.getCollection('orders').aggregate([
{$match:{
tipo: {$regex:"[A-Z]+"}
}
},
{$group:
{
_id:{
codigo:"1",
tipo:"$tipo",
},
total:{$sum:1}
}
},
{$group:
{
_id:"$_id.codigo",
tipos:
{
$push:
{
tipo:"$_id.tipo",
total:"$total"
}
},
totalGeneral:{$sum:"$total"}
}
}
]);
Response:
{
"_id" : "1",
"tipos" : [
{
"tipo" : "TIPO_01",
"total" : 13.0
},
{
"tipo" : "TIPO_02",
"total" : 2479.0
},
{
"tipo" : "TIPO_03",
"total" : 12445.0
},
{
"tipo" : "TIPO_04",
"total" : 12445.0
},
{
"tipo" : "TIPO_05",
"total" : 21.0
},
{
"tipo" : "TIPO_06",
"total" : 21590.0
},
{
"tipo" : "TIPO_07",
"total" : 1065.0
},
{
"tipo" : "TIPO_08",
"total" : 562.0
}
],
"totalGeneral" : 50620.0
}

adding data in fields of a single document in mongodb

I have this data:
_id : 1
status:1,
name:"name",
details:
{
crm:115,
webs:
{ tag:"blog" , url:"http://..."},
contacts:
{
_id:1,
name:"me",
phones:
{ tag:"home", number:"123..." },
{tag:"mobile", number:"123456789"}
}
}
I want one more entry in "phones" with {tag:office", number:"9823..."}
What would be the command/query for that?
You can easily push this into the array with the following query (I had to modify the JSON you pasted, as it was not valid a little):
db.collection.drop();
db.collection.insert( {
_id : 1,
status: 1,
name: "name",
details: {
crm:115,
webs: {
tag:"blog",
url:"http://..."
},
contacts: {
_id: 1,
name: "me",
phones: [
{ tag: "home", number: "123..." },
{ tag:"mobile", number:"123456789" }
]
}
}
} );
db.collection.update(
{ _id: 1 },
{ $push : { 'details.contacts.phones' : { tag:"office", rname:"9823" } } }
);
db.collection.find().pretty();
{
"_id" : 1,
"details" : {
"contacts" : {
"_id" : 1,
"name" : "me",
"phones" : [
{
"tag" : "home",
"number" : "123..."
},
{
"tag" : "mobile",
"number" : "123456789"
},
{
"tag" : "office",
"rname" : "9823"
}
]
},
"crm" : 115,
"webs" : {
"tag" : "blog",
"url" : "http://..."
}
},
"name" : "name",
"status" : 1
}
The value of the $push operator must refer to the array to be updated. So when the array field is embedded in other documents you need to use dot notation like this:
db.abcd.update({_id: 1},
{$push: {"details.contacts.phones": {tag:"office", rname:"9823"}}});