algolia how to orderby string-value and geolocation - algolia

I am working on a prototype for a new product, need some help to figure out algolia feasibility. I did go through their documentation but couldn't find right answer. Any help appreciated.
Best example of our data is classified. In dataset we have geolocation(lat-lng) and category
[
{
category: 'retail_shops',
"_geoloc": {
"lat": 40.639751,
"lng": -73.778925
},
title: 'walget1'
},
{
category: 'retail_shops',
"_geoloc": {
"lat": 40.639751,
"lng": -73.778925
},
title: 'walget1'
},
]
Search Requirement
sortby category, selected category first, then all other in anyorder
sortby geolocation, this is secondary filter
We need to display all data in the system but selected category first and distance as secondary.
I am trying to do this is javascript, did find find search and searchForFacetValues methods but documentation is all-around. Any ideal how to achieve this? don't need code but general guidance will definitely help.

In order to do so, you can use a multiple-queries strategy, where you would have a query with a filter that matches the category, and then another query that matches all objects not in the category. In the query parameters you can then use the aroundLatLng field or the aroundLatLngViaIp field.
The following snippet should help you get a better understanding of how to achieve what you want:
const client = algoliasearch('ApplicationID', 'apiKey');
// these two values would be obtained from somewhere (like InstantSearch)
const query = 'walg';
const category = 'retail_shops';
const queries = [{
indexName: 'products',
query: `${query}`,
params: {
hitsPerPage: 3,
filters: `category:${category}`,
aroundLatLngViaIP:true
}
}, {
indexName: 'products',
query: `${query}`,
params: {
hitsPerPage: 10,
filters: `NOT category:${category}`,
aroundLatLngViaIP:true
}
}];
const searchCallback = (err, content) => {
if (err) {
console.error(err);
return;
}
const categorySpecificResults = content.results[0];
for (var i = 0; i < categorySpecificResults.hits.length; ++i) {
console.log(categorySpecificResults.hits[i]);
}
const geolocatedOnlyResults = content.results[1];
for (var i = 0; i < geolocatedOnlyResults.hits.length; ++i) {
console.log(geolocatedOnlyResults.hits[i]);
}
}
// perform the 2 queries in a single API call:
// - 1st query targets index `products` with category filter and geolocation
// - 2nd query targets index `products` with negative category filter and geolocation
client.search(queries, searchCallback);
You will find more information about how to use multiple queries on this page:
How to use multiple Queries
If you want a more fine grained control on the geolocation, like for instance defining a precision or a maximum range for the search, you can find more about these different features on these pages:
aroundLatLng: used when you want to define the geographical center of your query
aroundRadius: used to limit a search to a certain range around the center of the query
aroundPrecision: defines how precise the ranking has to be when it comes to distance.

Related

How can I apply conditional filtering to MongoDB documents?

I am having a really hard time figuring out how to apply multiple levels of filtering to my mongoDB documents (not sure if this phrasing is correct).
I am trying to create an app that will allow users to perform a search and retrieve only those documents that match the filters they have chosen to apply. A user might chose to apply only one filter or combine multiple filters.
For example, the user might be looking for a house. The available filters could be location, size and type. If the user applies the location filter with a value of ‘London’, they should get only those houses available in London. If they choose to combine the above location with the type filter with a value of ‘2-bedroom-apartment’, they should get all 2-bedroom apartments available in London.
How can I make sure that the results are conditionally filtered, depending on the filters that the user has applied?
I think I am supposed to use $match, but I don’t understand if I can use multiple queries with it.
What I have come up with so far is the following:
const getFilteredData = async(req, res) => {
try {
const { filter1, filter2, filter3 } = req.query;
const filteredData = await dataModel.aggregate([
{$match:{
$and:[{filter1:filter1},{filter2: filter2}, {filter3:filter3}] //1st option: all of the filters are applied by the user
}}
])
res.status(201).json({data: filteredData});
}
catch(err) {
res.status(404).json({message: err.message});
}
}
With the above code, the results are filtered only when all 3 filters are being applied. How can I cater to different combinations of filters being applied by the user (only one filter, filter1 & filter3 combined etc)?
Any help will be massively appreciated.
Assuming req.query can be {name: "Coat", city: "Athens"} You can do something like:
const getFilteredData = async(req, res) => {
try {
const filtersArr = [];
for (const filterKey of ['name', 'city', 'category']) {
if (req.query[filterKey]) {
const thisFilter = {};
thisFilter[filterKey] = req.query[filterKey];
filtersArr.push(thisFilter);
}
}
console.log(filtersArr)
const filteredData = await filteredDataModel.aggregate([
{$match:{
$and: filtersArr //1st option: all of the filters are applied by the user
}}
])
res.status(201).json({data: filteredData});
}
catch(err) {
res.status(404).json({message: err.message});
}
}
You can also use the original req.query like this:
const filteredData = await filteredDataModel.find(req.query)
But iterating using the code allows you to validate the keys that you want...

JSONB filter on select via Supabase

I have such a logic (attributes column's type is JSONB - array of objects) that works:
But I want to implement logical OR here if trait_type is equal ... not AND:
JSONB's column structure:
[
{
"value":"Standard Issue Armor 1 (Purple)",
"trait_type":"Clothes"
},
{
"value":"Standard Issue Helmet 1 (Red)",
"trait_type":"Full Helmet"
},
{
"value":"Chrome",
"trait_type":"SmartSkin"
},
{
"value":"Base Drone (Blue)",
"trait_type":"Drone"
},
{
"value":"Thick",
"trait_type":"Eyebrows"
}
]
How that could be done?
Thanks in advance!
I didn't verify the code, so might not work, but I believe at least is in the right direction. You can use the .or() filter to connect multiple filters with logical or operator. For contains(), you can use the cs keyword inside the or filter like this:
const { data, error } = await supabase.from('NTFs')
.select('name, id_in_collection, owner_address')
.eq('collection_id', Number(id))
.contains('attributes', JSON.stringify([{trait_type: 'SmartSkin', value: 'Chrome'}]))
.or(`attributes.cs.${JSON.stringify([{trait_type: 'Drone', value: 'Armed Drone (Blue)'}])}`, `attributes.cs.${JSON.stringify([{trait_type: 'Drone', value: 'Armed Drone (Green)'}])}`)
.order('id_in_collection')
.range(fromIndex, toIndex)

Mongodb/Meteor: combine documents with ids in many arrays(returned by another query)

I am using meteor to make a query( for a publication) to find all the Results generated by Workers which has finished their work:
Results has the following structure:
result example 1:
{
_id: "sldf234sdf"
result_a:0,
result_b:0
}
result example 2:
{
_id: "ghjwef23qql"
result_a:0,
result_b:0
}
Workers is defined as:
{
_id: "iweyr23s"
results:["sldf234sdf", "ghjwef23qql"], //here is a list of
tag:'running'
}
Here is what I am trying to do:
// 1), I want to find all the workers which is finished with their tag
const workers = Workers.find({tag:'done'});
// 2), I want to get the resultid arrays in all the workers, then combine it into a big array
const results_id_arrays = workers[0].results + workers[1].results + ...
const results = Results.find({_id:{$in: results_id_arrays }});
So, my question is, how to make a mongodb query to implement the second step?
Is this what you are looking for?
you can get results ids like this:
const results_id_arrays = Workers.aggregate({$project:{a:'$results'}},
{$unwind:'$a'},
{$unwind:'$a'},
{$group:{_id:'a',res:{$addToSet:'$a'}}}).map(function(e) {return e.res})
and then
const results = Results.find({_id:{$in: results_id_arrays }});
does this work?
You can use the mighty underscorejs for this. There is also a meteor package for it. Check documentation here.
//fetch results
const workers = Workers.find({tag:'done'}, {results: 1, _id: 0}).fetch();
//pluck only the ids, without field name
const plucked = _.pluck(workers, 'results');
//use plucked ids to find results
return Results.find({_id:{$in: plucked }});

Meteorjs - What is the proper way to join collections on backend

I am very new to meteor.js and try to build an application with it. This time I wanted to try it over MEAN stack but at this point I am struggled to understand how to join two collection on server side...
I want very identical behaviour like mongodb populate to fetch some properties of inner document.
Let me tell you about my collection it is something like this
{
name: 'Name',
lastName: 'LastName',
anotherObject: '_id of another object'
}
and another object has some fields
{
neededField1: 'asd',
neededField2: 'zxc',
notNeededField: 'qwe'
}
So whenever I made a REST call to retrieve the first object I want it contains only neededFields of inner object so I need join them at backend but I cannot find a proper way to do it.
So far while searching it I saw some packages here is the list
Meteor Collections Helper
Publish with Relations
Reactive joins in Meteor (article)
Joins in Meteor.js (article)
Meteor Publish Composite
You will find the reywood:publish-composite useful for "joining" related collections even though SQL-like joins are not really practical in Mongo and Meteor. What you'll end up with is the appropriate documents and fields from each collection.
Using myCollection and otherCollection as pseudonyms for your two collections:
Meteor.publishComposite('pseudoJoin', {
find: function() {
return myCollection.find();
},
children: [
{
find: function(doc) {
return otherCollection.find(
{ _id: post.anotherObject },
{ fields: { neededField1: 1, neededField2: 1 } });
}
}
]
});
Note that the _id field of the otherCollection will be included automatically even though it isn't in the list of fields.
Update based on comments
Since you're only looking to return data to a REST call you don't have to worry about cursors or reactivity.
var myArray = myCollection.find().fetch();
var myOtherObject = {};
var joinedArray = myArray.map(function(el){
myOtherObject = otherCollection.findOne({ _id: el.anotherObject });
return {
_id: el._id,
name: el.name,
lastName: el.lastName,
neededField1: myOtherObject.neededField1,
neededField2: myOtherObject.neededField2
}
});
console.log(joinedArray); // These should be the droids you're looking for
This is based on a 1:1 relation. If there are many related objects then you have to repeat the parent object to the number of children.

mapreduce between consecutive documents

Setup:
I got a large collection with the following entries
Name - String
Begin - time stamp
End - time stamp
Problem:
I want to get the gaps between documents, Using the map-reduce paradigm.
Approach:
I'm trying to set a new collection of pairs mid, after that I can compute differences from it using $unwind and Pair[1].Begin - Pair[0].End
function map(){
emit(0, this)
}
function reduce(){
var i = 0;
var pairs = [];
while ( i < values.length -1){
pairs.push([values[i], values[i+1]]);
i = i + 1;
}
return {"pairs":pairs};
}
db.collection.mapReduce(map, reduce, sort:{begin:1}, out:{replace:"mid"})
This works with limited number of document because of the 16MB document cap. I'm not sure if I need to get the collection into memory and doing it there, How else can I approach this problem?
The mapReduce function of MongoDB has a different way of handling what you propose than the method you are using to solve it. The key factor here is "keeping" the "previous" document in order to make the comparison to the next.
The actual mechanism that supports this is the "scope" functionality, which allows a sort of "global" variable approach to use in the overall code. As you will see, what you are asking when that is considered takes no "reduction" at all as there is no "grouping", just emission of document "pair" data:
db.collection.mapReduce(
function() {
if ( last == null ) {
last = this;
} else {
emit(
{
"start_id": last._id,
"end_id": this._id
},
this.Begin - last.End
);
last = this;
}
},
function() {}, // no reduction required
{
"out": { "inline": 1 },
"scope": { "last": null }
}
)
Out with a collection as the output as required to your size.
But this way by using a "global" to keep the last document then the code is both simple and efficient.