SELECCIONA la SUMA (o CUENTA)
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login Subscribe¡Nuevo equipo de meta! Mira la entidad FortuneCookie
. Una de sus propiedades es $numberPrinted
, que es el número de veces que hemos impreso esa fortuna. En la página de categoría, aquí arriba, quiero imprimir el número total impreso para todas las fortunas de esta categoría.
Podríamos resolver esto haciendo un bucle sobre $category->getFortuneCookies()
... llamando a->getNumberPrinted()
y añadiéndolo a alguna variable $count
. Eso funcionaría siempre que tuviéramos siempre un número pequeño de galletas de la suerte. Pero el negocio de las galletas está en auge... y pronto tendremos cientos de galletas en cada categoría. Sería una enorme ralentización si consultáramos 500 galletas de la suerte sólo para calcular la suma. De hecho, ¡probablemente nos quedaríamos sin memoria antes!
Seguro que hay una forma mejor, ¿verdad? ¡Seguro que sí! Haz todo ese trabajo en la base de datos con una consulta de suma.
Anular los campos seleccionados
Pensemos: los datos que estamos consultando provendrán en última instancia de la entidad FortuneCookie
... así que abre FortuneCookieRepository
para que podamos añadir un nuevo método allí. ¿Qué te parece: public function
countNumberPrintedForCategory(Category $category): int.
// ... lines 1 - 17 | |
class FortuneCookieRepository extends ServiceEntityRepository | |
{ | |
// ... lines 20 - 24 | |
public function countNumberPrintedForCategory(Category $category): int | |
{ | |
} | |
// ... lines 29 - 71 | |
} |
La consulta empieza más o menos como todas. Digamos$result = $this->createQueryBuilder('fortuneCookie')
. Por cierto, el alias puede ser cualquier cosa. Personalmente, intento que sean lo suficientemente largos como para ser únicos en mi proyecto... pero lo suficientemente cortos como para no resultar molestos. Y lo que es más importante, en cuanto elijas un alias para una entidad, quédate con él.
// ... lines 1 - 24 | |
public function countNumberPrintedForCategory(Category $category): int | |
{ | |
$result = $this->createQueryBuilder('fortuneCookie') | |
// ... lines 28 - 34 | |
} | |
// ... lines 36 - 80 |
Vale, sabemos que cuando creamos un QueryBuilder, seleccionará todos los datos de FortuneCookie
. ¡Pero en este caso, no queremos eso! Así que, a continuación, decimos->select()
para anular eso.
Antes, en CategoryRepository
, utilizamos ->addSelect()
, que básicamente dice:
Coge lo que estemos seleccionando y selecciona también esto otro.
Pero esta vez, estoy utilizando a propósito ->select()
para que anule eso y sólo seleccione lo que pongamos a continuación. Dentro, escribe DQL: SUM()
una función con la que probablemente estés familiarizado seguida de fortuneCookie.
y el nombre de la propiedad que queremos utilizar - numberPrinted
. Y no hace falta que lo hagas, pero voy a añadir AS fortunesPrinted
, que dará nombre a ese resultado cuando sea devuelto. Lo veremos en un minuto.
// ... lines 1 - 24 | |
public function countNumberPrintedForCategory(Category $category): int | |
{ | |
$result = $this->createQueryBuilder('fortuneCookie') | |
->select('SUM(fortuneCookie.numberPrinted) AS fortunesPrinted') | |
// ... lines 29 - 34 | |
} | |
// ... lines 36 - 80 |
andWhere() con una entidad entera
Vale, eso se ocupa del ->select()
. Ahora necesitamos un ->andWhere()
confortuneCookie.category = :category
... llamando a ->setParameter()
para rellenar el category
dinámico con el objeto $category
.
// ... lines 1 - 24 | |
public function countNumberPrintedForCategory(Category $category): int | |
{ | |
$result = $this->createQueryBuilder('fortuneCookie') | |
// ... line 28 | |
->andWhere('fortuneCookie.category = :category') | |
->setParameter('category', $category) | |
// ... lines 31 - 34 | |
} | |
// ... lines 36 - 80 |
¡Esto también es interesante! En SQL, normalmente diríamos algo comoWHERE fortuneCookie.categoryId =
y luego el ID entero. Pero en Doctrine, no pensamos en las tablas ni en las columnas: nos centramos en las entidades. Y no hay ninguna propiedadcategoryId
en FortuneCookie
. En su lugar, cuando decimosfortuneCookie.category
estamos haciendo referencia a la propiedad $category
deFortuneCookie
. Y en lugar de pasar sólo el ID entero, pasamos el objetoCategory
completo. En realidad es posible pasar el ID, pero la mayoría de las veces pasarás el objeto entero de esta manera.
Bien, ¡terminemos esto! Convierte esto en una consulta con ->getQuery()
. A continuación, si lo piensas bien, en realidad sólo queremos una fila de resultados. Así que digamos->getOneOrNullResult()
. Por último, return $result
.
// ... lines 1 - 24 | |
public function countNumberPrintedForCategory(Category $category): int | |
{ | |
$result = $this->createQueryBuilder('fortuneCookie') | |
->select('SUM(fortuneCookie.numberPrinted) AS fortunesPrinted') | |
->andWhere('fortuneCookie.category = :category') | |
->setParameter('category', $category) | |
->getQuery() | |
->getOneOrNullResult(); | |
return $result; | |
} | |
// ... lines 36 - 80 |
Hasta ahora, todas nuestras consultas han devuelto objetos. Puesto que estamos seleccionando sólo una cosa... ¿cambia eso finalmente? ¡Averigüémoslo! Añade dd($result)
y luego dirígete a FortuneController
para utilizarlo. Para el controlador de la página mostrar, añade un argumento FortuneCookieRepository $fortuneCookieRepository
. A continuación, di$fortunesPrinted
igual a $fortuneCookieRepository->countNumberPrintedForCategory()
pasando por $category
.
// ... lines 1 - 24 | |
public function countNumberPrintedForCategory(Category $category): int | |
{ | |
// ... lines 27 - 32 | |
dd($result); | |
// ... lines 34 - 35 | |
} | |
// ... lines 37 - 81 |
¡Estupendo! Toma esa variable $fortunesPrinted
y pásala a Twig comofortunesPrinted
.
// ... lines 1 - 6 | |
use App\Repository\FortuneCookieRepository; | |
// ... lines 8 - 12 | |
class FortuneController extends AbstractController | |
// ... lines 14 - 30 | |
public function showCategory(int $id, CategoryRepository $categoryRepository, FortuneCookieRepository $fortuneCookieRepository): Response | |
{ | |
// ... lines 33 - 36 | |
$fortunesPrinted = $fortuneCookieRepository->countNumberPrintedForCategory($category); | |
// ... line 38 | |
return $this->render('fortune/showCategory.html.twig',[ | |
// ... line 40 | |
'fortunesPrinted' => $fortunesPrinted, | |
]); | |
} | |
} |
Por último, busca la plantilla - showCategory.html.twig
- y... hay un encabezado de tabla que dice "Historial de impresión". Añade unos paréntesis con {{ fortunesPrinted }}
. Añade |number_format
para que quede más bonito que la palabra total
.
// ... lines 1 - 8 | |
<table class="table-auto border mb-6"> | |
<thead class="bg-slate-500 text-white"> | |
// ... lines 11 - 14 | |
<th class="border p-4"> | |
Print History ({{ fortunesPrinted|number_format }} total) | |
</th> | |
// ... line 18 | |
</thead> | |
// ... lines 20 - 31 | |
</table> | |
// ... lines 33 - 40 |
¡Fantástico! Ya que tenemos ese dd()
, vamos a actualizar y... ¡mira eso! ¡Volvemos a tener un array con 1 clave llamada fortunesPrinted
! Sí, en cuanto empezamos a seleccionar datos específicos, nos devuelve esos datos específicos. Es exactamente como esperarías con una consulta SQL normal.
Si hubiéramos dicho ->select('fortuneCookie')
(lo cual es redundante porque eso es lo que ya hacecreateQueryBuilder()
), eso nos habría dado un objeto FortuneCookie
. Pero en cuanto seleccionamos una cosa concreta, se deshace del objeto y devuelve una matriz asociativa.
Utilizar getSingleScalarResult()
Como nuestro método debe devolver un int
, podríamos completarlo diciendoreturn $result['fortunesPrinted']
. Pero si te encuentras en una situación en la que estás seleccionando una fila de datos... y sólo una columna de datos, hay un atajo para obtener esa única columna: ->getSingleScalarResult()
. Podemos devolverla directamente.
// ... lines 1 - 17 | |
class FortuneCookieRepository extends ServiceEntityRepository | |
{ | |
// ... lines 20 - 24 | |
public function countNumberPrintedForCategory(Category $category): int | |
{ | |
$result = $this->createQueryBuilder('fortuneCookie') | |
// ... lines 28 - 31 | |
->getSingleScalarResult(); | |
// ... lines 33 - 35 | |
} | |
// ... lines 37 - 79 | |
} |
Mantendré el dd()
para que podamos verlo. Y... ¡impresionante! Obtenemos sólo el número! Bueno, técnicamente es una cadena. Si quieres ser estricto, puedes añadir (int)
. Y ahora... ¡ya está! ¡Tenemos un número total bien formateado!
// ... lines 1 - 24 | |
public function countNumberPrintedForCategory(Category $category): int | |
{ | |
// ... lines 27 - 34 | |
return (int) $result; | |
} | |
// ... lines 37 - 81 |
A continuación: Seleccionemos aún más datos y veamos cómo se complican las cosas.
Hi guys,
Does anybody knows how to write an
addSelect()
withCOUNT(DISCINCT...)
clause with a condition?Something like:
->addSelect('COUNT(DISTINCT customer.id) IF (... some condition ...) as customers')
Thanks!