Symfony: Jak Zwrócić Tylko Wyliczoną Wartość Wykorzystując QueryBuilder

0
89
Rate this post

W dzisiejszym artykule omówimy, jak zwrócić tylko wyliczoną wartość (np. średnią, sumę, maksimum itd.) z bazy danych przy użyciu QueryBuildera w Symfony. QueryBuilder to potężne narzędzie, które pozwala na programowe generowanie zapytań SQL w sposób bardziej elastyczny i czytelny. Często jednak potrzebujemy tylko jednej konkretnej, wyliczonej wartości, a nie całego zestawu danych. Jak to zrobić? Oto kilka podejść.

Wprowadzenie do QueryBuildera

Zanim przejdziemy do rzeczy, krótkie wprowadzenie. QueryBuilder w Symfony jest częścią pakietu Doctrine ORM i służy do konstrukcji zapytań SQL w programowalny sposób. Na przykład:

php
$qb = $this->entityManager->createQueryBuilder();
$qb->select('u')
->from('App\Entity\User', 'u')
->where('u.id = :id')
->setParameter('id', 1);

W powyższym kodzie stworzyliśmy zapytanie, które wybiera wszystkich użytkowników z tabeli User, gdzie id wynosi 1.

Jak Zwrócić Tylko Jedną Wyliczoną Wartość

Do rzeczy. Jeżeli chcemy zwrócić tylko jedną, wyliczoną wartość, możemy to zrobić na kilka sposobów.

Wykorzystanie select()

Najprostszym sposobem jest użycie metody select() wraz z odpowiednią funkcją SQL. Na przykład, jeśli chcemy obliczyć średnią wartość kolumny price w tabeli Product, zrobimy to tak:

php
$qb = $this->entityManager->createQueryBuilder();
$qb->select('AVG(p.price)')
->from('App\Entity\Product', 'p');

$result = $qb->getQuery()->getSingleScalarResult();

W tym przypadku, getSingleScalarResult() zwróci wyliczoną średnią jako pojedynczą wartość skalarną.

Użycie setMaxResults()

Innym sposobem jest użycie metody setMaxResults() z argumentem 1 w połączeniu z odpowiednią funkcją SQL. Na przykład:

php
$qb = $this->entityManager->createQueryBuilder();
$qb->select('MAX(p.price)')
->from('App\Entity\Product', 'p')
->setMaxResults(1);

$result = $qb->getQuery()->getSingleScalarResult();

Zastosowanie Subquery

W bardziej skomplikowanych przypadkach można korzystać z subquery. Na przykład, jeśli chcemy zwrócić sumę cen dla produktów z konkretnego działu, możemy zrobić to tak:

php
$qb = $this->entityManager->createQueryBuilder();
$sub = $this->entityManager->createQueryBuilder();

$qb->select('SUM(p.price)')
->from('App\Entity\Product', 'p')
->where(
$qb->expr()->in(
'p.category',
$sub->select('c.id')
->from('App\Entity\Category', 'c')
->where('c.name = :name')
->getDQL()
)
)
->setParameter('name', 'Elektronika');

$result = $qb->getQuery()->getSingleScalarResult();

QueryBuilder a Optymalizacja Zapytań

Warto również zauważyć, że wykorzystanie QueryBuildera pozwala na optymalizację zapytań. Dzięki temu, że możemy dynamicznie dodawać lub usuwać części zapytania, jesteśmy w stanie dostosować je do konkretnego przypadku. Na przykład, jeśli znamy już kategorię, nie musimy korzystać z subquery i możemy zrobić to tak:

php
$qb = $this->entityManager->createQueryBuilder();
$qb->select('SUM(p.price)')
->from('App\Entity\Product', 'p')
->where('p.category = :category')
->setParameter('category', $knownCategoryId);

$result = $qb->getQuery()->getSingleScalarResult();

Dzięki temu zapytanie będzie wykonane szybciej, co ma znaczenie, gdy pracujemy z dużymi ilościami danych.

Wykorzystanie QueryBuildera w Repozytoriach

W praktycznych aplikacjach często korzystamy z repozytoriów, aby enkapsulować logikę związaną z bazą danych. Dzięki temu, można łatwo używać QueryBuildera w kontekście konkretnej encji. Przykładowo, moglibyśmy mieć metodę w repozytorium produktów, która zwraca sumę cen:

php
namespace App\Repository;

use Doctrine\ORM\EntityRepository;

class ProductRepository extends EntityRepository
{
public function getSumOfPrices()
{
$qb = $this->createQueryBuilder('p');
$qb->select('SUM(p.price)');
return $qb->getQuery()->getSingleScalarResult();
}
}

W tym przypadku, metoda getSumOfPrices() jest dostępna dla każdego, kto ma dostęp do repozytorium produktów, co znacząco upraszcza kod w różnych częściach aplikacji.

Użycie QueryBuildera z Paginacją

Jeżeli chcielibyśmy zwrócić tylko jedną wyliczoną wartość, ale w kontekście paginacji (na przykład średnia cena produktów na jednej stronie), możemy to zrobić korzystając z setFirstResult() i setMaxResults():

php
$qb = $this->entityManager->createQueryBuilder();
$qb->select('AVG(p.price)')
->from('App\Entity\Product', 'p')
->setFirstResult(0)
->setMaxResults(10);

$result = $qb->getQuery()->getSingleScalarResult();

QueryBuilder i API

Jeżeli nasza aplikacja korzysta z REST API lub GraphQL, warto zwrócić uwagę na to, jak QueryBuilder wpływa na wydajność i optymalizację zapytań. Często możemy wykorzystać tę funkcję do zwracania wyliczonych wartości, które są specyficzne dla pewnych endpointów API. Na przykład, moglibyśmy zwrócić średnią cenę produktów w danej kategorii jako część większego zapytania API.

php
// Przykładowy endpoint API
public function getAveragePriceInCategory($categoryId)
{
$qb = $this->entityManager->createQueryBuilder();
$qb->select('AVG(p.price)')
->from('App\Entity\Product', 'p')
->where('p.category = :category')
->setParameter('category', $categoryId);

return $qb->getQuery()->getSingleScalarResult();
}

Użycie QueryBuildera w Testach

Ostatnią kwestią, którą chcę poruszyć, jest wykorzystanie QueryBuildera w testach jednostkowych i integracyjnych. Dzięki możliwości dynamicznego generowania zapytań, łatwo możemy stworzyć zestaw testów, które sprawdzają różne przypadki użycia naszych zapytań. Możemy na przykład sprawdzić, czy dla pustego zestawu danych zwracana jest wartość zero, czy też czy wyniki są zgodne z oczekiwaniami dla różnych zestawów testowych danych.

php
// Przykładowy test jednostkowy
public function testGetAveragePrice()
{
$productRepository = $this->entityManager->getRepository('App\Entity\Product');
$averagePrice = $productRepository->getAveragePrice();

$this->assertEquals(50, $averagePrice);
}

Zastosowanie QueryBuildera w testach pozwala na dużą elastyczność i precyzję w sprawdzaniu, czy nasza aplikacja działa zgodnie z oczekiwaniami.

Debugowanie QueryBuildera

Gdy pracujemy z QueryBuilderem, często może pojawić się potrzeba debugowania generowanych zapytań SQL, zwłaszcza w skomplikowanych scenariuszach. Możemy to zrobić na kilka sposobów:

Wykorzystanie getQuery()

Po skonstruowaniu zapytania, można je wypisać do debugowania korzystając z metody getQuery i getSQL. Przykładowo:

php
$qb = $this->entityManager->createQueryBuilder();
$qb->select('AVG(p.price)')
->from('App\Entity\Product', 'p');

$query = $qb->getQuery();
$sql = $query->getSQL();

// Wypisanie zapytania SQL
dump($sql);

Korzystanie z Profilera Symfony

Jeżeli korzystasz z Symfony, możesz również użyć wbudowanego profilera do zobaczenia wszystkich wykonanych zapytań SQL. Jest to szczególnie użyteczne w kontekście całej aplikacji, gdzie wiele różnych zapytań może być wykonanych w jednym cyklu życia żądania.

Wydajność i Optymalizacja

Kiedy mówimy o wydajności, warto wspomnieć o indeksach w bazie danych i o tym, jak QueryBuilder korzysta z nich. Jeżeli na przykład zwracasz sumę lub średnią wartości kolumny, która nie jest zaindeksowana, może to znacząco wpłynąć na czas wykonania zapytania. Dlatego warto zawsze mieć na uwadze strukturę bazy danych i odpowiednio ją optymalizować.

Użycie QueryBuildera w Kompleksowych Zapytaniach

QueryBuilder nie jest ograniczony do prostych zapytań i pozwala na konstrukcję bardzo skomplikowanych zapytań z wieloma joinami, subquery i warunkami. Na przykład, jeśli chcemy znaleźć średnią cenę produktów w kategoriach, które są aktualnie promowane, możemy zrobić to tak:

php
$qb = $this->entityManager->createQueryBuilder();
$sub = $this->entityManager->createQueryBuilder();

$qb->select('AVG(p.price)')
->from('App\Entity\Product', 'p')
->where(
$qb->expr()->in(
'p.category',
$sub->select('c.id')
->from('App\Entity\Category', 'c')
->where('c.promotion = :promotion')
->getDQL()
)
)
->setParameter('promotion', true);

$result = $qb->getQuery()->getSingleScalarResult();

Ten przykład pokazuje, jak można łączyć różne fragmenty zapytań, aby otrzymać dokładnie to, czego potrzebujemy.

Rozszerzalność i Reużywalność

Ostatnim aspektem, który warto uwzględnić, jest fakt, że QueryBuilder jest bardzo rozszerzalny. Możesz tworzyć własne metody, które dodają specyficzne fragmenty do zapytań, i następnie używać ich w różnych częściach aplikacji. To sprawia, że kod jest bardziej reużywalny i łatwiejszy w utrzymaniu.