Mongo collection structure migration - mongodb

I'm a relative newcomer to Mongodb and I've hit a situation that I don't quite see how I resolve. I have this field definition in a mongoose schema:
account_number: {
type: String,
index: false,
required: false
},
Which I am converting to
account_number: [
{
type: String,
index: false,
required: false
}
],
Which is fine, the point I am stuck on is now converting the data still held in the previous definition into the new array format. How would I do this on the command line?
Edit:
I have this now:
db.customers.find().snapshot().forEach(function (elem) {
db.customers.update({_id: elem._id},{$unset: {account_number: 1}});
db.customers.update({_id: elem._id},{$push: {account_number: elem.account_number}});});
Which is recreating the property as an array, but it is blank.

OK so this is what I got working:
db.customers.find().forEach(function (elem) {
db.customers.update({_id: elem._id}, {$unset: {account_number: 1}});
db.customers.update({_id: elem._id}, {$push: {account_number: elem.account_number}});
});
For anyone who finds this

Related

Use GraphQL Query to get results form MongoDB after aggregation with mongoose

so i have following problem.
I have a mongoDB collection and a corresponding mongoose model which looks like this.
export const ListItemSchema = new Schema<ListItemSchema>({
title: { type: String, required: true },
parentId: { type: Schema.Types.ObjectId, required: false },
});
export const TestSchema = new Schema<Test>(
{
title: { type: String, required: true },
list: { type: [ListItemSchema], required: false },
}
);
As you can see, my TestSchema holds an Array of ListItems inside -> TestSchema is also my Collection in MongoDB.
Now i want to query only my ListItems from a Test with a specific ID.
Well that was not that big of a problem at least from the MongoDB side.
I use MongoDB Aggregation Framework for this and call my aggregation inside a custom Resolver.
Here is the code to get an array of only my listItems from a specific TestModel
const test = TestModel.aggregate([
{$match: {_id: id}},
{$unwind: "$list"},
{
$match: {
"list.parentId": {$eq: null},
},
},
{$replaceRoot: {newRoot: "$list"}},
]);
This is the result
[ { _id: randomId,
title: 't',
parentId: null },
{ _id: randomId,
title: 'x'
parentId: null
} ]
The Query to trigger the resolver looks like this and is placed inside my Test Type Composer.
query getList {
test(testId:"2f334575196fe042ea83afbf", parentId: null) {
title
}
}
So far so good... BUT! Ofc my query will fail or will result in a not so good result^^ because GraphQL expects data based on the Test-Model but receives a completely random array.
So after a lot of typing here is the question:
How do i have to change my query to receive the list array?
Do i have to adjust the query or is it something with mongoose?
i really stuck at this point so any help would be awesome!
Thanks in advance :)
I'm not sure if I understood your issue correctly.
In your graphql, try to leave out exclamation mark(!) from the Query type.
something like :
type Query {
test: TestModel
}
instead of
type Query {
test: TestModel!
}
then you'll get the error message in console but still be able to receive any form of data.

How to add a position field on each document of a subdocument array with mongodb aggregations?

I'm currently working on a matching algorithm that calculates a score between two differents products lists.
I'm using mongodb aggregations to add a matching-score field calculated with differents arguments. Actually, the algorithm needs to know the position of each product in the list. So I thought to dynamically add a position field on each product to use it later.
I don't know which mongodb aggregation operator I have to use. Maybe someone can help me ?
Here is my list model :
const listSchema = mongoose.Schema({
type: { type: mongoose.Schema.Types.ObjectId, ref: "ListType", required: true },
links: [{
brand: { type: mongoose.Schema.Types.ObjectId, ref: "Brand", required: true },
work: { type: mongoose.Schema.Types.ObjectId, ref: "Product" },
}],
associatedUser: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true }
}, { versionKey: false });
In advance thank you,
Pierre
One method is to use the $reduce operator, which can be done in an aggregation pipeline, or as of MongoDB 4.2 in an update.
This example replaces an 'arrayField' containing
["Hello","World"]
with an array of objects like
[{index:0,value:"Hello"},{index:1,value:"World"}]
db.collection.update({},[{$set:{
arrayField:{
$reduce:{
input:"$arrayField",
initialValue:[],
in:{ $push:[
"$$value",
{index:{$size:"$$value"},
value:"$$this"}
]}
}
}
}}])

MongoDB findOneAndUpdate projection

I'm currently using MongoDB and using the findOneAndUpdate method. I'm trying to use the projection however it doesn't seem to be working 100% successfully.
Here is the projection:
{
orderID: '$_id',
_id: false,
user: true,
guild: true,
order: true,
status: true,
orderExpiry: true,
priority: true,
sentAt: true
}
As you can see, I'm trying to set orderID to the value of _id however, it doesn't do anything.
Here is the code I am executing for reference :
await this.db.collection('orders').findOneAndUpdate(filter, { $set: { ...data } },
{ returnOriginal: false, projection: this.getProjectionFields() });
I hope someone can help me, thank you!
As far as I know projection with .find() or similar .findOneAndUpdate() does not support field transformations(adding new field out of existing) which $project in aggregation is capable of doing. So projection can be used just to include or exclude certain fields from result. Although this is true but up-to certain extent this is false, we can transform an array field using projection operators, Check : projection.

Updating the path 'x' would create a conflict at 'x'

This error happens when I tried to update upsert item:
Updating the path 'x' would create a conflict at 'x'
Field should appear either in $set, or in $setOnInsert. Not in both.
I had the same problem while performing an update query using PyMongo.
I was trying to do:
> db.people.update( {'name':'lmn'}, { $inc : { 'key1' : 2 }, $set: { 'key1' : 5 }})
Notice that here I'm trying to update the value of key1 from two MongoDB Update Operators.
This basically happens when you try to update the value of a same key with more than one MongoDB Update Operators within the same query.
You can find a list of Update Operators over here
If you pass the same key in $set and in $unset when updating an item, you will get that error.
For example:
const body = {
_id: '47b82d36f33ad21b90'
name: 'John',
lastName: 'Smith'
}
MyModel.findByIdAndUpdate(body._id, { $set: body, $unset: {name: 1}})
// Updating the path 'name' would create a conflict at 'name'
You cannot have the same path referenced more than once in an update. For example, even though the below would result in something logical, MongoDB will not allow it.
db.getCollection("user").updateOne(
{_id: ...},
{$set: {'address': {state: 'CA'}, 'address.city' : 'San Diego'}}
)
You would get the following error:
Updating the path 'address.city' would create a conflict at 'address'
db.products.update(
{ _id: 1 },
{
$set: { item: "apple" },
$setOnInsert: { defaultQty: 100 }
},
{ upsert: true }
)
Below is the key explanation to the issue:
MongoDB creates a new document with _id equal to 1 from the
condition, and then applies the $set AND $setOnInsert operations to
this document.
If you want a field value is set or updated regardless of insertion or update, use it in $set. If you want it to be set only on insertion, use it in $setOnInsert.
Here is the example: https://docs.mongodb.com/manual/reference/operator/update/setOnInsert/#example
Starting from MongoDB 4.2 you can use aggregate pipelines in update:
db.your_collection.update({
_id: 1
},
[{
$set:{
x_field: {
$cond: {
if: {$eq:[{$type:"$_id"} , "missing"]},
then: 'upsert value', // it's the upsert case
else: '$x_field' // it's the update case
}
}
}
}],
{
upsert: true
})
db.collection.bulkWrite() also supports it
With the Ruby library at least, it's possible to get this error if you have the same key twice, once as a symbol and once as a string:
db.getCollection("user").updateOne(
{_id: ...},
{$set: {'name': "Horse", name: "Horse"}}
)
I recently had the same issue while using the query below.
TextContainer.findOneAndUpdate({ blockId: req.params.blockId, 'content._id': req.params.noteId }, { $set: { 'content.note': req.body.note } }, { upsert: true, new: true })
When i have changed 'content.note' to 'content.$.note' it has been fixed. So my final query is :
TextContainer.findOneAndUpdate({ blockId: req.params.blockId, 'content._id': req.params.noteId }, { $set: { 'content.$.note': req.body.note } }, { upsert: true, new: true })

mongoose: myModel.find always return subdocument

var myschema = new Schema({
name: {type:String, default:'fullname'},
subdoc: {
day1: {type:Array, default:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},
day2: {type:Array, default:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}
}
});
var mymodel = Mongoose.model('mytest',myschema);
//mongoose 3.5.6: find
mymodel.find({},{'name'}, function(err,docs){
logger.info("---> " + docs);
});
results:
---> { _id: 512da190ba48050f2e000001, **subdoc: {}**, name: 'fullname' }
Only name field is requested to be returned, but this function always returns subdoc: {}. Can someone explain this?
use mongodb shell, it looks fine
db.mytests.find({},{"name":1})
{ "_id" : ObjectId("512da190ba48050f2e000001"), "name" : "fullname" }
Then I change the model to:
var myschema = new Schema({
name: {type:String, default:'fullname'},
subdoc: [
day1: {type:Array, default:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},
day2: {type:Array, default:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}
] // **note: not {} here**
})
use the same mongoose api find, the result is just what I expected:
--->{ _id: 512da46fffebd24b30000002, name: 'fullname' }
My questions is: Why field 'subdoc' is returned with the former schema?
According to the docs, it appears as if you are a little bit off.
Example:
// name LIKE john and only selecting the "name" and "friends" fields, executing immediately
MyModel.find({ name: /john/i }, 'name friends', function (err, docs) { })
With that, I would do something like this:
myModel.find( {}, 'name', callback);
Edit
To address your comments: In mongodb console, the proper way to format it would be {'name':1, 'friend':1}. Using:
db.collection.find({}, {'name friend'})
will throw an error. To do this properly, you would do:
db.collection.find({}, {'name':1, 'friend':1})
However, if you prefer this way of field selection, mongoose does allow you to do that as well.
myModel.find({}, {'name':1, 'friend': 1}, callback);
See: API Docs