Can I make a MongoDB field value change depending on other values? - database

As an example, here is something similar to what will be in my database:
"name": "Task A",
"completed": false,
"no_of_items": placeholder1
"no_completed_items": placeholder2
"items": {"name": "Task A1",
"completed": false,
"no_of_items": placeholder1
"no_completed_items": placeholder2
"items": {placeholder3}
},
"name": "Task A2",
"completed": false,
"no_of_items": placeholder1
"no_completed_items": placeholder2
"items": {placeholder3}
With that in mind, what I would like is:
placeholder1 to be the number of items in the items field (is there a way to get the length perhaps?).
placeholder2 to be the number of items in the items field where completed is true.
'completed' to be changed from false to true when no_completed_items equals no_of_items
placeholder3 is just there because this will be multiple levels deep, but shouldn't affect our example. At a certain depth, objects will no longer have multiple items, and users will check off these bottom-level tasks. When they complete all of the tasks, I would like the level above to reflect that 'completed' is now true (based on the no_completed_items being the same as no_of_items, as these could change in the future).

Related

Azure Cognitive Search Complex object filtering

I have an index with azure cognative search but cant seem to find the right syntax to query it for what I need.
I have documents that looks like the below and want to be able to pass in a search for "black denim shirt" and have that matched against each item object in the document rather than the whole document.
I need this match to be confined to the objects as I don't want the "black" and "denim" from the "black denim shirt" query to be matched to a "black denim jeans". Therefore the match/higher ranked result should be Document 2
Document 1:
{
"id": "Style1",
"itemKeyWords": [
{
"productKeyWords": "shirt,oversized shirt,denim",
"attributeKeyWords": "blue"
},
{
"productKeyWords": "Skinny, denim, jeans",
"attributeKeyWords": "black"
}
]
}
Document 2:
{
"id": "Style2",
"itemKeyWords": [
{
"productKeyWords": "shirt,oversized shirt,denim",
"attributeKeyWords": "black"
},
{
"productKeyWords": "Skinny, denim, jeans",
"attributeKeyWords": "blue"
}
]
}
I have the itemKeyWords set up in the index as a
{
"name": "itemKeyWords",
"type": "Collection(Edm.ComplexType)",
"fields": [
{
"name": "productKeyWords",
"type": "Edm.String",
"searchable": true,
"filterable": true,
"retrievable": true,
"sortable": false,
"facetable": true,
"key": false,
"indexAnalyzer": null,
"searchAnalyzer": null,
"analyzer": "en.lucene",
"normalizer": null,
"synonymMaps": []
},
{
"name": "attributeKeyWords",
"type": "Edm.String",
"searchable": true,
"filterable": true,
"retrievable": true,
"sortable": false,
"facetable": true,
"key": false,
"indexAnalyzer": null,
"searchAnalyzer": null,
"analyzer": "en.lucene",
"normalizer": null,
"synonymMaps": []
}
]
}
I have tried various attempts using this as a guid but cant seem to get the syntax right
https://learn.microsoft.com/en-gb/azure/search/search-howto-complex-data-types?tabs=portal
Unfortunately, as of today, it is not possible to make "search" requests (queries that rely on the tokenized content) that enforce the requirement to have all matches within a specific entry of a complex object collection. This is only supported for filters right now (as long as the filter does not rely on the search.in function).
I can think of two (less than idea) work around:
Index each entry of the collection as separate documents
Flatten the sub-fields into a single field:
AggregateField: "Skinny, denim, jeans. black"
And then emit a query that use proximity search (to make sure all terms are within a certain distance):
queryType=full&search="black denim jeans"~5
If it's important for you to still keep the structured version of the content in the document (attribute and keywords separately), you can still index them along side the aggregated field for retrieval purpose (you can target different fields for matching purpose vs the one you actually return in the response by using select and searchFields)
queryType=full&search="black denim jeans"~3&searchFields=aggregatedFields&select=productKeyWords, attributeKeyWords
or
queryType=full&search=aggregatedFields:"black denim jeans"~3&select=productKeyWords,attributeKeyWords

How to add child entities without id to parent in state normalized with normalizr

I've recently started using normalizr with zustand in a new React app. It's been a very good experience so far, having solved most of the painful problems I've had in the past.
I've just bumped into an issue I can't think of a clean way of solving for the past few days.
Imagine I have a normalizr-normalized state looking like:
{
"entities": {
"triggers": {
"1": {
"id": 1,
"condition": "WHEN_CURRENCY_EXCHANGED",
"enabled": true,
"value": "TRY"
},
"2": {
"id": 2,
"condition": "WHEN_CURRENCY_EXCHANGED",
"enabled": true,
"value": "GBP"
},
"3": {
"id": 3,
"condition": "WHEN_TRANSACTION_CREATED",
"enabled": true,
"value": true
}
},
"campaigns": {
"19": {
"id": 19,
"name": "Some campaign name",
"triggers": [
1,
2,
3
]
}
}
},
"result": 19
}
And we have a page that allows a user to add one or more triggers to the campaign and then save them. The problem is that at the time of adding these triggers, they do not have an id until the user clicks the Save button (ids are generated by the database). When the Save button is clicked, the state is being denormalized (via normalizr's denormalize function) and sent as payload to the backend looking like the following:
{
"id": 19,
"name": "Some campaign name",
"triggers": [
{
"id": 1,
"condition": "WHEN_CURRENCY_EXCHANGED",
"enabled": true,
"value": "TRY"
},
{
"id": 2,
"condition": "WHEN_CURRENCY_EXCHANGED",
"enabled": true,
"value": "GBP"
},
{
"id": 3,
"condition": "WHEN_TRANSACTION_CREATED",
"enabled": true,
"value": true
}
]
}
The problem is that if the user adds an entity to the triggers, it does not have an id as ids are generated by the database and I cannot find a proper way to add it to the state (due to the id-based nature of normalized states).
The only workaround I can think of is generating some temporary IDs (e.g. uuid) when a trigger is added on the front-end but is not yet saved and then going over each entity upon denormalization, doing something like if (isUuid(trigger.id)) delete trigger.id, which seems too tedious and workaroundish.
Appreciate your help.
P.S. There is something similar explained here. The problem is that in our case the generateId('comment') logic is happening on the backend.
A simple solution is to split.
The create trigger API call and the add trigger to campaign API call.
Do the first, then save the trigger into the normalized store with the id generated by the backend.
Then add it to the campaign.

Sorting the results from solr query

Hello I am trying to simply sort the results of my query alphabetically. The data that is returned looks like this:
"FacetFilters": [
{
"Id": 0,
"Name": "small",
"ResultCount": 47,
"IsSelected": false,
"Hide": false
},
{
"Id": 0,
"Name": "n/a",
"ResultCount": 1,
"IsSelected": false,
"Hide": false
},
{
"Id": 0,
"Name": "medium",
"ResultCount": 79,
"IsSelected": false,
"Hide": false
},
{
"Id": 0,
"Name": "large",
"ResultCount": 4,
"IsSelected": false,
"Hide": false
}
]
I was able to this issue post-query by reversing the list using: FacetFilters.Reverse();, but I would prefer to just get the results in the correct order through the query. Could someone please tell me what the best way is to go about this? Thank you. For the record I am using solrnet package for .Net.
You can't sort facets in descending order with the old Facet API (which is what SolrNet uses). Until SolrNet supports the JSON Facet API natively you'll have to add it yourself.
See How to implement JSON facet API in SolrNet for how to do the first part, then see Order Facet Fields by Descending Value for how to sort a facet in descending order by using the JSON facet API instead.
"sort":"index desc"

Set criteria in query for fields and fields in nested objects

I have a document like this:
{
"InDate": "11.09.2015",
"Kst2Kst": true,
"OutDate": "11.09.2015",
"__v": 0,
"_id": ObjectId('55f2df2d7e12a9f1f52837e6'),
"accepted": true,
"inventar": [
{
"accepted": "1",
"name": "AAAA",
"isstammkost": true,
"stammkost": "IWXI"
},
{
"accepted": "1",
"name": "BBBB",
"isstammkost": false,
"stammkost": "null"
}
]
}
I want to select the data with "isstammkost": true in the inventar-array.
My query is:
Move.findOne({accepted : true, 'inventar.isstammkost' : true},
'OutDate InDate inventar.name', function(err, res)
It doesn't work -> It selects all, even with inventar.isstammkost : false.
The "normal" query works like I want (without criteria in sub-array). Whats the right way to set criteria in sub-array?
Of course it will return the "isstammkost": false part, because that is part of the same document as the "isstammkost": true. They are both objects in the array "inventar", a top-level field in a single document. Without some sort of projection, the entire document will always be returned to a mongodb query and thus nodejs will pass them on to you.
I'm not terribly up-to-speed on nodejs, but if this were the mongo shell it would look like this:
> db.MyDB.findOne({{accepted : true, "inventar.isstammkost" : true}, {"inventar.isstammkost.$": 1});
You will need to find out how to add that extra parameter to the nodejs function.

MongoDB Array Query Performance

I'm trying to figure out what the best schema is for a dating site like app. User's have a listing (possibly many) and they can view other user listings to 'like' and 'dislike' them.
Currently i'm just storing the other persons listing id in a likedBy and dislikedBy array. When a user 'likes' a listing, it puts their listing id into the 'liked' listings arrays. However I would now like to track the timestamp that a user likes a listing. This would be used for a user's 'history list' or for data analysis.
I would need to do two separate queries:
find all active listings that this user has not liked or disliked before
and for a user's history of 'liked'/'disliked' choices
find all the listings user X has liked in chronological order
My current schema is:
listings
_id: 'sdf3f'
likedBy: ['12ac', 'as3vd', 'sadf3']
dislikedBy: ['asdf', 'sdsdf', 'asdfas']
active: bool
Could I do something like this?
listings
_id: 'sdf3f'
likedBy: [{'12ac', date: Date}, {'ds3d', date: Date}]
dislikedBy: [{'s12ac', date: Date}, {'6fs3d', date: Date}]
active: bool
I was also thinking of making a new collection for choices.
choices
Id
userId // id of current user making the choice
userlistId // listing of the user making the choice
listingChoseId // the listing they chose yes/no
type
date
I'm not sure of the performance implications of having these choices in another collection when doing the find all active listings that this user has not liked or disliked before.
Any insight would be greatly appreciated!
Well you obviously thought it was a good idea to have these embedded in the "listings" documents so your additional usage patterns to the cases presented here worked properly. With that in mind there is no reason to throw that away.
To clarify though, the structure you seem to want is something like this:
{
"_id": "sdf3f",
"likedBy": [
{ "userId": "12ac", "date": ISODate("2014-04-09T07:30:47.091Z") },
{ "userId": "as3vd", "date": ISODate("2014-04-09T07:30:47.091Z") },
{ "userId": "sadf3", "date": ISODate("2014-04-09T07:30:47.091Z") }
],
"dislikedBy": [
{ "userId": "asdf", "date": ISODate("2014-04-09T07:30:47.091Z") },
{ "userId": "sdsdf", "date": ISODate("2014-04-09T07:30:47.091Z") },
{ "userId": "asdfas", "date": ISODate("2014-04-09T07:30:47.091Z") }
],
"active": true
}
Which is all well and fine except that there is one catch. Because you have this content in two array fields you would not be able to create an index over both of those fields. That is a restriction where only one array type of field (or multikey) can be be included within a compound index.
So to solve the obvious problem with your first query not being able to use an index, you would structure like this instead:
{
"_id": "sdf3f",
"votes": [
{
"userId": "12ac",
"type": "like",
"date": ISODate("2014-04-09T07:30:47.091Z")
},
{
"userId": "as3vd",
"type": "like",
"date": ISODate("2014-04-09T07:30:47.091Z")
},
{
"userId": "sadf3",
"type": "like",
"date": ISODate("2014-04-09T07:30:47.091Z")
},
{
"userId": "asdf",
"type": "dislike",
"date": ISODate("2014-04-09T07:30:47.091Z")
},
{
"userId": "sdsdf",
"type": "dislike",
"date": ISODate("2014-04-09T07:30:47.091Z")
},
{
"userId": "asdfas",
"type": "dislike",
"date": ISODate("2014-04-09T07:30:47.091Z")
}
],
"active": true
}
This allows an index that covers this form:
db.post.ensureIndex({
"active": 1,
"votes.userId": 1,
"votes.date": 1,
"votes.type": 1
})
Actually you will probably want a few indexes to suit your usage patterns, but the point is now can have indexes you can use.
Covering the first case you have this form of query:
db.post.find({ "active": true, "votes.userId": { "$ne": "12ac" } })
That makes sense considering that you clearly are not going to have both an like and dislike option for each user. By the order of that index, at least active can be used to filter because your negating condition needs to scan everything else. No way around that with any structure.
For the other case you probably want the userId to be in an index before the date and as the first element. Then your query is quite simple:
db.post.find({ "votes.userId": "12ac" })
.sort({ "votes.userId": 1, "votes.date": 1 })
But you may be wondering that you suddenly lost something in that getting the count of "likes" and "dislikes" was as easy as testing the size of the array before, but now it's a little different. Not a problem that cannot be solved using aggregate:
db.post.aggregate([
{ "$unwind": "$votes" },
{ "$group": {
"_id": {
"_id": "$_id",
"active": "$active"
},
"likes": { "$sum": { "$cond": [
{ "$eq": [ "$votes.type", "like" ] },
1,
0
]}},
"dislikes": { "$sum": { "$cond": [
{ "$eq": [ "$votes.type", "dislike" ] },
1,
0
]}}
])
So whatever your actual usage form you can store any important parts of the document to keep in the grouping _id and then evaluate the count of "likes" and "dislikes" in an easy manner.
You may also not that changing an entry from like to dislike can also be done in a single atomic update.
There is much more you can do, but I would prefer this structure for the reasons as given.

Resources