Elasticquent icon indicating copy to clipboard operation
Elasticquent copied to clipboard

Searching multiple types/models

Open paulocoghi opened this issue 9 years ago • 10 comments

Is it possible to search multiple types/models at the same time with Elasticquent?

paulocoghi avatar May 20 '15 17:05 paulocoghi

I'm very interested in the answer too, or functionality

alfchee avatar Jun 17 '15 00:06 alfchee

I would also like to know if this is possible. Thanks.

ilikourou avatar Jun 22 '15 18:06 ilikourou

Presently it's not possible in Elastiquent, it is however possible using ElasticSearch:

By not limiting our search to a particular index or type, we have searched across all documents in the cluster. Elasticsearch forwarded the search request in parallel to a primary or replica of every shard in the cluster, gathered the results to select the overall top 10, and returned them to us.

  • https://www.elastic.co/guide/en/elasticsearch/guide/current/multi-index-multi-type.html

Changing Elasticquent to search multiple models at the same time will require a bit of modification (and also from the source level would not make sense, currently).

https://github.com/adamfairholm/Elasticquent/blob/master/src/ElasticquentTrait.php#L244-L269

    public static function searchByQuery($query = null, $aggregations = null, $sourceFields = null, $limit = null, $offset = null, $sort = null)
    {
        $instance = new static;
        $params = $instance->getBasicEsParams(true, true, true, $limit, $offset);
        if ($sourceFields) {
            $params['body']['_source']['include'] = $sourceFields;
        }
        if ($query) {
            $params['body']['query'] = $query;
        }
        if ($aggregations) {
            $params['body']['aggs'] = $aggregations;
        }

        if ($sort) {
            $params['body']['sort'] = $sort;
        }
        $result = $instance->getElasticSearchClient()->search($params);
        return new ResultCollection($result, $instance = new static);
    }

The problem here is that ResultCollection($result, $instance = new static); essentially says that the ElasticquentResultCollection item that is returned will be based on the model that you initiate the search on.

Searching multiple indexes at a time will require a new class that performs just a search, and somehow the collection that is returned should be able to differentiate between different models.

Adding this would make the code more complex, and I can't see too many benefits due to adding it.

timgws avatar Jun 23 '15 01:06 timgws

Usually you don't build a Laravel app based only on one model. What happens if you have to search the whole site for something, is there any other way to do it?

I tried making two different searches (on different types) and then use the merge() method to merge the returned collections, but it didn't work.

ilikourou avatar Jun 23 '15 05:06 ilikourou

I've tried to use merge() too, and doesn't work because is not the same method of \Illuminate\Support\Collection , what I have to do to merge the results was something like this:

   $result = new \Illuminate\Support\Collection([]);

    //query
    $models1 = FirstModel::search($query);
    $models2 = SecondModel::search($query);

    // work the collection
    foreach($models1 as $model1) {
        $result->push($model1);
    }
    foreach($models2 as $model2) {
        $result->push($model2);
    }

alfchee avatar Jun 23 '15 15:06 alfchee

OK, I will look into why the merging is not working a little later tonight, then. It definitely should be OK.

timgws avatar Jun 24 '15 00:06 timgws

Edit: I said I would look into why the merging is not working a little later tonight, but a quick check shows why merge will not work.

ResultCollection extends Illuminate\Database\Eloquent\Collection.

The merge function creates a new instance of the class you are calling merge on.

@alfchee this will mean in your case that when you are attempting to merge SecondModel into FirstModel, all of the values from SecondModel are being merged into a new FirstModel instance (which is usually not what you want to do).

This brings me back to my original comment:

Changing Elasticquent to search multiple models at the same time will require a bit of modification (and also from the source level would not make sense, currently).

The best way to do this might be to create a new Facade (called Search?) that allows you to perform multiple searches based on the models that you want to search on, then merging them similar to what @alfchee has shown above.

timgws avatar Jun 24 '15 00:06 timgws

+1 for alfchee's method.

I needed to show results from different tables: Articles, Blog entries, Deals, etc. And I needed to display them in order of relevance score, regardless of the type of document.

For sorting based on the documentScore:

$result->sortByDesc(function ($item){ return $item->documentScore(); });

chongkan avatar Nov 06 '15 21:11 chongkan

I too would find a nice solution very valuable, as like some said earlier, many Laravel apps are built on many models, with relationships etc.

Anyhow, I got around this using alfchee's method above. It's a little hacky, but it get's my results and I've also grouped them so I can later iterate and "break up" my search results by type etc...

$books = Book::search($query);
$articles = Article::search($query);

$all = array_merge(
  array(
    'books' => $books->all(),
    'articles' => $articles->all()
  )
);

//Can now iterate on this in my view etc..
return $all;

taledo avatar Mar 10 '16 05:03 taledo

I wrote basic && useful function. You can edit search filters or query in if blocks.

    public function searchMultipleTypes($tableNames = [], $queryText = '')
    {
        if(!$tableNames || $queryText == '')
            return [];

        $all = [];

        foreach ($tableNames as $table)
        {
            if($table == (new Category())->getTable())
            {
                $params['body']['query']['match']['title'] = $queryText;
                $all[$table] = Category::complexSearch($params);
            }

            else if($table == (new Post())->getTable())
                $all[$table] = Post::search($queryText);

            else if($table == (new City())->getTable())
                $all[$table] = City::search($queryText);
        }

        return $all;
    }

and you can call with:

$searchTables = ['categories', 'posts', 'cities'];
        $data = searchMultipleTypes($searchTables, $queryText);

aniltekinarslan avatar Dec 11 '18 12:12 aniltekinarslan