nicolaslopezj / searchable

A php trait to search laravel models
MIT License
2.01k stars 291 forks source link

Is it possible to make advanced queries using pivot tables? #41

Open subsider opened 9 years ago

subsider commented 9 years ago

Hi, Having the Product model with the following relationship through products_categories table:

    public function sectors()
    {
        return $this->belongsToMany('Category', 'products_categories', 'product_id', 'category_id');
    }

how can I use $searchable in order to join products with categories? In the readme file you show an example using 1:N relationships (user has many posts), but no idea how to reflect N:M relationship with this

Thanks in advance

sergiocastrovale commented 9 years ago

I'd like to know this as well. I have a Post table which links to category_post (pivot) and I'd like to be able to search for a Category and retrieve results.

sergiocastrovale commented 9 years ago

Basically, this: http://stackoverflow.com/a/17774479/2261056

emacaste commented 9 years ago

@sergiocastrovale how you do the trick?

sergiocastrovale commented 9 years ago

Hi, this is working for me. It joins 3 tables which have pivots to Texture.

protected $searchable = [
    'columns' => [
        'textures.name' => 20,
        'tags.name' => 10,
        'categories.name' => 5,
        'sizes.name' => 25
    ],
    'joins' => [
        'category_texture' => ['category_texture.texture_id', 'textures.id'],
        'categories' => ['categories.id','category_texture.category_id'],
        'tag_texture' => ['tag_texture.texture_id','textures.id'],
        'tags' => ['tags.id','tag_texture.tag_id'],
        'size_texture' => ['size_texture.texture_id','textures.id'],
        'sizes' => ['sizes.id','size_texture.size_id'],
    ],
];

And then:

Texture::with(['categories', 'tags', 'sizes'])->search($text)->distinct('textures.id')->get();

Please let me know if you find any issues with this - they might appear on my end as well.

emacaste commented 9 years ago

@sergiocastrovale Thx, it works very well, but it is veeeeeeeeery slow (due to subqueries i think). Don't you know if there is a more optimized solution for pivot search out of there?

sergiocastrovale commented 9 years ago

I'm not sure. This took a while to get right and it's the best way I could find following the documentation of the package. However if @nicolaslopezj could jump in on this with a better solution, that would be awesome.

emacaste commented 9 years ago

FYI, i have a lot of relations hasmany and manytomany with thousands records each...maybe that is the cause of slowness (in combination with subqueries)

sergiocastrovale commented 9 years ago

Yeah, it makes sense. I'm using a very small dummy database here, so I don't feel any real impact at all, but when the project goes live I'll have to browse through 15k+ results so your problem will be my problem as well ...

emacaste commented 9 years ago

@sergiocastrovale using straight eloquent methods dramatically speed up queries. Now i'm using whereHas, orWhereHas, etc. and mysql took 1.3 seconds, whereas with searchable i have to wait 12-13 seconds... I've lost score system (very interesting), but for now i privilege speed.

Maybe a good solution would be mixup eloquent for joins and score system?

sergiocastrovale commented 9 years ago

Could you post your equivalent query in straight eloquent? I'm not able to test it now but I'd like to check it tomorrow. The score system isn't my top priority as well (although it would be nice to have it).

emacaste commented 9 years ago

Sure, here the code:

$term = Input::get('term') . '%';

        $items = Item::with('subject', 'type')
            ->orWhereHas('subject', function ($query) use ($term) {
                $query->where('nome', 'LIKE', $term);
            })
            ->orWhereHas('author', function ($query) use ($term) {
                $query->where('nome', 'LIKE', $term);
            })
            ->orWhere('titolo', 'LIKE', $term)
            ->orWhere('descrizione', 'LIKE', $term)
            ->orWhere('commento', 'LIKE', $term)
            ->orWhere('note', 'LIKE', $term)
            ->orWhere('isbn', 'LIKE', $term)
            ->paginate(20);

Subject and Author are ManyToMany with Item. Eloquent translate orwherehas into subquery that not is the best approach, but for now it works well for me.

sergiocastrovale commented 9 years ago

Thanks a lot, I'll take a look at this tomorrow. But perhaps the best results will show up when I have the full database with me.

jarektkaczyk commented 9 years ago

@beniaminorossini Show the relations and number of rows involved, that make your query slow.

With core eloquent features, there's no way to achieve scoring using whereHas, but I'm on it right now and hopefully I'll find the most optimized solution to the issue soon.

emacaste commented 9 years ago

@jarektkaczyk thx a lot!

jarektkaczyk commented 9 years ago

@beniaminorossini @sergiocastrovale @nicolaslopezj

So I've worked on improving performance and usability, and here it goes: https://github.com/jarektkaczyk/eloquence/wiki/Builder---searchable-and-more

Check it out guys, the scoring algo is based on the one use here by Nicolas as well as the basic idea, however it is greatly improved:

Example:

User::search(
    '"Billy the Kid" "* Billy the Kid *" Billy Kid', // phrase with wildcards 
    ['name', 'posts.title', 'posts.comments.body'], // specify related columns 
    false // turn off fulltext 
)

It written from scratch with totally different approach in mind, so it couldn't be incorporated here as any PR, no intention to undervalue your great work Nicolas.

Worth mentioning that you don't have to worry about the joins - it works on eloquent relations with dot nesting, just like with('some.relation') method, so you literally don't have to touch your model (just use the base trait shipped with the package).

Give it a try and let me know.

emacaste commented 9 years ago

thx so much @jarektkaczyk i will try it soon

sergiocastrovale commented 9 years ago

That looks promising! I will see if my client lets me try this ;)