How to ignore duplicate properties of other fields in the object? - node.js

I wrote a for loop that takes a table of data and creates 3,500 objects using this schema:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var LocationSchema = new Schema({
twp: {type: String, index: true, unique: true, dropDups: true},
rge: {type: String, unique: true},
});
module.exports = mongoose.model('Location', LocationSchema);
Let's say this is the first object created:
{
"_id" : ObjectId("56f5eb02683e79de61278449"),
"rge" : "008W",
"twp" : "004S",
"__v" : 0
}
If any other Objects are instantiated with the value twp: 004S, I would like the object to still be created, but have no value for twp. It would look like so:
{
"_id" : ObjectId("56f5eb02683e79de61274949"),
"rge" : "009E",
"__v" : 0
}
As you can see I experimented with adding unique, and dropDups, but this still created multiple objects with the same twp value. I also experimented with adding the code below (per someone's recommendation) , but still the same results.
var twpSet = new Set();
LocationSchema.methods.twp1 = function () {
var curTwp = this.twp;
if (twpSet.has(curTwp)) {
this.twp = undefined; // remove the twp field once duplicated
} else {
twpSet.add(curTwp); // save the existing twp value
}
};
LocationSchema.queue('twp1');

The problem is because if you don't include a specific field in a document but that field has a "unique" index on it then it is still considered for indexing purposes that the field has a value of null.
Citation:
If a document does not have a value for a field, the index entry for that item will be null in any index that includes it. Thus, in many situations you will want to combine the unique constraint with the sparse option. Sparse indexes skip over any document that is missing the indexed field, rather than storing null for the index entry. Since unique indexes cannot have duplicate values for a field, without the sparse option, MongoDB will reject the second document and all subsequent documents without the indexed field.
To combat this use the "sparse" property on the created index. Heres's a demonstration showing both cases:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.set("debug",true);
mongoose.connect('mongodb://localhost/test');
var sparseSchema = new Schema({
"a": { "type": String, "unique": true, "sparse": true },
"b": { "type": String, "unique": true }
});
var Sparse = mongoose.model('Sparsetest',sparseSchema);
async.eachSeries(
[{ "a": 1, "b": 2 },{ "a": 2 },{ "b": 3 },{ "b": 4 },{ "a": 3 }],
function(item,callback) {
Sparse.create(item,function(err,doc) {
console.log(doc);
callback(err);
});
},
function(err) {
if (err) throw err;
mongoose.disconnect();
}
);
The code is destined to error, but the output will show you what happens:
Mongoose: sparsetests.ensureIndex({ a: 1 }) { unique: true, sparse: true, background: true }
Mongoose: sparsetests.insert({ a: '1', b: '2', _id: ObjectId("56f706820c2db3902e557cff"), __v: 0 })
Mongoose: sparsetests.ensureIndex({ b: 1 }) { unique: true, background: true }
{ _id: 56f706820c2db3902e557cff, b: '2', a: '1', __v: 0 }
Mongoose: sparsetests.insert({ a: '2', _id: ObjectId("56f706820c2db3902e557d00"), __v: 0 })
{ _id: 56f706820c2db3902e557d00, a: '2', __v: 0 }
Mongoose: sparsetests.insert({ b: '3', _id: ObjectId("56f706820c2db3902e557d01"), __v: 0 })
{ _id: 56f706820c2db3902e557d01, b: '3', __v: 0 }
Mongoose: sparsetests.insert({ b: '4', _id: ObjectId("56f706820c2db3902e557d02"), __v: 0 })
{ _id: 56f706820c2db3902e557d02, b: '4', __v: 0 }
Mongoose: sparsetests.insert({ a: '3', _id: ObjectId("56f706820c2db3902e557d03"), __v: 0 })
undefined
^
MongoError: E11000 duplicate key error collection: test.sparsetests index: b_1 dup key: { : null }
So everything is fine for the first four documents created. The first document has unique values for both properties, the second does not duplicate "a" and the third and forth do not duplicate "b". Notably here the third and forth documents do not contain an "a" property, but since that property is "sparse" indexed this is not an issue.
The same is not true though of the "b" property, and when we try to insert another document where that property is considered null the duplicate key error is thrown for that index. Even though the "a" property was still unique the "b" already has null so it is not.
So where you want a "unique" index but you are not going to include the property in every document, you must turn on "sparse".
N.B The "dropDups" option is deprecated and a "no-op" in any release past MongoDB 3.x. It was never a good option to remove existing duplicates from the collection anyway. If you have duplicate data existing then you need a manual process to remove it.

Related

Mongoose unique if not null and if state

I have a unique index like this
code: {
type: String,
index: {
unique: true,
partialFilterExpression: {
code: { $type: 'string' }
}
},
default: null
},
state: { type: Number, default: 0 },
but When the state is 2 (archived) I want to keep the code, but it should be able to reuse the code, so it cannot be unique if state is 2.
Is there any away that I could accomplish this?
This is possible, though it's through a work around documented here https://jira.mongodb.org/browse/SERVER-25023.
In MongoDB 4.7 you will be able to apply different index options to the same field but for now you can add a non-existent field to separate the two indexes.
Here's an example using the work around.
(async () => {
const ItemSchema = mongoose.Schema({
code: {
type: String,
default: null
},
state: {
type: Number,
default: 0,
},
});
// Define a unique index for active items
ItemSchema.index({code: 1}, {
name: 'code_1_unique',
partialFilterExpression: {
$and: [
{code: {$type: 'string'}},
{state: {$eq: 0}}
]
},
unique: true
})
// Defined a non-unique index for non-active items
ItemSchema.index({code: 1, nonExistantField: 1}, {
name: 'code_1_nonunique',
partialFilterExpression: {
$and: [
{code: {$type: 'string'}},
{state: {$eq: 2}}
]
},
})
const Item = mongoose.model('Item', ItemSchema)
await mongoose.connect('mongodb://localhost:27017/so-unique-compound-indexes')
// Drop the collection for test to run correctly
await Item.deleteMany({})
// Successfully create an item
console.log('\nCreating a unique item')
const itemA = await Item.create({code: 'abc'});
// Throws error when trying to create with the same code
await Item.create({code: 'abc'})
.catch(err => {console.log('\nThrowing a duplicate error when creating with the same code')})
// Change the active code
console.log('\nChanging item state to 2')
itemA.state = 2;
await itemA.save();
// Successfully created a new doc with sama code
await Item.create({code: 'abc'})
.then(() => console.log('\nSuccessfully created a new doc with sama code'))
.catch(() => console.log('\nThrowing a duplicate error'));
// Throws error when trying to create with the same code
Item.create({code: 'abc'})
.catch(err => {console.log('\nThrowing a duplicate error when creating with the same code again')})
})();
This is not possible with using indexes. Even if you use a compound index for code and state there will still be a case where
new document
{
code: 'abc',
state: 0
}
archived document
{
code: 'abc',
state: 2
}
Now although you have the same code you will not be able to archive the new document or unarchive the archived document.
You can do something like this
const checkCode = await this.Model.findOne({code:'abc', active:0})
if(checkCode){
throw new Error('Code has to be unique')
}
else{
.....do something
}

MongoDB - Update/Insert the Array of object

I've one collection i.e 'comments', Which contain two fields. Like below -
Comments -
{
_id: 'unique_string',
dataVar: [{
name: {
type: string
},
value: {
type: string
}
}]
}
You can assume the collection data is like below -
[
_id: 'xxxxxx',
user: 'robin',
dataVar: [{
name: 'abc',
value: 123
}, {
name: 'bcd',
value: 12345
}]
]
Now the problem is - (I'm using mongoose in nodeJS application)
- How to update and insert the data in 'dataVar' ?
- If data is no available then new document should be created.
Case 1.
If user send the post data like
{
_id: 'xxxxxx',
user: 'robin',
dataVar: [{name: 'abc', value: 12345}]
}
then after performing query, the above document (whose _id is 'xxxxxx') should be update like below -
{
_id: 'xxxxxx',
user: 'robin',
dataVar: [
{name: 'abc', value: 12345},
{name: 'bcd', value: 12345}
]
}
Case 2.
If data is not present then new document should be created.
To update record in mongodb, you can use like
comments.findOneAndUpdate({ _id: 'xxxxxx' }, { $push: { dataVar: {$each: [{name: 'abc', value: 123}, {name: 'def', value: 456}] } } })
You can use {upsert: true} option for creating field in collection if not exist.
For updating record, you have to use sub-schema inside your main schema. So that _id field is created in every object of array. So you can uniquely identify object to update inside array.
comments.findOneAndUpdate({ "dataVar._id": 'xxxxxx' }, { $set: { name: 'xyz', value: 789 } })
In this way, you can update object, which is inside array.
If you don't want to use _id, you have at-least one field which contains unique values. So that you can findOneAndUpdate that specific object inside array.

Mongoose how to auto add _id to objects in array within collection item?

i have a mongo collection that looks like this:
{
name: string
_id: (auto set)
items: array[
name: string
url: string
items: array[
{
name: string,
url: string,
items: []
}
]
]
}
I'm using findByIdAndUpdate (with mongoose) to add an item into the items array:
Menu.findByIdAndUpdate(
req.body.parentid,
{
$push: {
items: {
name: req.body.item.name,
url: req.body.item.url,
items: []
}
}
},
{
safe: true,
upsert: true,
new: true
},
function(err, model) {
if (err !== null) {
console.log(err);
}
}
);
This works fine, but it does not add an _id to each object inserted into the items array. And i really need an id for each one.
I'm guessing it comes from the method used, findByIdAndUpdate as it looks more like an update rather than an insert. If my thinking is correct.
Using mongodb 3.2.10 and mongoose 4.7.6.
Any help would be really appreciated.
Thanks.
EDIT: the _id: (auto set) is not real, it's being automatically added via mongo. But just at the top level objects.
Found the solution in this thread: mongoDB : Creating An ObjectId For Each New Child Added To The Array Field
basically, added
var ObjectID = require('mongodb').ObjectID;
and then forcing the creation:
$push: {
items: {
_id: new ObjectID(),
name: req.body.item.name,
url: req.body.item.url,
items: []
}
}
You dont need to sepcify _id: (auto set) in mongoose schema it will automatically add unique _id with each document.
if you don't define _id in Schema, mongoose automatically add a _id to array item.
for example:
const countrySchema = new Schema({
name: {
type: String
},
cities: [
{
// don't define _id here.
name: String
}
],
});
now when you insert a row, the result is something like this:
{name : 'Iran', cities : [{_id : 6202902b45f0d858ac141537,name :
'Tabriz'}]}

How we find particular key value using populate method in node js and mongodb

I have two Schema with values in MONGODB. One is level schema and second is child schema. I am doing reference to level schema for particular key value. I storing levels 1 id to child schema DB using mongoose.Schema.Types.ObjectID{ref: level}. I am properly stored level
1 id in child schema using referencing. But when I populate the level 1 id querying to my child schema it shows my level 1 id null
1). This is my schema
usercurrLevelId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Level',
index: true
}
2). This is second schema
var LevelSchema = new Schema({
_id: { type: String },
age: { type: Number },
level_num: { type: Number },
min_score: { type: Number },
max_questions: { type: Number }
});
var Level = mongoose.model('Level', LevelSchema);
3). This is node js code
function(){
var query = {'_id':firstinfo._id};
var select = 'usercurrLevelId';
childQuizInfo.find(query,select).populate({
path: 'usercurrLevelId',
model: 'Level',
select: 'level_num'
}).exec(function(err,levelno){
if (err) return next(err);
res.send(levelno);
console.log(levelno);
});
}
4). The stored value in MONGODB
"_id" : ObjectId("57cb01e10797d129136f7997"),
"usercurrLevelId" : ObjectId("57c14943b78cd543fc59b7ee")
5). The output I got in console
[ { _id: 57cb01e10797d129136f7997, usercurrLevelId: null } ]

find and update one field in array of subdoc mongoose

i'm new in mongodb and mongoose. i'm using node js, and i want to find and update specific field in one of array in sub document.
here is my data structure :
var PostSchema = new Schema({
title: String,
comment: [
{
name: String,
value: String
}
],
createdAt: Date,
updateAt: Date
})
this is my data example :
{
_id : 12,
title : 'some article here...',
comment : [{
_id : 1,
name : 'joe',
value : 'wow that fantastic...'
},{
_id : 2,
name : 'rocky',
value : 'really... thats insane...'
},{
_id : 3,
name : 'jane',
value : 'i think its impossible to do...'
}],
createdAt : 2016-04-14 04:50:54.760Z,
updatedAt : 2016-04-14 04:50:54.760Z
}
i need to update comment which have _id : 2, i want to change the value become 'what???'. i try to update with :
Post.findOne({'_id': 12, 'comment._id': 2}, {_id: 0, 'comment.$': 1}, function (err, cb) {
cb.comment.value = 'what???'
cb.save()
})
but it failed... so, can you help me? Thanks
you can use findOneAndUpdate and positional operator $ to update specific comment value
Post.findOneAndUpdate({"_id": 12, "comment._id": 2},
{
$set: {
"comment.$.value ": "New value set here"
}
},
{ new: true } // return updated post
).exec(function(error, post) {
if(error) {
return res.status(400).send({msg: 'Update failed!'});
}
return res.status(200).send(post);
});
It will work for array updation in mongodb using mongoose
Post.findOneAndUpdate(
{ id: 12,comment_id:2 },
{ $push: { comment:"new comment" } }
)

Resources