Optimizar Consultas con Eloquent en Laravel

Vamos a hablar sobre la importancia de optimizar las consultas a la base de datos en Laravel y conocer como hacerlas.

Partimos de una relación con los Post y una relación con la categoría es decir obviamente es como quien dice hereda las categorías las las relaciones que ya tenga la categoría de base que en este caso sería la de post y a qué demonios quiero llegar con todo esto que es la parte de optimización de los queries aquí puedes ver un poco como yo lo voy a optimizar pero para saber qué demonios tenemos que colocar aquí ya que el tema sobre todo es en la de listado es decir la de Index que sería la tabla que tenemos por acá Aquí es importante saber qué demonios estamos manejando para saber qu qué demonios tenemos que optimizar ya que si por ejemplo aquí no estamos utilizando la categoría:

<td>
   {{ $p->id }}
</td>
<td>
   {{ $p->title }}
</td>
<td>
   {{ $p->posted }}
</td>
<td>
   {{ $p->category->title }}
</td>

Y aquí no estuviéramos utilizando ni el post ni la categoría por lo tanto no haría falta traerla a quien basa la relación todo esto depende de lo que te soliciten para la página que estás construyendo en mi caso yo sí lo quiero colocar la categoría:

class Book extends Model
{
   use HasFactory;

   protected $fillable = ['title', 'subtitle', 'date', 'url_clean', 'description', 'content', 'image', 'path', 'page', 'posted', 'price', 'price_offers', 'post_id', 'user_id'];

   public function post()
   {
       return $this->belongsTo(Post::class)
           ->select(array('id', 'url_clean', 'title', 'category_id'));
   }
}

class Post extends Model
{
   protected $fillable = ['title', 'url_clean', 'content', 'category_id', 'posted', 'description', 'final_content', 'aux_content', 'web_content', 'image', 'path', 'date', 'type', 'post_id_language', 'language'];
  
   public function category()
   {
       return $this->belongsTo(Category::class)
       ->select(array('id', 'url_clean', 'title'));
   }
}

class Category extends Model
{
   protected $fillable = ['title', 'url_clean', 'image', 'body', 'body_en'];
}

Ya que es un poco lógico pero si es un libro un libro del Laravel yo tengo que colocar Cuál es la categoría en este caso no la coloqué directamente ya que la estoy trayendo del post no me hacía falta porque si no sería una dualidad ahí una de las características que tiene que tener una base de datos evitar la redundancia Entonces si le colocaba una categoría otra vez al Book no tenía sentido porque para como pudiera seleccionar una categoría distinta por ejemplo suponte php y Laravel entonces el post con Laravel y el Book con php no tiene sentido ahí para mí entonces lo manejo de esta forma y es el listado que puedes ver justamente no por aquí sino sería el de book por acá aquí tengo la categoría entonces suponte que sería un poco como quien dice que lo haríamos al inicio no le colocamos e aquí para las consultas:

Listado de book

Para armar esta consulta, hacemos la siguiente operación:

$books = Book
     ::with('post', 'post.category')
           ->select('id', 'title', 'subtitle', 'date', 'posted', 'post_id')->orderBy($this->sortColumn, $this->sortDirection);

Eloquent y Query Builder

Para armar esto tuvieras dos formas una es empleando los left join y join y demás que es un poco como lo dice aquí y la otra es empleando ya métodos de Eloquent como viene siendo el with que nos trae la relación recuerda que la diferencia es que aquí si no le colocá el with:

$books = Book::select('id', 'title', 'subtitle', 'date', 'posted', 'post_id')->orderBy($this->sortColumn, $this->sortDirection);

Lo dejáramos así de igual manera va a funcionar esto si instalas el plugin de debug bar como hacemos en el curso y ahí pudieras ver esto más en detalle Ah como quien dice a los primeros pasos pareciera que todo va Exactamente igual ya que fíjate que todo carga más o menos a la misma velocidad Pero si tú instalas el plugin de debugbar o directamente habilitas las consultas realizadas a la aplicación vas a ver que aquí tuviéramos el problema de n +1 en la cual por cada registros estamos haciendo una consulta adicional a la base de datos ya que lo que está pasando aquí Recuerda que en Laravel trabaja con el esquema Lazy loading lo que significa que va a traer la Data cuando se solicite es decir aquí por efecto no le está trayendo porque no estamos colocando aquí el with por lo tanto cuando aquí referenciamos el post va a ver que no existe Book y va a ser una consulta la base de datos Y adicionalmente aquí tuviéramos otro problema y es que aquí también estamos trayendo la categoría:

{{ $b->id }}
{{ $b->title }}
{{ $b->subtitle }}
{{ $b->date->format('d-m-Y') }}
{{ $b->post->category->title }}
{{ $b->posted }}

Listado libros index

Por lo tanto una vez traído el post también traería luego la categoría así que sería un una cosa así como problema de 2 por n + 1 por qué serían por cada registro dos consultas adicionales a la base de datos cosa que sería un problema la cuestión es que aquí en desarrollo siempre es difícil de verlo porque es un ambiente todo local todo funciona bastante rápido pero cuando vamos a producción y múltiples personas se están conectando a la aplicación Ahí sí vas a tener muchos problemas entonces la primera lo primero que tienes que hacer aquí es en base a lo a las relaciones que estamos manejando desde el listado ya que siempre el problema es en el listado sobre todo aquí tenemos que hacer esta optimización ya sea mediante join left join o lo que tú quieras o directamente con el with que sería aquí siempre lo recomendado.

Primera recomendación, usa el with o joins

Así que primera recomendación Cuál es la siguiente recomendación aquí un poco lo mismo pero en base a los datos que vas a manejar desde acá aquí colocas un Select esto puede que no parezca tanto pero aún así Recuerda que con esto estás evitando que desde la conexión a la base de datos traiga más datos que no vas a emplear sobre todo y comentando un poquito lo que te indicaba al inicio.

Según da recomendación, trae solamente las columnas que vas a usar

Para el post tengo un campo de content para toda la publicación para la categoría tengo también un campo de content para lo que es una un super post y también para Book también tengo un campo de content que es para el contenido HTML, pero, NO lo empleamos en el listado, por lo tanto, NO deberíamos de traerlo.

Recuerda que estamos trayendo menos datos de la base de datos Y eso se agradece a la final porque estás utilizando menos recursos y aquí volvemos a lo mismo que el objetivo es optimizar nuestras consultas entiéndase simplemente devolver lo mínimo y necesario y no más entonces eso es importantísimo aquí un detalle con el Select es que si tú por ejemplo aquí no colocas el depos ID entonces básicamente todo va a tronar era algo que me costaba un poquito al inicio no lo leía la documentación oficial puede que lo diga pero es que tan larga y vas a ver que te indica que categoría es nula todo el mundo es nulo y todo se fue al demonio Qué es lo que está pasando aquí que no puede traer aquí el post independientemente si lo estás especificando aquí con el with o no porque no tiene como quien dice el conector él necesita la columna una relacional para poder traer a sus amiguitos que sería el post y la categoría Entonces aunque no estemos utilizando esto de manera directa aquí en el lado tiene que estar aquí presente Entonces eso es un detallazo que tienes que tener en mente o si no te va a dar un error eso por un lado Cuál es el siguiente problema Okay como te comentaba yo no quiero traer del post Su contenido porque yo no lo voy a emplear entonces cómo demonios hago aquí para limitarlo aquí tenemos un par de maneras:

Una es especificando aquí los campos con los cuales vas a trabajar en este caso yo quiero campo de categoría ID entonces puedo colocar dos puntos y category ID con esto estamos indicando Cuáles campos queremos traer y aquí puedes colocar el resto por ejemplo en mi caso posted y demás En caso de que lo vaya a utilizar no lo voy a utilizar pero es para dar un ejemplo es decir separa los campos por comas:

Post::with('category:id,url_clean,title')

Pero vas a ver que no sé por qué razón hay veces cuando estoy utilizando tanto el Select con el with.

Otra forma es empleando desde el select con el with en el modelo con Campos exclusivos:

class Post extends Model
{
   protected $fillable = ['title', 'url_clean', 'content', 'category_id', 'posted', 'description', 'final_content', 'aux_content', 'web_content', 'image', 'path', 'date', 'type', 'post_id_language', 'language'];
  
   public function category()
   {
       return $this->belongsTo(Category::class)
       ->select(array('id', 'url_clean', 'title'));
   }
}

Rest API

Si no lo puedes pasar por acá entonces pásalo directamente desde la relación tal cual te mostraba por aquí entonces finalmente todo lo mencionado también lo tienes que aplicar a una RestAPI creo que tiene todo el sentido del mundo si para lo que es las consultas normalitas que tenemos aquí para pintar una tabla tiene sentido optimizar las consultas para una RestAPI creo que viene siendo más que obligatorio aquí ya también tengo otra consulta:

Book::select('books.title', 'books.subtitle', 'books.date', 'books.url_clean', 'books.description', 'books.image', 'books.path', 'books.page', 'books.posted', 'books.price', 'books.price_offers', 'books.post_id', DB::raw('DATE_FORMAT(file_payments.created_at, "%d-%m-%Y %H:%i") as date_buyed'), 'file_payments.payments')->leftJoin('file_payments', function ($leftJoin) use ($user) {
                        $leftJoin
                            ->on('books.id', 'file_payments.file_paymentable_id')
                            ->where("file_paymentable_type", Book::class)
                            ->where('file_payments.user_id', $user->id)
                            ->where('file_payments.unenroll');
                    })->where('posted', 'yes')
                    ->get()

En este caso estoy empleando los left join sin motivo alguno pero así lo implemente y aquí otra vez con el Select en este caso no empleé el Wi porque otra vez estoy empleando aquí los l join Aunque voy mejor para esta que es más sencilla ya que la de arriba es cuando está autenticado por aquí estoy indicando Cuáles son los datos que yo quiero en este caso otra vez sería la de listado y no necesito el contenido por lo mencionado antes usualmente no se utiliza Así que coloco los campos con los cuales yo voy a trabajar y Bueno aquí puedes ver un poco la diferencia Aquí tengo la rapi esto sería en base al al especificado no tengo la de content solamente la de post con los campos que tengo de la relación Es decir aquí Tampoco trae la de content por lo mencionado aquí en la por acá si hay que colocara content que lo va a tener que colocar porque voy a jalarlo desde el detalle del libro no quiero otro aquí aparecería todo esto esto luego tengo que ver cómo optimizo eso pero bueno de momento no lo tengo entonces quedaría así Creo que es bastante fácil aquí entender pero sin llegar aquí a la relación otra vez la directa aquí sería la de Book que también tiene un content aquí está y Bueno aquí puedes comparar rapidito esto Qué pasa si quito todo esto coloco get puedes ver que ahora también tengo la de content que es básicamente una página Entonces si para lo otro tenía sentido para un módulo de dashboard para una raspi creo que tiene mucho más sentido ya que aquí es un poco diferente primero porque estás colocando datos que realmente no vas a emplear como viene siendo la de contempor listado y segundo que sea lo que sea que vaya a consumir esto que usualmente va a ser un dispositivo móvil la página Esto va a pesar Por decirlo de alguna manera coloquial mucho más que lo que teníamos aquí inicialmente por lo tanto aquí es más que obligatorio también optimizar tus peticiones para manejar sol los datos con los cuales vas a trabajar ya que otra vez sea lo que sea que vaya a consumir esta Api no es que va a consumir uno por uno es que tienes que esperar que se cargue toda la página que obviamente va a tener un peso mayor y sería considerablemente mayor porque tiene más contenido Aunque a la final no pesa tanto pero aún así es bueno optimizarlo cuando tenía contenido a cuando no lo tenía y aparte esto también le sumas toda la carga extra que va a tener la base de datos sin necesidad alguna aparte que también ganas en modularización y organización de tu proyecto Así que sí o sí Siempre tienes que optimizar tus consultas Así que eso era el mensaje que te quería dar sin más que decir nos vemos en otro video

- Andrés Cruz

In english

Andrés Cruz

Desarrollo con Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz En Udemy

Acepto recibir anuncios de interes sobre este Blog.