lucianosnchz's avatar

Search is too slow (Models relationships)

Hello!! I want to know if you can help me. When I search for a term, it takes a long time to process the query. What do you recommend to optimize this code?

 public function scopeBuscadorClientes($query, $buscar, $sucursal, $tipo_prop, $frecuencia, $fuente, $ult_visita)
    {
        return $this->where('nombre', 'like', '%'.$buscar.'%')
                    ->orWhere('apellido', 'like', '%'.$buscar.'%')
                    ->orWhere('email', 'like', '%'.$buscar.'%')
                    ->orWhere('id_secundario', 'like', '%'.$buscar.'%')
                    ->orWhereHas('telefonos', function($query) use ($buscar){
                        $query->where('numero', 'like', '%'.$buscar.'%');
                    })
                    ->orWhereHas('propiedades.telefonos', function($query) use ($buscar){
                        $query->where('numero', 'like', '%'.$buscar.'%');
                    })
                    ->orWhereHas('propiedades', function($query) use ($buscar){
                        $query->where('direccion', 'like', '%'.$buscar.'%');
                    })
                    ->when($sucursal, function ($query) use ($sucursal) {
                        
                        return $query->whereHas('propiedades', function ($q) use ($sucursal) {
                            $q->where('sucursal_id', $sucursal);
                        });
                    })
                    ->when($tipo_prop, function ($query) use ($tipo_prop) {
                        return $query->whereHas('propiedades', function ($q) use ($tipo_prop) {
                            $q->where('tipo_id', $tipo_prop);
                        });
                    })
                    ->when($frecuencia, function ($query) use ($frecuencia) {
                        
                        return $query->whereHas('propiedades', function ($q) use ($frecuencia) {
                            $q->where('frecuencia_id', $frecuencia);
                        });
                    })
                    ->when($fuente, function ($query) use ($fuente) {
                        
                        return $query->where('fuente_id', $fuente);
                    })
                    ->when($ult_visita, function ($query) use ($ult_visita) {
                            return $query->whereHas('propiedades', function ($q) use ($ult_visita) {
                                $q->whereDoesntHave('visitas', function ($q) use ($ult_visita) {
                                    $q->whereBetween('fecha', [$ult_visita, Carbon::now()]);
                                });
                            });
                    });

Thanks

0 likes
4 replies
LaryAI's avatar
Level 58

One possible solution to optimize this code is to use eager loading to reduce the number of database queries. This can be done by adding the "with" method to the query, specifying the related models to be loaded. For example, if the "telefonos" and "propiedades" models are related to the "clientes" model, the query can be modified as follows:

public function scopeBuscadorClientes($query, $buscar, $sucursal, $tipo_prop, $frecuencia, $fuente, $ult_visita)
{
    return $this->with(['telefonos', 'propiedades.telefonos', 'propiedades.visitas'])
                ->where('nombre', 'like', '%'.$buscar.'%')
                ->orWhere('apellido', 'like', '%'.$buscar.'%')
                ->orWhere('email', 'like', '%'.$buscar.'%')
                ->orWhere('id_secundario', 'like', '%'.$buscar.'%')
                ->orWhereHas('telefonos', function($query) use ($buscar){
                    $query->where('numero', 'like', '%'.$buscar.'%');
                })
                ->orWhereHas('propiedades.telefonos', function($query) use ($buscar){
                    $query->where('numero', 'like', '%'.$buscar.'%');
                })
                ->orWhereHas('propiedades', function($query) use ($buscar){
                    $$query->where('direccion', 'like', '%'.$buscar.'%');
                })
                ->when($sucursal, function ($query) use ($sucursal) {
                    return $query->whereHas('propiedades', function ($q) use ($sucursal) {
                        $q->where('sucursal_id', $sucursal);
                    });
                })
                ->when($tipo_prop, function ($query) use ($tipo_prop) {
                    return $query->whereHas('propiedades', function ($q) use ($tipo_prop) {
                        $q->where('tipo_id', $tipo_prop);
                    });
                })
                ->when($frecuencia, function ($query) use ($frecuencia) {
                    return $query->whereHas('propiedades', function ($q) use ($frecuencia) {
                        $q->where('frecuencia_id', $frecuencia);
                    });
                })
                ->when($fuente, function ($query) use ($fuente) {
                    return $query->where('fuente_id', $fuente);
                })
                ->when($ult_visita, function ($query) use ($ult_visita) {
                    return $query->whereHas('propiedades', function ($q) use ($ult_visita) {
                        $q->whereDoesntHave('visitas', function ($q) use ($ult_visita) {
                            $q->whereBetween('fecha', [$ult_visita, Carbon::now()]);
                        });
                    });
                });
}

This will load all the related "telefonos", "propiedades.telefonos", and "propiedades.visitas" models in a single query, instead of making separate queries for each related model. This can significantly improve the performance of the search function.

Another possible optimization is to use indexing on the database tables to speed up the search queries. This can be done by adding indexes to the columns that are frequently searched or filtered. For example, if the "nombre", "apellido", "email", and "id_secundario" columns are frequently searched, they can be indexed as follows:

ALTER TABLE clientes ADD INDEX nombre_idx (nombre);
ALTER TABLE clientes ADD INDEX apellido_idx (apellido);
ALTER TABLE clientes ADD INDEX email_idx (email);
ALTER TABLE clientes ADD INDEX id_secundario_idx (id_secundario);

This will create indexes on the specified columns, which can speed up the search queries that use these columns. However, adding too many indexes can also slow down the database performance, so it's important to only add indexes that are necessary for the search queries.

psrz's avatar

First, I strongly suggest watching https://laracasts.com/series/eloquent-performance-patterns, I think some of things you are doing are covered in that series.

Second, the indexes suggested by "Lary" are worthless when searching with wildcards to left and right (%some_value%)

You need to use fulltext indexes. I haven't used mysql in a long while so I'm not the best to guide you there but shouldn't be too problematic to implement.

Tray2's avatar

Convert your query to SQL, and then run EXPLAIN <your sql> , it will give you an execution plan. From that execution plan you will see what takes time, and which indexes are used or isn't used.

There is an art to knowing what to do to make a query run faster. However, as @psrz says, you are using like then you would need to use a fulltext index.

Tray2's avatar

@lucianosnchz Then you probably have more data in prod. You need to run explain on the query in production, then analyse the result.

Please or to participate in this conversation.