Apply filtering with an array of value instead of one value for a filter in Algolia - algolia

I have in my index a list of object, each of them has an objectID value.
On some search, i want to filter OUT a certain number of them, using there objectID.
For the moment it works with one value as a string, i would like to know how to do for multiple value.
filters = 'NOT objectID:' + objectIDToFilter;
This work for one object, what can i do to apply this for an array of ObjectID. because :
filters = 'NOT objectID:' + arrayObjectID;
does not work.
I was thinking of generating a huge string with an arrayId.map with all my 'NOT objectID:1 AND NOT objectID: 2 ...' but i wanted to know if there is a cleaner way to do it.
I unfortunately misunderstood the line in algolia doc :
Array Attributes: Any attribute set up as an array will match the filter as soon as one of the values in the array match.
This apparently refers to the value itself in Algolia and not the filter

So i did not found a solution on algolia doc, i went for the long string, hope there is no limits on how much filter we can add on a query (found nothing about that).
Here is what i did if someone need it :
let filters = `NOT objectID:${userID}`;
blockedByUsers.map((blockedByUser) => {
filters = filters + ` AND NOT objectID:${blockedByUser}`;
});
If you need to add multiple but don't have a starting point like i do, you can't start the query with an AND , a solution i found to bypass that:
let filters = `NOT objectID:${blockedByUsers[0]}`;
blockedByUsers.map((blockedByUser, i) => {
if (i > 0) filters = filters + ` AND NOT objectID:${blockedByUser}`;
});
There is probably a cleaner solution, but this work. If you have found other solution for that problems i'll be happy to see :)

Related

How can I upsert a record and array element at the same time?

That is meant to be read as a dual upsert operation, upsert the document then the array element.
So MongoDB is a denormalized store for me (we're event sourced) and one of the things I'm trying to deal with is the concurrent nature of that. The problem is this:
Events can come in out of order, so each update to the database need to be an upsert.
I need to be able to not only upsert the parent document but an element in an array property of that document.
For example:
If the document doesn't exist, create it. All events in this stream have the document's ID but only part of the information depending on the event.
If the document does exist, then update it. This is the easy part. The update command is just written as UpdateOneAsync and as an upsert.
If the event is actually to update a list, then that list element needs to be upserted. So if the document doesn't exist, it needs to be created and the list item will be upserted (resulting in an insert); if the document does exist, then we need to find the element and update it as an upsert, so if the element exists then it is updated otherwise it is inserted.
If at all possible, having it execute as a single atomic operation would be ideal, but if it can only be done in multiple steps, then so be it. I'm getting a number of mixed examples on the net due to the large change in the 2.x driver. Not sure what I'm looking for beyond the UpdateOneAsync. Currently using 2.4.x. Explained examples would be appreciated. TIA
Note:
Reiterating that this is a question regarding the MongoDB C# driver 2.4.x
Took some tinkering, but I got it.
var notificationData = new NotificationData
{
ReferenceId = e.ReferenceId,
NotificationId = e.NotificationId,
DeliveredDateUtc = e.SentDate.DateTime
};
var matchDocument = Builders<SurveyData>.Filter.Eq(s => s.SurveyId, e.EntityId);
// first upsert the document to make sure that you have a collection to write to
var surveyUpsert = new UpdateOneModel<SurveyData>(
matchDocument,
Builders<SurveyData>.Update
.SetOnInsert(f => f.SurveyId, e.EntityId)
.SetOnInsert(f => f.Notifications, new List<NotificationData>())){ IsUpsert = true};
// then push a new element if none of the existing elements match
var noMatchReferenceId = Builders<SurveyData>.Filter
.Not(Builders<SurveyData>.Filter.ElemMatch(s => s.Notifications, n => n.ReferenceId.Equals(e.ReferenceId)));
var insertNewNotification = new UpdateOneModel<SurveyData>(
matchDocument & noMatchReferenceId,
Builders<SurveyData>.Update
.Push(s => s.Notifications, notificationData));
// then update the element that does match the reference ID (if any)
var matchReferenceId = Builders<SurveyData>.Filter
.ElemMatch(s => s.Notifications, Builders<NotificationData>.Filter.Eq(n => n.ReferenceId, notificationData.ReferenceId));
var updateExistingNotification = new UpdateOneModel<SurveyData>(
matchDocument & matchReferenceId,
Builders<SurveyData>.Update
// apparently the mongo C# driver will convert any negative index into an index symbol ('$')
.Set(s => s.Notifications[-1].NotificationId, e.NotificationId)
.Set(s => s.Notifications[-1].DeliveredDateUtc, notificationData.DeliveredDateUtc));
// execute these as a batch and in order
var result = await _surveyRepository.DatabaseCollection
.BulkWriteAsync(
new []{ surveyUpsert, insertNewNotification, updateExistingNotification },
new BulkWriteOptions { IsOrdered = true })
.ConfigureAwait(false);
The post linked as being a dupe was absolutely helpful, but it was not the answer. There were a few things that needed to be discovered.
The 'second statement' in the linked example didn't work
correctly, at least when translated literally. To get it to work, I had to match on the
element and then invert the logic by wrapping it in the Not() filter.
In order to use 'this index' on the match, you have to use a
negative index on the array. As it turns out, the C# driver will
convert any negative index to the '$' character when the query is
rendered.
In order to ensure they are run in order, you must include bulk write
options with IsOrdered set to true.

MongoDB: Performing actions on a field of a collection

I am very new to MongoDB (only got started out of interest last week). I am trying to figure out something and I'm not entirely sure of the terminology to go about searching for it. So I decided to make a SO question.
I created a collection called Students. Students has the fields id, name, undergrad(which is a boolean), classes(which is an array) and units(which is 3 times the number of classes the student has).
Now, I wanted to see how I could perform actions on a particular field of Students. What I did was, inserted a couple documents and purposefully did not include the units field. And I wanted to $set the units field forEach document/student that did not have the field. I did the following:
var studentDoc = db.students.find({units: {$exists:false}})
studentDoc.forEach(function(stu){
db.student.update({_id:stu._id}, {$set:{units:{$size:"$classes"}}})
}
)
Question 1: Is what I've done even remotely correct?
Question 2: When I type studentDoc after setting the var studentDoc, it doesn't print anything. But when I write
var studentDoc = db.students.find({units:{$exists:false}}).toArray()
it prints studentDoc as an array but still doesn't seem to do anything in the forEach loop.
Question 3: How do I $set the units field as 3 * (size of classes array)
I hope I have been clear in my question. I have tried searching on the MongoDB docs and google, but haven't had any luck (probably because of my lack of knowledge to search for the correct things).
Any help would be great! You can even point me in the right direction, and that'll be great!
Thank you in advance for all your help!!
I'm not sure why foreach not getting in. You can do the same with below working code.
for(i=0;i<studentDoc.length();i++){
var stud_id = studentDoc[i]._id;
var doc = db.students.findOne({"_id":stud_id})["classes"];
if(doc){
var len = doc.length;
db.students.update({"_id":stud_id},{$set:{"units":len*3}})
}
}
As far as I know, You can't use $size:"$" queries in update statement. you will get error like below:
The dollar ($) prefixed field '$size' in 'units.$size' is not valid for storage.
The return value of db.collection.find() is cursor.
In the mongo shell, if the returned cursor is not assigned to a variable using the var keyword, the cursor is automatically iterated up to 20 times to access up to the first 20 documents that match the query. To iterate manually, assign the returned cursor to a variable using the var keyword. So
var studentDoc = db.students.find({units: {$exists:false}})
You should iterator studentDoc manually.
Whereas, the cursor.toArray() returns an array that contains all the documents from a cursor. The method iterates completely the cursor, loading all the documents into RAM and exhausting the cursor. Thus
var studentDoc = db.students.find({units:{$exists:false}}).toArray()
it prints studentDoc as an array.
If you want to use forEach, here is cursor.forEach().
db.students.find({units:{$exists:false}}).forEach()
var studentDoc = db.students.find({units: {$exists:false}})
here studentDoc is a cursor, it's not printable.
you can use forEach
studentDoc.forEach(printjson);
or iterator the cursor
while (studentDoc.hasNext()) {
printjson(studentDoc.next());
}

Extbase "findBy" with multiple parameters

I got an extension in which i want to include some filters, i know figured out that i can filter the results that are shown of my listAction() by using findBy.
I tested it and it worked like this:
$cars = $this->carRepository->findByCarid("1");
$this->view->assign('cars', $cars);
My problem now is i need to filter the result with more than one Parameter, what if i want to add findByColor("blue") so it gives me all cars wit hid 1 and color blue? What solution does extbase have for that kind of search queries? i can`t find anything good or understandable in the documentation.
You have to extend you repository and code this functionality on your own. Extbase offers you a simple but powerful API to do so.
class whatEverYourRepositoryIsCalled extends \TYPO3\CMS\Extbase\Persistence\Repository {
public function findByFilter($carId, $color) {
// Create empty query = select * from table
$query = $this->createQuery();
// Add query options
return $query->matching(
// ALL conditions have to be met (AND)
$query->logicalAnd(
// table column carId must be euqal to $carId
$query->equals('carId', $carId),
// table column color must be euqal to $color
$query->equals('color', $color)
)
);
}
}
This is a quite simple approach to your problem. In a real world scenario I would probably use an array of filter criteria to do the filtering like array('carId' => 1, 'color' => 'blue'). Inside of findByFilter() those values would be extracted and added to the query.
The key is to build the desired query. A quite comprehensive explanation of how to do that can be found at http://blog.typoplanet.de/2010/01/27/the-repository-and-query-object-of-extbase/. Unfortunately it's not completely up to date but the part about constructing queries is still valid.

Composite views in couchbase

I'm new to Couchbase and am struggling to get a composite index to do what I want it to. The use-case is this:
I have a set of "Enumerations" being stored as documents
Each has a "last_updated" field which -- as you may have guessed -- stores the last time that the field was updated
I want to be able to show only those enumerations which have been updated since some given date but still sort the list by the name of the enumeration
I've created a Couchbase View like this:
function (doc, meta) {
var time_array;
if (doc.doc_type === "enum") {
if (doc.last_updated) {
time_array = doc.last_updated.split(/[- :]/);
} else {
time_array = [0,0,0,0,0,0];
}
for(var i=0; i<time_array.length; i++) { time_array[i] = parseInt(time_array[i], 10); }
time_array.unshift(meta.id);
emit(time_array, null);
}
}
I have one record that doesn't have the last_updated field set and therefore has it's time fields are all set to zero. I thought as a first test I could filter out that result and I put in the following:
startkey = ["a",2012,0,0,0,0,0]
endkey = ["Z",2014,0,0,0,0,0]
While the list is sorted by the 'id' it isn't filtering anything! Can anyone tell me what I'm doing wrong? Is there a better composite view to achieve these results?
In couchbase when you query view by startkey - endkey you're unable to filter results by 2 or more properties. Couchbase has only one index, so it will filter your results only by first param. So your query will be identical to query with:
startkey = ["a"]
endkey = ["Z"]
Here is a link to complete answer by Filipe Manana why it can't be filtered by those dates.
Here is a quote from it:
For composite keys (arrays), elements are compared from left to right and comparison finishes as soon as a element is different from the corresponding element in the other key (same as what happens when comparing strings à la memcmp() or strcmp()).
So if you want to have a view that filters by date, date array should go first in composite key.

Filter array using NSPredicate and obtains new object composed by some elements in the query

I've got an array like that
Word array (
{
translation = (
{
name = Roma;
lang = it;
},
{
name = Rome;
lang = en;
}
);
type = provenance;
value = RMU;
},
{
translation = (
{
name = "Milano";
lang = it;
},
{
name = "Milan";
lang = en;
}
);
type = destination;
value = MIL;
},)
The idea is to filter it using an NSPredicate and receive and an array of dictionaries based on the lang key, I'd like to get something like this made by filtering for lang == it,
Word array (
{
name = Roma;
lang = it;
type = provenance;
value = RMU;
},
{
name = "Milano";
lang = it;
type = destination;
value = MIL;
})
I can't simplify the data because it comes from a "JSON" service.
I've tried different predicates using SUBQUERY but none of them works, documentation about SUBQUERY is pretty poor, I'm missing something, probably the problem is that I'd like to receive an object that is really different from the source.
Of course I'm able to obtain that structure enumerating, I'm wondering if there is a shorter solution
This answer from Dave DeLong link to SUBQUERY explanation gave a me a lot of hints about SUBQUERY, but I'm not able to find a solution to my problem.
Can someone give me a hints about?
You can't do this with a predicate. (Well, you could, but it would be stupidly complex, difficult to understand and maintain, and in the end it would be easier to write the code yourself)
NSPredicate is for extracting a subset of data from an existing set. It only* does filtering, because a predicate is simply a statement that evaluates to true or false. If you have a collection and filter it with a predicate, then what happens is the collection starts iterating over its elements and asks the predicate: "does this pass your test?" "does this pass your test?" "does this pass your test?"... Every time that the predicate answers "yes this passes my test", the collection adds that object to a new collection. It is that new collection that is returned from the filter method.
THUS:
NSPredicate does not (easily) allow for merging two sets of data (which is what you're asking for). It is possible (because you can do pretty much anything with a FUNCTION() expression), but it makes for inherently unreadable predicates.
SO:
Don't use NSPredicate to merge your dataset. Do it yourself.