Optimización de consultas SQL

Por defecto, Eloquent ORM obtiene todas las columnas de cada registro consultado. Una manera de optimizar tus consultas -sobretodo cuando requieres obtener una gran cantidad de datos- es seleccionar los campos específicos que necesitas mostrar en la vista o colocar en tus reportes. En esta lección veremos cómo lograr esto.

Esta lección incluye un video premium

Regístrate para ver este video y cientos de lecciones exclusivas.

Mira el código en GitHub: actual, resultado, comparación.

Seleccionar columnas específicas de una tabla con Eloquent

Seleccionar columnas específicas con el ORM Eloquent es muy sencillo, simplemente llama al método select pasando como primer argumento un arreglo con los campos que quieres seleccionar:

<?php

use App\Product;

$products = Product::query()
    ->select(['title', 'slug'])
    ->with('category')
    ->get();

Nota además que estoy utilizando el método estático query para comenzar a escribir la consulta. Esto es completamente opcional; sin embargo, en mi opinión luce mejor cuando necesitamos escribir una consulta con varias líneas, pero esto también es posible:

<?php

    $products = Product::select(['title', 'slug'])
        ->with('category')
        ->get();

El método select no se encuentra en la clase del modelo, pero Laravel utiliza los métodos mágicos __call y __callStatic para comenzar a construir la nueva consulta:

<?php
// Extracto de Illuminate\Database\Eloquent\Model

abstract class Model
{

    public function __call($method, $parameters)
    {
        //...

        return $this->forwardCallTo($this->newQuery(), $method, $parameters);
    }
}

Si ahora cargamos la página veremos el siguiente error (al menos en Laravel 6.13, es posible que este comportamiento se mejore luego):

Property of non-object - Laravel Ignition

Este error que vimos en la serie Patrón Null Object en Laravel, sucede porque $product->category devuelve null.

Para poder obtener las categorías a las que un producto pertenece, necesitamos seleccionar el campo category_id:

<?php

Product::select(['title', 'slug', 'category_id']);

// O:

Product::query()->select(['title', 'slug', 'category_id']);

Seleccionar columnas específicas nos ayuda a reducir la cantidad de memoria que se requiere para traer cada registro – sobretodo en tablas con muchas columnas y donde requiramos obtener muchos registros; sin embargo, debemos tener cuidado porque si se nos olvida seleccionar una columna importante podemos obtener resultados inesperados. Por ejemplo, he olvidado seleccionar el campo image y todas las imágenes están rotas. Corrijamos esto:

<?php

Product::select(['title', 'slug', 'image', 'category_id']);

// O:

Product::query()
    ->select(['title', 'slug', 'image', 'category_id']);

Seleccionar columnas específicas de relaciones con Eloquent

También podemos especificar las columnas que se obtienen para cada registro relacionado, por ejemplo:

<?php
// routes/web.php

    $products = Product::query()
        ->select(['title', 'slug', 'image', 'category_id'])
        ->with([
            'category' => function ($q) {
                $q->select(['id', 'title', 'slug']);
            }
        ])
        ->get();

Alternativamente, podemos usar la siguiente sintaxis, como vimos en la lección nueva sintaxis para seleccionar columnas específicas con Eager Loading en Laravel 5.3:

<?php
// routes/web.php

    $products = Product::query()
        ->select(['title', 'slug', 'image', 'category_id'])
        ->with(['category:id,title,slug'])
        ->get();

Presta atención al hecho de que he seleccionado el id de la categoría. Laravel 6.13 no está haciendo esto automáticamente y es importante obtener este campo para que Eloquent cree la relación con el modelo de productos (products.category_id = product_categories.id).

Aprende más sobre bases de datos y SQL con nuestro libro digital disponible en Leanpub con 40% de descuento por prelanzamiento.

Ver más

Luego de esto podemos ver las consultas SQL resultantes desde Telescope:

select
  `title`,
  `slug`,
  `image`,
  `category_id`
from
  `products`
select
  `id`,
  `title`,
  `slug`
from
  `product_categories`
where
  `product_categories`.`id` in (
    1,
    2,
    3
  )

Con esta optimización el tiempo de ejecución de la consulta se reduce de 6.5ms a 2.9ms aproximadamente. Aunque estos tiempos pueden variar la mejora parece ser significativa.

Sin embargo, este cambio nos trae dos posibles puntos negativos:

1. Podemos olvidar seleccionar una columna importante, puede ser difícil detectar esto si la columna olvidada puede contener valores null.

2. Si en el listado de productos ahora queremos mostrar la descripción -u otro campo no seleccionado- tendremos que regresar a la ruta o al controlador de productos para agregar el campo adicional.

Regístrate hoy en Styde y obtén acceso a todo nuestro contenido.

Lección anterior Tip de optimización con Eloquent #2: Evita el problema de N+1 Lección siguiente Tip de optimización con Eloquent #4: Utiliza paginación