Group search result from multiple indices to single hits in Algolia - php

I am trying to implement the Algolia search. I am using PHP.
Scenario:
I have three tables (products, resources, and news). I am currently using MultipleQueries (DOCs Link Here) from this documentation.
As a result, I am getting results in the following format as in the documentation.
{
"results": [
{
"hits": [
{
........
},
],
"page": 0,
"nbHits": 1,
"nbPages": 1,
"hitsPerPage": 20,
"processingTimeMS": 1,
"query": "jimmie paint",
"params": "query=jimmie+paint&attributesToRetrieve=firstname,lastname&hitsPerPage=50"
"index": "people"
},
{
"hits": [
{
...........
},
{
...........
}
],
"page": 0,
"nbHits": 1,
"nbPages": 1,
"hitsPerPage": 20,
"processingTimeMS": 1,
"query": "jimmie paint",
"params": "query=jimmie+paint&attributesToRetrieve=firstname,lastname&hitsPerPage=50"
"index": "famous_people"
},
{
..............
}
]
}
This is great, but WHAT I WANT is to group the results of 3 indices into single hits. Below is a sample I am expecting from the API.
{
"results": [
{
"hits": [
{
........
},
{
........
},
{
........
},
{
........
},
{
........
},
{
........
},
{
........
},
],
"page": 0,
"nbHits": 1,
"nbPages": 1,
"hitsPerPage": 20,
"processingTimeMS": 1,
"query": "jimmie paint",
"params": "query=jimmie+paint&attributesToRetrieve=firstname,lastname&hitsPerPage=50"
"index": "indices goes here"
},
]
}
I searched a lot but could not come with suitable solution. Is this even possible using Algolia. Any help would be greatly appreciated.
Thank you in advance.

There's no concept of aggregation across multiple indices baked into Algolia. You'll have to aggregate the hit records yourself via code before displaying.
It's more typical for Algolia users to display results from multiple indices in a federated way using one of the front end libraries. Autocomplete is great at this:
https://www.algolia.com/doc/guides/solutions/ecommerce/search/autocomplete/federated-search/#combining-different-data-sources

Related

Elasticsearch -- count number of keyword occurences in a document

Database: Elasticsearch v7.2
Application: Laravel v5.7
Using Elasticsearch/Elasticsearch (https://github.com/elastic/elasticsearch-php) Official PHP Library
I have a query_string query for Elasticsearch with this code to retrieve documents that have a certain phrase as I search throughout my index
[
"query_string" => [
"default_field" => $content,
"query" => $keywords
]
],
and the $keywords variable contains:
("MCU" OR "Marvel" OR "Spiderman")
Now, I want to count the NUMBER OF OCCURENCES of these words in the documents that I'm about to retrieve
I used the aggs query with this:
'aggs' => [
'count' => [
'terms' => [
'field' => 'content.keyword'
]
]
]
However, I have no idea how to associate these doc_count and display it in a matched manner with the hits -- because the key itself is the content, instead of the IDs
Im planning to display the whole document and pertain how many times the $keywords above have occurred in each document as Mentions
Is there other way to do the counting of occurrences without using the aggs in Elasticsearch?
If you only wants to count the occurrences of keywords, then you don't have to enable fielddata, try the filters aggs along with your query
GET my_index/_search
{
"query": {
"query_string": {
"default_field": "content",
"query": "MCU OR Marvel OR Spiderman"
}
},
"aggs": {
"count": {
"filters": {
"filters": {
"mcu": {
"match": {
"content": "MCU"
}
},
"marvel": {
"match": {
"content": "Marvel"
}
},
"spiderman": {
"match": {
"content": "Spiderman"
}
}
}
}
}
}
}
Result with be like below :
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"hits": {
"total": 4,
"max_score": 1.219939,
"hits": [
....
....
]
},
"aggregations": {
"count": {
"buckets": {
"marvel": {
"doc_count": 2
},
"mcu": {
"doc_count": 2
},
"spiderman": {
"doc_count": 1
}
}
}
}
}
Source : https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-filters-aggregation.html
Thanks to sir #AshrafulIslam, I was able to come up with Elasticsearch's feature called highlights. Though highlights literally emphasizes keywords that occur, I resorted to PHP's substr_count() function to count the <em> tags
I added this code as a sibling of the ['body']['query'] element:
"highlight" => [
"fields" => [
"content" => ["number_of_fragments" => 0]
],
'require_field_match' => false
]
Then as I loop through the ['hits']['hits'] array element, I performed something like this:
$articles = $client->search($params);
$hits = $articles['hits']['hits'];
for($i=0; $i<count($hits); $i++){
$hits[$i]['_source']['count_mentions'] = substr_count($hits[$i]['highlight']['content'][0],"<em>");
}
Enabling Fieldata may not be the best way to enable text search.
https://www.elastic.co/guide/en/elasticsearch/reference/current/fielddata.html#before-enabling-fielddata
Before you enable fielddata, consider why you are using a text field for aggregations, sorting, or in a script. It usually doesn’t make sense to do so.
A text field is analyzed before indexing so that a value like New York can be found by searching for new or for york. A terms aggregation on this field will return a new bucket and a york bucket, when you probably want a single bucket called New York.
Instead, you should have a text field for full text searches, and an unanalyzed keyword field with doc_values enabled for aggregations, as follows:
PUT my_index
{
"mappings": {
"properties": {
"my_field": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
}
}
}
}

Nested collection resolve in Laravel Resource

I've got two one-to-many relationships in Laravel, where Cities(id, name, region_id) <- Regions(id, name, country_id) <- Countries(id, name).
I'd need to create a Resource that can return all the Cities in all the Regions in a given Country, like:
"results": [
{
"region": 1,
"name": "Abruzzo",
"cities": {
{"id": 1,
"name": "Aaa"},
{"id": 2,
"name": "Aab"},
},
},
{
"region": 2,
"name": "Basilicata",
"cities": {
{"id": 1,
"name": "Baa"},
{"id": 2,
"name": "Bab"},
},
}
for just one level of relationship, i would create a RegionResource, a RegionsResource and use the Resolve() method inside a, let's say, ResultsResource, like:
'regions' => (new RegionsResource($country))->resolve(),
but I don't seem to be able to do it in a recursive way for both the levels. Could anyone help me? Thanks.
Solved thanks to my colleague. Here's the code:
$cities = collect();
$regions->each(function($region) use(&$cities) {
$cities->push($region->cities);
});

Whats the best approach to create filter-based search in Laravel or raw SQL?

I'm working on a query to find offers based in different filters and need some guidance on how to do that.
My model structure is basically: offer -> place -> categories -> filters -> filterOptions
offer (belongsTo) <-> (hasMany) place
place (belongsToMany) <-> (belongsToMany) category
place (belongsToMany) <-> (belongsToMany) filter
filter (belongsToMany) <-> (belongsToMany) category
filter (hasMany) <-> (belongsToMany) filterOption
Search params comes in request JSON just like this below, which says "get offers from places with category 1 and has those filters + filterOptions"
{
"category_id":1,
"filters":[
{
"filter_id":1,
"options":[
{"option_id":2},
{"option_id":3}
]
},
{
"filter_id":2,
"options":[
{"option_id":4},
{"option_id":6}
]
}
]
}
So far I'm just getting the entire list without filtering.
$q = Offer::with([
'place',
'place.categories',
'place.categories.filters',
'place.categories.filters.filterOptions',
])
This is returning the JSON objects like this:
[
{
"id": 4,
"place_id": 2,
"place": {
"id": 2,
"categories": [
{
"id": 3,
"name": "Mechanics",
"pivot": {
"place_id": 2,
"category_id": 3
},
"filters": [
{
"id": 5,
"name": "Services",
"pivot": {
"category_id": 3,
"filter_id": 5,
},
"filter_options": [
{
"id": 8,
"name": "Tires",
"filter_id": 5
},
{
"id": 9,
"name": "Painting",
"filter_id": 5
}
]
},
{
"id": 6,
"name": "Amenities",
"pivot": {
"category_id": 3,
"filter_id": 6,
},
"filter_options": [
{
"id": 11,
"name": "Wifi",
"filter_id": 6
},
{
"id": 12,
"name": "Food",
"filter_id": 6
}
]
}
]
}
]
}
}
]
I have no idea on what is the starting point to perform this filtering.
Should I use eloquent or raw query? single or multiple queries? joins or grouping?
Solved this way:
$params = json_decode($request->getContent());
$q = Offer::with([
'place:id,title',
'place.categories:id,name',
'place.categories.filters:id,name',
'place.categories.filters.filterOptions:id,name,filter_id'
])
->whereHas('place.categories', function($q) use ($params){
$q->where('id', $params->category_id);
})
->whereHas('place.categories.filters', function($q) use ($params){
$filters = array_flatten(array_pluck($params->filters, 'filter_id'));
$q->whereIn('id', $filters);
})
->whereHas('place.categories.filters.filterOptions', function($q) use ($params){
$filterOptions = array_flatten(array_pluck($params->filters, 'options.*.option_id'));
$q->whereIn('id', $filterOptions);
})
->select('id','percent','place_id')
->get();

QueryBuilder limit is working for only first row in cakephp 3.2 [duplicate]

This question already has an answer here:
How to limit contained associations per record/group?
(1 answer)
Closed 6 years ago.
Hii i am new to cakephp 3.2 v.
Here i have used model association (hasMany).
Here in bind section (campaign_videos) ,i want to fetch only one record ,
so for this ,i have put below code to manage it.
my actual data in db.
[
{
"id": 1,
"user_id": 95,
"campaign_videos": [
{
"id": 1,
"campaign_id": 1,
},
{
"id": 3,
"campaign_id": 1,
}
]
},
{
"id": 2,
"user_id": 95,
"campaign_videos": [
{
"id": 2,
"campaign_id": 2,
}
]
},
$fetchCampaignFirst = $this->Campaigns->find()->contain(['CampaignVideos' => ['queryBuilder' => function ($q) {
return $q->limit(1);
}]]);
I am getting this limit working for first data only ,not for others (others even not showing the fetched data).
Below i have written the output
Here i want to get an output like
[
{
"id": 1,
"user_id": 95,
"campaign_videos": [
{
"id": 1,
"campaign_id": 1,
},
]
},
{
"id": 2,
"user_id": 95,
"campaign_videos": [
{
"id": 2,
"campaign_id": 2,
}
]
}]
Only want the first record of campaign_videos.
Here after using the queryBuilder query , i am getting out put like.
[
{
"id": 1,
"user_id": 95,
"campaign_videos": [
{
"id": 1,
"campaign_id": 1,
},
]
},
{
"id": 2,
"user_id": 95,
"campaign_videos": [
]
}]
I am not getting any data for second id ,while data is present for it.
Please suggest me.
Thank you in advance.
Maybe I'm wrong (so feel free to downvote my answer) but I think it's not feasible using a simple query
in fact cake, when loading associated data, does a single query on the associated table and after that matches the rows with the corresponding ones in the main table.
So you would need to do a mysql query that find the first record of every category. i.e. something like what is described in this article
I think that the only (or maybe the simpler) solution is to loop through your records:
$campaigns= $this->Campaigns->find();
foreach($campaigns as $campaign)
{
$campaign->campaign_videos = $this->Campaigns->CampaignVideos-find()
->where(['campaign_id' => $campaign->id]
->order(['id' => 'asc'])
->limit(1);
}

How to get only first row of association table in cakephp 3.2 [duplicate]

This question already has an answer here:
How to limit contained associations per record/group?
(1 answer)
Closed 6 years ago.
Hii i am new to cakephp 3.2 v.
Here i have used model association (hasMany).
Here in bind section (campaign_videos) ,i want to fetch only one record ,
so for this ,i have put below code to manage it.
my actual data in db.
[
{
"id": 1,
"user_id": 95,
"campaign_videos": [
{
"id": 1,
"campaign_id": 1,
},
{
"id": 3,
"campaign_id": 1,
}
]
},
{
"id": 2,
"user_id": 95,
"campaign_videos": [
{
"id": 2,
"campaign_id": 2,
}
]
},
$fetchCampaignFirst = $this->Campaigns->find()->contain(['CampaignVideos' => ['queryBuilder' => function ($q) {
return $q->limit(1);
}]]);
I am getting this limit working for first data only ,not for others (others even not showing the fetched data).
Below i have written the output
Here i want to get an output like
[
{
"id": 1,
"user_id": 95,
"campaign_videos": [
{
"id": 1,
"campaign_id": 1,
},
]
},
{
"id": 2,
"user_id": 95,
"campaign_videos": [
{
"id": 2,
"campaign_id": 2,
}
]
}]
Only want the first record of campaign_videos.
Here after using the queryBuilder query , i am getting out put like.
[
{
"id": 1,
"user_id": 95,
"campaign_videos": [
{
"id": 1,
"campaign_id": 1,
},
]
},
{
"id": 2,
"user_id": 95,
"campaign_videos": [
]
}]
I am not getting any data for second id ,while data is present for it.
Please suggest me.
Thank you in advance.
Maybe I'm wrong (so feel free to downvote my answer) but I think it's not feasible using a simple query
in fact cake, when loading associated data, does a single query on the associated table and after that matches the rows with the corresponding ones in the main table.
So you would need to do a mysql query that find the first record of every category. i.e. something like what is described in this article
I think that the only (or maybe the simpler) solution is to loop through your records:
$campaigns= $this->Campaigns->find();
foreach($campaigns as $campaign)
{
$campaign->campaign_videos = $this->Campaigns->CampaignVideos-find()
->where(['campaign_id' => $campaign->id]
->order(['id' => 'asc'])
->limit(1);
}

Categories