MongoDB insert unique object into array (by author field) - javascript

How do I:
Update an object in the reviews array, if an existing object with the same author exists
Insert a new object otherwise
(In other words: update a review if the author has already written one, and create a new one if they haven't)
Document structure:
{
"_id": "(book id)",
"title": "Title",
"author": "Author",
"reviews": [{
"author": "(user id)",
"rating": 4
}],
}
This is what I've tried so far -- it only updates:
db.books.update_one(
{"_id": ObjectId(book_id)},
{"$set": {"reviews.$[elem]": { # or $push etc.
"author": user_id, "rating": rating}}},
array_filters=[{"elem.author": user_id}])
Alternatively I have also tried result.modified_count == 0: then insert but this would insert a duplicate if the previous rating is the same as the new one (hence not updating anything and result.modified_count == 0)
Sorry for another similar question! Thank you in advance!
EDIT:
From the $addToSet operator page:
"...you cannot specify that MongoDB compare only a subset of the fields in the document to determine whether the document is a duplicate of an existing array element."
However I'm still interested if anyone has a way to do this (it doesn't have to be in one query)!

From reading the docs, I would try find and modify with a condition on reviews.author, then an update with an update operator expression using $push on reviews.

Related

Deleting an object from a nested array in DynamoDB - AWS JavaScript SDK

I'm building an app where I need to delete items stored in the database. Here's a (shortened) example of user data I have in my DynamoDB table called 'registeredUsers':
{
"userId": "f3a0f858-57b4-4420-81fa-1f0acdec979d"
"aboutMe": "My name is Mary, and I just love jigsaw puzzles! My favourite jigsaw category is Architecture, but I also like ones with plants in them.",
"age": 27,
"email": "mary_smith#gmail.com",
"favourites": {
"imageLibrary": [
{
"id": "71ff8060-fcf2-4523-98e5-f48127d7d88b",
"name": "bird.jpg",
"rating": 5,
"url": "https://s3.eu-west-2.amazonaws.com/jigsaw-image-library/image-library/images/bird.jpg"
},
{
"id": "fea4fd2a-851b-411f-8dc2-1ae0e144188a",
"name": "porsche.jpg",
"rating": 3,
"url": "https://s3.eu-west-2.amazonaws.com/jigsaw-image-library/image-library/images/porsche.jpg"
},
{
"id": "328b913f-b364-47df-929d-925676156e97",
"name": "rose.jpg",
"rating": 0,
"url": "https://s3.eu-west-2.amazonaws.com/jigsaw-image-library/image-library/images/rose.jpg"
}
]
}
}
I want to be able to delete the item 'rose.jpg' in the user.favourites.imageLibrary array. In order to select the correct user, I can provide the userId as the primary key. Then, in order to select the correct image in the array, I can pass the AWS.DocumentClient the 'id' of the item in order to delete it. However, I'm having trouble understanding the AWS API Reference docs. The examples given in the developer guide do not describe how to delete an item by looking at one of it's attributes. I know I have to provide an UpdateExpression and an ExpressionAttributeValues object. When I wanted to change a user setting, I found it pretty easy to do:
const params = {
TableName: REGISTERED_USERS_TABLE,
Key: { userId },
UpdateExpression: "set userPreferences.difficulty.showGridOverlay = :d",
ExpressionAttributeValues: {
":d": !showGridOverlay
},
ReturnValues: "UPDATED_NEW"
};
To conclude, I need a suitable Key, UpdateExpression and ExpressionAttributeValues object to access the rose.jpg item in the favourites array.
Unfortunately, the UpdateExpression syntax is not as powerful as you would have liked. It supports entire nested documents inside the item, but not sophisticated expressions to search in them or to modify them. The only ability it gives you inside a list is to access or modify its Nth element. For example:
REMOVE #favorites.#imagelibrary[3]
Will remove the 3rd element of imagelibrary (note that the "#imagelibrary" will need to be defined in ExpressionAttributeNames), and you can also have a condition on #favorites.#imagelibrary[3].#id, for example, in ConditionExpression. But unfortunately, there is no way to specify more complex combinations of conditions and updates, such as "find me the i where #favorites.#imagelibrary[i].#id is equal something, and then REMOVE this specific element".
Your remaining option is to read the full value of the item (or with ProjectionExpression just the #favorties.#imagelibrary array), and then in your own code find which of the elements you want to remove (e.g., discover that it is the 3rd element), and then in a separate update, remove the 3rd element.
Note that if there's a possibility that some other parallel operation also changes the item, you must use a conditional update (both UpdateExpression and ConditionExpression) for the element removal, to ensure the element that you are removing still has the id you expected. If the condition fails, you need to repeat the whole operation again - read the modified item again, find the element again, and try to remove it again. This is an example of the so-called "optimistic locking" technique which is often used with DynamoDB.

mongoose - avoid duplicate when all fields are equal to new entry

so I have a mongoose schema in my node application with two fields: tag and task, and I want to be able to save entries where the combination of both properties doesnt exist yet.
For example: my DB already has the following entries:
{tag:tag1, task:task1}
{tag:tag1, task:task2}
{tag:tag2, task:task1}
I want to be able to create {tag:tag2, task:task2}, but not {tag:tag1, task:task1} again, so I guess I cant use primary or unique in any of those fields, since they can repeat, except when their combination already exists
so which query should I use to save? Or should I find if it already exists first?
Use Unique Compound Indexing
db.users.createIndex( { "tag": 1, "task": 1 }, { unique: true } )
For more info visit the Link

Algolia search on nested objects in a record - multiple facetFilters in one object

I’m migrating from Mongo to Firebase with Algolia on top to provide the search. But hitting a snag coming up with a comparable way to search in individual elements of a record.
I have an object that stores when a room is available: from and to. Each record can have many individual from/to combos (see the sample below with 2). I want to be able to run a search something like:
roomavailable.from <= 1522195200 AND roomavailable.to >=1522900799
But only have the query search a match within each element, not any facet in all elements. An element query in Mongo works like that. But if I run that query on the record listed below, it will return the record, because the two roomavailable objects satisfy the .from and .to query. I think.
Is there a way to ensure the search is looking only at matching a pair of .from and .to in an individual object/element?
Below is the pertinent part of the record stored in Algolia so you can see the structure.
"roomavailable": [
{
"_id": "rJbdWvY9M",
"from": 1522195200,
"to": 1522799999
},
{
"_id": "r1H_-vKqz",
"from": 1523923200,
"to": 1524268799
}
],
And here is the Mongo (mongoose) equivalent where its searching inside individual elements (this works):
$elemMatch: {
from: {
$lte: moment(dateArray[0]).utc().startOf('day').format()
},
to: {
$gte: moment(dateArray[1]).utc().endOf('day').format()
}
}
I have also tried this query but it seems to still match either the .from AND .to but in any of the the individual roomavailable elements:
index.search({
query: '',
filters: filters,
facetFilters: [roomavailable.from: 1522195200, roomavailable.to: 1524268799],
attributesToRetrieve: [
"roomavailable",
],
restrictHighlightAndSnippetArrays: true
})
I found a couple posts on Algolia discussing using 1 bracket vs. 2 brackets in the facetFilters. I've tried both. Neither work.
Any suggestions would be awesome. Thanks!
Edit: See discussion on Algolia Discourse:
https://discourse.algolia.com/t/how-to-match-multiple-attributes-in-nested-object-with-numericfilters/4887/8
Hi #kanec, thanks for clarifying your question!
Indeed what #Alefort suggested (using roomavailable in a separate index) would be the easiest option since the query I mentioned above will definitely return the results you want. This will mean that you'll have to query the room availability index separately in order to get which IDs are available, so you'll have to use multiple-queries:
https://www.algolia.com/doc/api-reference/api-methods/multiple-queries/
That said, I asked our core API team to see if there's a more reasonable way to approach this issue, but I fear that this is a filter limit due to performance reasons with arrays. You could transform your data structure in the following and index your rooms as an object instead:
[
{
"roomavailable": {
"0": {
"_id": "rJbdWvY9M",
"from": 1522195200,
"to": 1522799999
},
"1": {
"_id": "r1H_-vKqz",
"from": 1523923200,
"to": 1524268799
}
}
}
]
So you can apply the following filter:
{
"filters": "roomavailable.0.from <= 1522195200 AND roomavailable.0.to >= 1522799999 AND roomavailable.1.from <= 1522195200 AND roomavailable.1.to >=1522900799"
}
The downside of this is that you'll need to know the length of roomavailable in order to build the search query on the front-end (you can do so at indexing time by adding a roomavailable_count property) and also this will probably will be less performant with a considerable number of rooms per item; in this case, switching to a dedicated index makes totally sense for the following reasons:
If in your backend you frequently update available rooms you won't impact the other indices' build time
Filters will perform better (as explained above)
Indexing strategy will be simpler to handle
Let me know what you think about this and if it helps you out.

React: array order is completely random - what determines the array order per the syntax

I have done tutorials and have seen examples where the array list item that is dynamically generated works exactly as intended. For some reason, in the code I am having to map in this particular array list is not generating the array list in any discernible order.
Reason being... From what I seen every time an action is added the key and index and other parameter is "adjusted" on the fly... leading to something completely random.
***Updated the description to be much more accurate to what is occuring based on my code base. I have learned we are using imuttable.js
More info here https://facebook.github.io/immutable-js/ and because of this there are certain things happening prior to the object being mapped...
For example we are using filter for a json object in our this.props
it is written as follows:
const filteredConstant = jsonObjects.filter((jsonObject) => {
return (jsonObject.status === status.APPROVED)
}
From the documentation in immutable it says filter will do the following
Seq({a:1,b:2,c:3,d:4}).filter(x => x % 2 === 0)
// Seq { b: 2, d: 4 }
From here we can rework the const / variable and now use map() to map the object. The below code did not work in the sense that open adding items to the object of jsonObject2 would produce random results. The key and even if I added an index there would be no rhyme or reason as to what the order would be... intended result would be oldest - newest data entry or ascending.
filteredConstant.valueSeq().map((jsonObjectNew, //index) => {
// code here
key={jsonObjectNew.id} //index={index} (//doesn't work either way)
}
apparently valueSeq() doesn't have an affect. Introducing a timestamp appended to a unique id doesn't work either.
Like I said previously it seems as if every-time there is a new entry it "remaps" and thus reorders randomly what is going on. I could be interpreting what the key actually is... I still haven't figured out how and where the key is being generated ideally from the server side. But even in terms of a key that is base64 for example would still have to go in some type of order based on letters and numerics? Am I wrong to think this?
Alas, I was able to create a fix and I will share that below. But more insight into this would be appreciated.
In jsfiddle I cannot replicate the issue but if any help can be offered on what exactly controls the array order and or new data entering into the object would said action have on the effect of the overall order?
So I was able to do a fix and there is something that I had to grasp in order to understand what the map'ed list was not in any type of order.
The answer lies in the fact that I was mapping an object... It wasn't an array or map object that was being inserted to dynamically... It was simply an object that for all intents and purposes is a dictionary of dictionaries.
Per the example react gives they dynamically build and array that is of an order per insertion... new item at the bottom. here is what that array looks like.
[
{
"id": 1388534400000,
"author": "Pete O'malley",
"text": "Hey there!"
},
{
"id": 1420070400000,
"author": "Paul O’Shannessy",
"text": "React is *great*!"
},
{
"id": 1470769287060,
"author": "Christian",
"text": "how are you"
}
] = JSON.stringify(this.props.data, null, 4)
and so on...
My object that was being used was not like this at all... It is just an object of objects.
Here is an example with 2 entries.
{
"93b1keyId": {
"xxxxx": "",
"xxxxx": {
"xxxx": "xxxx",
"xxxxx": [
{
"xxx": "xxxx",
"xxxxx": "xxxxx "
}
],
"xxxxx": "",
"xxxxx": "",
//code here
},
"Date": "2016-08-16",
"xxxxxx": "xxxxxxxx",
"timeCreated": "2016-08-16 17:49:39 pm -0400",
"xxxxx": {
"xxxxxx": "xxxxxx",
"xxxxxxx": "xxxx"
},
"xxxxxx": xxxxxx,
"id": "93b1KeyId",
"xxxxxx": "xxxxx"
},
"ed79KeyId": {
"xxxxx": "",
"xxxxx": {
"xxxx": "xxxx",
"xxxxx": [
{
"xxx": "xxxx",
"xxxxx": "xxxxx "
}
],
"xxxxx": "",
"xxxxx": "",
//code here
},
"Date": "2016-08-16",
"xxxxxx": "xxxxxxxx",
"xxxxx": {
"xxxxxx": "xxxxxx",
"xxxxxxx": "xxxx"
},
"xxxxxx": xxxxxx,
"id": "ed79613e-d4bf-4fb4-993f-c212993d5d3b",
"xxxxxx": "xxxxx"
}
} = JSON.stringify(jsonObjectOriginal, null, 4)
So as you see this is not an array... it is just a dictionary of dictionaries. objects in objects.
Per this entry I am able to confirm with reasonable assurance that order of dictionary objects are not ordered.
Stack question regarding property order
In particular see section 12.6.4 of the ECMAScript specification:
The mechanics and order of enumerating the properties ... is not specified.
&
4.3.3 Object
An object is a member of the type Object. It is an unordered collection of properties each of which contains a primitive value, object, or function. A function stored in a property of an object is called a method.
What was confusing about the situation is that it is a little misleading to consider "mapping" to be iterative of insertion order if what it is mapping is an unordered object.
Per the mozilla their documentation states the following.
A Map object iterates its elements in insertion order — a for...of loop returns an array of [key, value] for each iteration.
Again, that is only if you are inserting to the actuall map object itself... which for me was not the case.
So, the answer...
I had to re-sort the object upon mapping the React function which displays to the UI...
I used sortBy which is an immutable sub method of the map() method.
Doc information can be found here: immutable sortBy method
{filteredConstant.sortBy(sortBy => sortBy.timeCreated).map((jsonObject2) => {
//code here
So now, the new mapped object is reordered correctly based on insertion time and displayed to the UI correctly. I enjoyed learning / working through this one!!! but I feel mozilla should perhaps update that page.
I verified my "theory" by studying the jsonObjectOriginal and jsonObjectNew... The original was the one taking in random order.

Idempotency in MongoDB nested array, possible?

I am writing a REST api which I want to make idempotent. I am kind of struggling right now with nested arrays and idempotency. I want to update an item in product_notes array in one atomic operation. Is that possible in MongoDB? Or do I have to store arrays as objects instead (see my example at the end of this post)? Is it for example possible to mimic the upsert behaviour but for arrays?
{
username: "test01",
product_notes: [
{ product_id: ObjectID("123"), note: "My comment!" },
{ product_id: ObjectID("124"), note: "My other comment" } ]
}
If I want to update the note for an existing product_node I just use the update command and $set but what if the product_id isn't in the array yet. Then I would like to do an upsert but that (as far as I know) isn't part of the embedded document/array operators.
One way to solve this, and make it idempotent, would be to just add a new collection product_notes to relate between product_id and username.
This feels like violating the purpose of document-based databases.
Another solution:
{
username: "test01",
product_notes: {
"123": { product_id: ObjectID("123"), note: "My comment!" },
"124": { product_id: ObjectID("124"), note: "My other comment" } }
}
Anyone a bit more experienced than me who have anything to share regarding this?
My understanding of your requirement is that you would like to store unique product ids (array) for an user.
You could create an composite unique index on "username" and "username.product_id". So that when the same product id is inserted in the array, you would an exception which you could catch and handle in the code as you wanted the service to be Idempotent.
In terms of adding the new element to an array (i.e. product_notes), I have used Spring data in which you need to get the document by primary key (i.e. top level attribute - example "_id") and then add a new element to an array and update the document.
In terms of updating an attribute in existing array element:-
Again, get the document by primary key (i.e. top level attribute -
example "_id")
Find the correct product id occurrence by iterating the array data
Replace the "[]" with array occurrence
product_notes.[].note

Categories