Przeszukiwanie pogrupowanej bazy (Doctrine)

0

Witam,
tworzę właśnie projekt w Symfony 4 i mam w bazie danych utworzoną tabelę (screen w załączniku) i chciałbym w repozytorium utworzyć funkcję, która przyjmuje id produktu zwracając wiersze tabeli, które reprezentują najniższą wartość dla każdego sklepu. Napisałem taki kod:

  public function findSmallestPrices($productId)
    {
       return $this->createQueryBuilder('p')
            ->select('p, MIN(p.price) AS min_price')
            ->where('p.product = :val')
            ->setParameter('val', $productId)
            ->groupBy('p.store')
            ->orderBy('min_price', 'ASC')
            ->setMaxResults(10)
            ->getQuery()
            ->getResult()
        ;
}

Wszystko byłoby tak jak należy gdyby funkcja ta zwracała cały wiersz z najmniejszą ceną, a zamiast tego dostaję 4 (bo mam 4 różne sklepy) tablice 2 wymiarowe gdzie pod indeksem "0" każdej z nich mam pierwszy zapisany wiersz tej encji z danego sklepu, natomiast pod indeksem "1" wartość najniższej ceny dla danego sklepu. Wiem że wartość najniższą potem można ukryć za pomocą AS HIDDEN, lecz nie rozumiem dlaczego polecenie oderBy (czy to min_price czy p.price) działa sortując kolejność wypisywania samych grup a nie pól wewnątrz nich?? Próbowałem też osiągnąć swój cel za pomocą polecenia:

->having('p.price < 100')// domyślnie chciałbym by tu była formuła (p.price == min_price), lecz wyczytałem że w doctrine nie można korzystać z wartości wyciągniętych za pomocą selecta w poleceniu having i where

Zamiast selecta i orderBy lecz dla wartości mniejszych niż 100 zwraca mi pustą listę a dla większych niż 100 ten sam wynik co za pomocą pierwszego kodu. Póki co tymczasowo zrobiłem funkcję która pobiera id produktu i sklepu przeszukuje bazę danych zwracając wiersz dla danego produktu i sklepu i uruchamiam ją w pętli dla każdego sklepu osobno, lecz wiem, że to niezbyt "czyste" rozwiązanie, a mimo wszystko chciałbym, aby uczyć się pisać jak najbardziej optymalny kod. Czy ktoś jest w stanie wytłumaczyć mi błąd mojego rozumowania i nakierować na dobrą drogę??
Za pomoc z góry dziękuje.

0

A zastosowanie expr()->min() w select jak niżej?

$result = $query
    ->select('user', $qb->expr()->min('posts.length'))
    ->from('MyBundle:User', 'user')
    ->leftJoin('a.posts', 'posts')
    ->groupBy ('user.id')
    ->orderBy('user.name','ASC')
    ->getQuery()->getResult();
0
Limitless napisał(a):

A zastosowanie expr()->min() w select jak niżej?

$result = $query
    ->select('user', $qb->expr()->min('posts.length'))
    ->from('MyBundle:User', 'user')
    ->leftJoin('a.posts', 'posts')
    ->groupBy ('user.id')
    ->orderBy('user.name','ASC')
    ->getQuery()->getResult();

Wynik jest dokładnie ten sam co przy wywołaniu 1 fragmentu kodu w moim poście :/

1

Nie wiem czy Ci to pomoże, ale w SQL powinno zadziałać coś takiego:

SELECT MIN(p.price) AS min_price FROM p GROUP BY (product_id, store_id) ORDER BY product_id

nie potrzebujesz żadnego HAVING.

Twój problem bierze się z tego, że nigdy się nie nauczyłeś SQL, sorry za bezpośredniość, ale jesteś kolejną ofiarą ORMów.

0
TomRZ napisał(a):

Nie wiem czy Ci to pomoże, ale w SQL powinno zadziałać coś takiego:

SELECT MIN(p.price) AS min_price FROM p GROUP BY (product_id, store_id) ORDER BY product_id

nie potrzebujesz żadnego HAVING.

Twój problem bierze się z tego, że nigdy się nie nauczyłeś SQL, sorry za bezpośredniość, ale jesteś kolejną ofiarą ORMów.

Dopiero się uczę i dlatego czasem wolę zapytać kogoś na forum niż robić coś okrężną drogą, która tylko wydłuża czas ładowania i zwiększa objętość kodu. Jak już wcześniej w komentarzu opisałem wczoraj próbując coś z tym zrobić dowiedziałem się, że akurat Having tutaj mi jednak nie pomoże bo jedynie może zmniejszyć ilość rekordów które spełniają warunek. Także dziękuję za konstruktywną krytykę ;) Mam nadzieję, że w miarę szybko się nauczę i nie będę już popełniał takich błędów.

0

Zanim się weźmiesz za Doctrine, przećwcz mocno sam SQL - ta inwestycja później wielokrotnie Ci się zwróci.

Możesz robić np. tak: najpierw stwórz SQL który zwróci dane w taki sposób w jaki chcesz, a dopiero później przetłumacz to na buildera w ORM.

0
TomRZ napisał(a):

Zanim się weźmiesz za Doctrine, przećwcz mocno sam SQL - ta inwestycja później wielokrotnie Ci się zwróci.

Możesz robić np. tak: najpierw stwórz SQL który zwróci dane w taki sposób w jaki chcesz, a dopiero później przetłumacz to na buildera w ORM.

Co do zapytania w SQL to myślę, że potrzebuję czegoś takiego:

SELECT * FROM `product_list` WHERE (`product_id`,`price`) IN (SELECT 1, min(`price`) FROM `product_list` GROUP BY `store_id`)

To co ty podałeś jest spoko tylko o ile dobrze się orientuję to wyrzuci mi tylko wartości minimalne ceny dla każdego sklepu, a potem posortuje według id produktu, a to już osiągałem z pierwszą wersją kodu. Ja potrzebuję zaś całego wiersza (czyt. daty, ceny itp. ), dla każdego sklepu podając tylko id produktu. Ten kod jest ok?? Czy znowu okrężną drogą to robię?

0
TomRZ napisał(a):

Zanim się weźmiesz za Doctrine, przećwcz mocno sam SQL - ta inwestycja później wielokrotnie Ci się zwróci.

Możesz robić np. tak: najpierw stwórz SQL który zwróci dane w taki sposób w jaki chcesz, a dopiero później przetłumacz to na buildera w ORM.

Teraz to wiem, bo wczoraj natknąłem się na stronkę gdzie SQL jest bardzo rozpisany i ilość tych informacji mnie strasznie wczoraj przytłoczyła i myślę że jak zrealizuje ten projekt to ostro się wezmę za przerabianie tego kursu. Tak jak pisałem w komentarzu rano wczoraj natknąłem się w dokumentacji doctrine na opcję createNativeQuery() i tutaj moje pytanie czy ja dobrze rozumiem że za pomocą tej funkcji mogę pisać zapytania w czystym SQL?

0
kris016 napisał(a):

To co ty podałeś jest spoko tylko o ile dobrze się orientuję to wyrzuci mi tylko wartości minimalne ceny dla każdego sklepu, a potem posortuje według id produktu, a to już osiągałem z pierwszą wersją kodu. Ja potrzebuję zaś całego wiersza (czyt. daty, ceny itp. ), dla każdego sklepu podając tylko id produktu. Ten kod jest ok?? Czy znowu okrężną drogą to robię?

Dodajesz p.* i to wszystko:

SELECT p.*, MIN(p.price) AS min_price FROM p GROUP BY (product_id, store_id) ORDER BY product_id

I staraj się nie używać subselekcji, tylko jak już, to JOINów kiedy łączysz ze sobą dwie różne tabele - w tym przypadku gdybyś chciał dołączyć np. jakieś informacje o sklepie który jest indentyfikowany przez store_id. Staraj się NIGDY nie robić subselekcji rekursywnych - czyli do tej samej tabeli z której pobierasz aktualnie dane, to jest uzasadnione w bardzo niewielu przypadkach i trzeba to robić umiejętnie.

0
TomRZ napisał(a):
kris016 napisał(a):

To co ty podałeś jest spoko tylko o ile dobrze się orientuję to wyrzuci mi tylko wartości minimalne ceny dla każdego sklepu, a potem posortuje według id produktu, a to już osiągałem z pierwszą wersją kodu. Ja potrzebuję zaś całego wiersza (czyt. daty, ceny itp. ), dla każdego sklepu podając tylko id produktu. Ten kod jest ok?? Czy znowu okrężną drogą to robię?

Dodajesz p.* i to wszystko:

SELECT p.*, MIN(p.price) AS min_price FROM p GROUP BY (product_id, store_id) ORDER BY product_id

I staraj się nie używać subselekcji, tylko jak już, to JOINów kiedy łączysz ze sobą dwie różne tabele - w tym przypadku gdybyś chciał dołączyć np. jakieś informacje o sklepie który jest indentyfikowany przez store_id. Staraj się NIGDY nie robić subselekcji rekursywnych - czyli do tej samej tabeli z której pobierasz aktualnie dane, to jest uzasadnione w bardzo niewielu przypadkach i trzeba to robić umiejętnie.

Super dziękuję ci bardzo :)

0

No dobra to w odpowiedzi na to co Marcin napisał, tutaj masz zapytanie tak jak powinno prawidłowo wyglądać i powinno się wykonać prawidłowo np. w PostgreSQL, to zapytanie które podałem Ci wcześniej zadziała tylko na MySQL i jest nie za dobre, chociaż prostsze do zrozumienia.

SELECT p1.* FROM p as p1 INNER JOIN (  SELECT product_id, store_id, MIN(p.price) AS price FROM p GROUP BY (product_id, store_id)  ) as p2 ON p1.product_id = p2.product_id AND p1.store_id = p2.store_id ORDER BY p1.product_id

ale nie wiem na 100% czy czegoś tu nie pokiełbasiłem.

I w sumie mamy tu rekursywny JOIN przed którym wcześniej ostrzegałem, taka ironia losu :)

1 użytkowników online, w tym zalogowanych: 0, gości: 1