adamfairholm / Elasticquent

Map Larvel Eloquent models to Elasticsearch types
MIT License
201 stars 38 forks source link

Searching multiple types/models #29

Open paulocoghi opened 9 years ago

paulocoghi commented 9 years ago

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

alfchee commented 9 years ago

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

ilikourou commented 9 years ago

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

timgws commented 9 years ago

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.

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.

ilikourou commented 9 years ago

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.

alfchee commented 9 years ago

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);
    }
timgws commented 9 years ago

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

timgws commented 9 years ago

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.

chongkan commented 8 years ago

+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(); });

taledo commented 8 years ago

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;
aniltekinarslan commented 5 years ago

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);