How to migrate Legacy Realm JSON data to MongoDB Realm (Atlas) - problem with relationships - mongodb

I'm trying to migrate from Legacy Realm to MongoDB Realm and I'm having trouble migrating data with relationships.
Example schema:
{
name: 'Location',
primaryKey: '_id',
properties: {
_id: 'string',
_partition: 'string?',
Name: 'string',
WorkTime: 'WorkTime[]',
},
}
{
name: 'WorkTime',
primaryKey: '_id',
properties: {
_id: 'string',
_partition: 'string?',
Day: 'string?',
Open: 'string?',
Closed: 'string?',
},
}
In what format should I push the json to MongoDB Atlas, so it links with the related data?
I tried adding the Ids (with and without partition string) from my WorkTime collection, following https://docs.mongodb.com/manual/tutorial/model-referenced-one-to-many-relationships-between-documents/
Example JSON:
{
"_id":"adaaade7-2b4f-5342-92a1-f00857a61253"
"_partition":"TestPartition",
"Name":"Test",
"WorkTime":[
{
"_partition":"TestPartition",
"_id":"1229ac83-f390-00e3-14a7-ffa9f8eb36e4"
},
{
"_partition":"TestPartition",
"_id":"9af60ff2-064b-3f09-6621-e7f949b003be"
},
{
"_partition":"TestPartition",
"_id":"04c33a8c-5e59-45ab-fbf8-e70446324475"
},
],
}
And when I try to fetch the data from realm (realm.objects('Location')), I don't get anything. Locations show up only when WorkTime is empty.
I also tried passing the whole WorkTime object, instead of Id, but then it creates a copy of the full object.

I figure it out, the referenced objects need to be Id strings, like in the example. https://docs.mongodb.com/manual/tutorial/model-referenced-one-to-many-relationships-between-documents/
So, if you have
{
"_id":"adaaade7-2b4f-5342-92a1-f00857a61253"
"_partition":"TestPartition",
"Test": {"_id":"9af60ff2-064b-3f09-6621-e7f949b003be", "_partition":"TestPartition"},
"WorkTime":[
{
"_partition":"TestPartition",
"_id":"1229ac83-f390-00e3-14a7-ffa9f8eb36e4"
},
],
}
should be converted to
{
"_id":"adaaade7-2b4f-5342-92a1-f00857a61253"
"_partition":"TestPartition",
"Test": "9af60ff2-064b-3f09-6621-e7f949b003be",
"WorkTime":[
"1229ac83-f390-00e3-14a7-ffa9f8eb36e4"
],
}
Leaving only the Ids as strings.
I iterated though the objects recursively, deleted non-id props of nested objects, replaced Id with _id and added partition key. After that you have to replace the nested objects with string {"_id":"9af60ff2-064b-3f09-6621-e7f949b003be"} -> 9af60ff2-064b-3f09-6621-e7f949b003be.

Related

Prisma - adding new object into existing array and sorting it afterwards

I'm using Prisma in combination with MongoDB and this is how I currently update existing document:
await prisma.H2H.update({
where: { id: existingH2HDocument.id },
data: {
matches: {
push: {
matchId: editInput.matchId,
leagueId: editInput.leagueId,
date: editInput.date,
season: editInput.season,
},
},
},
})
Basically my model of the document looks like this:
id: 1234
matches: [
{
object1
},
{
object2
}
...
]
I'm adding new objects into the matches array, is there a way how to sort it during the update?
Using push adds them to the bottom but I need the newly added objects on the top(so basically an unshift).

How to define mongoose schema for nested documents

I need to define the mongoose schema for for nested documents which is given below.
Documents:
"Options":[{"Value":["28","30","32","34","36","38","40","42","44","46"],"_id":{"$oid":"5de8427af55716115dd43c8f"},"Name":"Size"},{"Value":["White"],"_id":{"$oid":"5de8427af55716115dd43c8e"},"Name":"Colour"}]
I was declaring like below but its not working.
const Product = new Schema(
{
Options: [{ value: { _id: ObjectId, Name: String } }]
},
{
timestamps: {
createdAt: "createdAt",
updatedAt: "updatedAt"
},
collection: "products"
}
);
Here I need the schema where if i will directly add/update the same document then it will be added.
You need to modify your schema like this :
{
Options: [ new Schema ({ value: [...], _id: Schema.Types.ObjectId, Name: String })]
}
This is the way to create an array of subdocuments with Mongoose. If you don't use the "new Schema" key words, you are actually creating a field with type "Mixed", which needs a different way to handle updates.
You can also omit the _id, it should be added automatically.
You can find more information on subdocument on this page :
https://mongoosejs.com/docs/subdocs.html
...and on mixed type fields : https://mongoosejs.com/docs/schematypes.html#mixed
...which will explain shortly the problem.
{
Options: [ new Schema ({ _id: mongoose.Types.ObjectId(),value: [String], Name: String } })]
}

Pulling/deleting an item from a nested array

Note: it's a Meteor project.
My schema looks like that:
{
_id: 'someid'
nlu: {
data: {
synonyms:[
{_id:'abc', value:'car', synonyms:['automobile']}
]
}
}
}
The schema is defined with simple-schema. Relevant parts:
'nlu.data.synonyms.$': Object,
'nlu.data.synonyms.$._id': {type: String, autoValue: ()=> uuidv4()},
'nlu.data.synonyms.$.value': {type:String, regEx:/.*\S.*/},
'nlu.data.synonyms.$.synonyms': {type: Array, minCount:1},
'nlu.data.synonyms.$.synonyms.$': {type:String, regEx:/.*\S.*/},
I am trying to remove {_id:'abc'}:
Projects.update({_id: 'someid'},
{$pull: {'nlu.data.synonyms' : {_id: 'abc'}}});
The query returns 1 (one doc was updated) but the item was not removed from the array. Any idea?
This is my insert query
db.test.insert({
"_id": "someid",
"nlu": {
"data": {
"synonyms": [
{
"_id": "abc"
},
{
"_id": "def"
},
10,
[ 5, { "_id": 5 } ]
]
}
}
})
And here is my update
db.test.update(
{
"_id": "someid",
"nlu.data.synonyms._id": "abc"
},
{
"$pull": {
"nlu.data.synonyms": {
"_id": "abc"
}
}
}
)
The problem broke down to the autoValue parameter on your _id property.
This is a very powerful feature to manipulate automatic values on your schema. However, it prevented from pulling as it had always returned a value, indicating that this field should be set.
In order to make it aware of the pulling, you can make it aware of an operator being present (as in cases of mongo updates).
Your autoValue would then look like:
'nlu.data.synonyms.$._id': {type: String, autoValue: function(){
if (this.operator) {
this.unset();
return;
}
return uuidv4();
}},
Edit: Note the function here being not an arrow function, otherwise it losses the context that is bound on it by SimpleSchema.
It basically only returns a new uuid4 when there is no operator present (as in insert operations). You can extend this further by the provided functionality (see the documentation) to your needs.
I just summarized my code to a reproducable example:
import uuidv4 from 'uuid/v4';
const Projects = new Mongo.Collection('PROJECTS')
const ProjectSchema ={
nlu: Object,
'nlu.data': Object,
'nlu.data.synonyms': {
type: Array,
},
'nlu.data.synonyms.$': {
type: Object,
},
'nlu.data.synonyms.$._id': {type: String, autoValue: function(){
if (this.operator) {
this.unset();
return;
}
return uuidv4();
}},
'nlu.data.synonyms.$.value': {type:String, regEx:/.*\S.*/},
'nlu.data.synonyms.$.synonyms': {type: Array, minCount:1},
'nlu.data.synonyms.$.synonyms.$': {type:String, regEx:/.*\S.*/},
};
Projects.attachSchema(ProjectSchema);
Meteor.startup(() => {
const insertId = Projects.insert({
nlu: {
data: {
synonyms:[
{value:'car', synonyms:['automobile']},
]
}
}
});
Projects.update({_id: insertId}, {$pull: {'nlu.data.synonyms' : {value: 'car'}}});
const afterUpdate = Projects.findOne(insertId);
console.log(afterUpdate, afterUpdate.nlu.data.synonyms.length); // 0
});
Optional Alternative: Normalizing Collections
However there is one additional note for optimization.
You can work around this auto-id generation issue by normalizing synonyms into an own collection, where the mongo insert provides you an id. I am not sure how unique this id will be compared to uuidv4 but i never faced id issues with that.
A setup could look like this:
const Synonyms = new Mongo.Collection('SYNONYMS');
const SynonymsSchema = {
value: {type:String, regEx:/.*\S.*/},
synonyms: {type: Array, minCount:1},
'synonyms.$': {type:String, regEx:/.*\S.*/},
};
Synonyms.attachSchema(SynonymsSchema);
const Projects = new Mongo.Collection('PROJECTS')
const ProjectSchema ={
nlu: Object,
'nlu.data': Object,
'nlu.data.synonyms': {
type: Array,
},
'nlu.data.synonyms.$': {
type: String,
},
};
Projects.attachSchema(ProjectSchema);
Meteor.startup(() => {
// just add this entry once
if (Synonyms.find().count() === 0) {
Synonyms.insert({
value: 'car',
synonyms: ['automobile']
})
}
// get the id
const carId = Synonyms.findOne()._id;
const insertId = Projects.insert({
nlu: {
data: {
synonyms:[carId] // push the _id as reference
}
}
});
// ['MG464i9PgyniuGHpn'] => reference to Synonyms document
console.log(Projects.findOne(insertId).nlu.data.synonyms);
Projects.update({_id: insertId}, {$pull: {'nlu.data.synonyms' : carId }}); // pull the reference
const afterUpdate = Projects.findOne(insertId);
console.log(afterUpdate, afterUpdate.nlu.data.synonyms.length);
});
I know this was not part of the question but I just wanted to point out that there are many benefits of normalizing complex document structures into separate collections:
no duplicate data
decouple data that is not intended to be bound (here: Synonyms could be also used independently from Projects)
update referred documents once, all Projects will point to the very actual version (since it's a reference)
finer publication/subscription handling => more control about what data flows over the wire
reduces complex auto and default value generation
changes in the referred collection's schema may have only few consequences for UI and functions that make use of the referrer's schema.
Of course this has also disadvantages:
more collections to handle
more code to write (more code = more potential errors)
more tests to write (much more time to invest)
sometimes you need to denormalize back for this one case out of 100
you have to invest a lot of time in data schema design before starting to code

What is the better implementation? MongoDb Query

I need some help with MongoDb. I need to check if an object exists in the database. If it's true, then I need to check if this object has a specific element into array (Products). If not, I need to create this object(Order) with this element(Cookie) in to array(Products).
Example data:
Order {
_id: ObjectId("580bc55f54101f1d18152d88"),
code: "AVG223424",
products: [
{
name: "Cookie"
},
{
name: "Soda"
}
]
}
Finally, what is the better implementation?
Assuming you are using a collection with the name 'Orders'
db
.Orders
.update({
code: "12345"
}, {
$addToSet: {
products: {
name: "Cookie"
}
},
$setOnInsert: {
code: "12345"
}
}, {
upsert: true
});
This query looks for a document with the same 'code,' and if found, will add the object '{name: "Cookie"}' if there is no other Object with the same key/val pairs. If the document does not exist, the '$setOnInsert' command will set the specified fields only if a new document is created.

fetching documents based on nested subdoc array value

I'm trying to get all documents in a collection based on a subdocument array values. This is my data structure in the collection i'm seeking:
{
_id: ObjectId('...'),
name: "my event",
members:
[
{
_id: ObjectId('...'),
name: "family",
users: [ObjectId('...'),ObjectId('...'),ObjectId('...')]
},
{
_id: ObjectId('...'),
name: "work",
users: [ObjectId('...'),ObjectId('...'),ObjectId('...')]
}
]
}
I should point out that the schema of these objects are defined like so:
Events:
{
name: { type: String },
members: {type: [{ type: ObjectId, ref: 'MemberGroup' }], default:[] }
}
MemberGroup:
{
name: { type: String },
users: [{type: mongoose.Schema.Types.ObjectId, ref: 'User'}]
}
and of course User is just an arbitrary object with an id.
What i'm trying to fetch: i want to retrieve all events which has a specific user id in its member.users field.
i'm not sure if its event possible in a single call but here is what i've tried:
var userId = new mongoose.Schema.ObjectId('57d9503ef10d5ffc08d6b8cc');
events.find({members: { $elemMatch : { users: { $in: [userId]} } }})
this syntax work but return no elements even though i know there are matching elements (using robomongo to visualize the db)
So after a long search in stackoverflow i came across the good answare here:
MongoDB: How to find out if an array field contains an element?
my query changed to the following which gave me my desired result:
var userId = new mongoose.Schema.ObjectId('57d9503ef10d5ffc08d6b8cc');
events.find({}, {members: { $elemMatch : { users: { $eq: userId} } }})
notice the use of the second find parameter to specify the query limit instead of the first one (which is odd)