Malinaa
30.07.2021, 10:01:10
W bazie danych SQL, mam dwie tabele, jedna z artykułami "articles" druga z kategoriami artykułów "categories".
1. Jak napisać zapytanie, aby wybrać tylko po jednym artykule z każdej kategorii (artykuły np. najnowsze wpisy w kategorii)?
Tytuł artykułu i nazwa kategorii (wymagane dane) są w osobnych tabelach (tabele tworzą relacje).
2. Może ktoś wie jak to samo zrobić w Doctrine (Symfony), byłoby super.
gornik
2.09.2021, 00:37:16
Może coś takiego?
SELECT * FROM articles
WHERE id IN (
SELECT MAX(id)
FROM articles
GROUP BY category_id
);
I dla symfony 5:
<?php
namespace App\Repository;
use ...
class ArticleRepository extends ServiceEntityRepository
{
// ...
public function findNewestForCategory(){
$em = $this->getEntityManager();
$query = $em->createQuery("SELECT a FROM App\Entity\Article a WHERE a.id IN (SELECT MAX(a1.id) FROM App\Entity\Article a1 GROUP BY a1.category)");
return $query->getResult();
}
}
LowiczakPL
8.09.2021, 13:23:48
Jeśli masz poprawnie zrobione relacje to ja bym to zrobił tak:
foreach ($categories as $category) {
$article = $category->getArticles()->first();
}
nospor
8.09.2021, 14:00:17
Mega "optymalne" Lowiczak
LowiczakPL
8.09.2021, 14:21:05
... to dla dopełnienia aby nie było że skąd ta pętla ma wiedzieć że pierwszy rekord jest najnowszym artykułem, może np wyglądać to tak dla Twojej encji
Category ale możesz oczywiście sortować jak chcesz i po dowolnym polu
/**
* @ORM\OneToMany(targetEntity=Article::class, mappedBy="category")
* @ORM\OrderBy({"createdAt" = "DESC"})
*/
private $articles;
nospor
8.09.2021, 14:22:02
Nadal nieoptymalne rozwiazanie

W zasadzie jedno z najgorszych jakie mozna bylo podac
LowiczakPL
8.09.2021, 14:36:31
Przecież idzie to w 1 zapytaniu do bazy to dlaczego jest nieoptymalne, tak konkretnie o jaką rzecz chodzi?
nospor
8.09.2021, 14:38:44
foreach ($categories as $category) {
$article = $category->getArticles()->first();
}
Od kiedy zapytanie w petli idzie jako jedno do bazy?
1) Tyle ile masz kategorii, tyle generujesz zapytan do bazy tu
2) getArticles() zwraca raczej wszystkie artykuly i dopiero jak je zwroci to ty pobierasz pierwszy z nich. Aczkolwiek tu sobie glowy nie dam uciac bo nie pamietam jak dziala doctrine w tym momencie choc na logike raczej tak jak mowie
LowiczakPL
8.09.2021, 14:43:19
... a no widzisz w Symfony jest inaczej niż zakładasz,
ta pętla i first() to tylko i wyłącznie 1 zapytanie do bazy danych
dla mnie to najprostsze, można to na repo zrobić ale po co, to zależy od potrzeby i od ilości danych
nospor
8.09.2021, 14:45:37
O ile jeszcze w first jestem w stanie uwierzyc, to co do petli mnie nie przekonasz

Nie ma bata
LowiczakPL
8.09.2021, 14:49:45
w Symfony to też może być jako 1 zapytanie do bazy, gdzie masz zagnieżdżone relacje z encji Category do encji Article a ta ma relacje do encji Autor
foreach ($categories as $category) {
$autorName = $category->getArticles()->first()->getAuthor()->getName;
}
nospor
8.09.2021, 14:51:47
Jak juz mowilem co do zagnieszdzen w doctrine klocic sie nie bede bo dawno nie pracowalem.
Ale petla to petla. Masz tam tyle zapytan ile kategori lecisz w petli i tego nie zmienisz
LowiczakPL
8.09.2021, 15:00:35
W przypadku Doctrine i Symfony zapomnij o standardowym myśleniu że odwołanie w pętli do encji to zapytanie do bazy, w Symfony tak nie ma, jeśli wiesz jak to się robi idzie to jako 1 zapytanie bez strat na pamięci i czasie wywołania skryptu i zapytania do bazy.
Jeśli znasz autorytet Symfony spytaj go a potwierdzi Ci bez wahania że to możliwe.
nospor
8.09.2021, 15:02:08
nawet w symfony petla to petla. tego nie zmienisz. bez dzialajacego przykladu nadal uwazam to za nieoptymalne
LowiczakPL
8.09.2021, 15:14:03
Moje rozwiązanie stosuje się wtedy kiedy właśnie potrzeba Ci optymalnego rozwiązania gdyż pada wydajność,
ono właśnie podnosi wydajność źle napisanego kodu zmniejszając ilość zapytań do bazy danych nawet z kilkuset do 1
co nie oznacza że to lekarstwo na wszelkie zło
tu jest jakaś namiastka o tym
https://www.doctrine-project.org/projects/d...erformance.html
nospor
8.09.2021, 15:17:24
W linku co podales nic nie widze na ten temat.
To inaczej, co wg ciebie bedzie zawieralo $article po wykonaniu tej petli?
foreach ($categories as $category) {
$article = $category->getArticles()->first();
}
LowiczakPL
8.09.2021, 15:22:45
$article to pierwszy element kolekcji czyli Obiekt encji Article zawierający najnowszy artykuł w konkretnej kategorii,
to może ten art
https://www.uvdesk.com/en/blog/doctrine-pro...s-lazy-loading/
nospor
8.09.2021, 15:28:25
"$article to pierwszy element kolekcji czyli Obiekt encji Article zawierający najnowszy artykuł w konkretnej kategorii,"
ja to wiem. autor chcial pierwszy element wszystkich kategorii. Po petli, autor dostanie tylko pierwszy artykul ostatniej kategorii
I tak wiem co to lazy loading. I sam mi wlasnie podales bron ze mowisz zle
"
These entities are then partially loaded and wrapped into a proxy object. At this point, only the id of the associated entity is known. Then when we further access a method or property of this proxied object, Doctrine will make a request to the database to load that property if it’s not already loaded.
"
czyli dla twojego drugiego przykladu nie bedzie jedno zapytanie na iteracje tylko kilka, w zaleznosci do ilu encji sie odwolasz. Wlansie o to mi chodzi. Ubzduralo ci sie ze symfony i doctrine robia magie ale to ni edo konca tak.
LowiczakPL
8.09.2021, 15:30:40
dokładnie tak jak piszesz, to autor zadecyduje co ma się stać,
ja tylko dałem przykład pobrania rekordu w pętli czyli zawsze najnowszego artykułu jak zna Symfony to raczej zauważy to że ta zmienna jest nadpisywana chyba nie jest .... robiony
nospor
8.09.2021, 15:32:39
A ja ci tylko wyjasniam, ze to nie jest jedno zapytanie do bazy bo nie jest.
Odpal to sobie, odpal debug mode i sobie sprawdz
LowiczakPL
8.09.2021, 15:34:52
.. jest tam zdanie jęśli nie został jeszcze załadowany
ja go zawsze ładuję jako proxy i mam do niego dostęp bez odwoływania się do bazy, czyli bez względu na to że mam relację
Kategoria -> Artykuł -> Autor artykuły - nazwisko autora
to ja mam to jako 1 zapytanie, nawet jak pobieram to w pętli jak sugerowałem na początku tego posta
nospor
8.09.2021, 15:37:27
Ale jak to jedno zapytanie odpalasz w petli to masz juz x zapytan
Jak masz 10kategori to masz 10 zapytan.
A jesli wszystko ladujesz od razu jak tu chyba doczytalem, to juz w ogole autor pyta o 10 rekordow a ty z bazy pobierasz 1000. Temu to jest nadal nieoptymalne
LowiczakPL
8.09.2021, 16:12:08
Reasumując, dzięki tej naszej dyskusji zoptymalizowałem przez przypadek 16 zapytań do 4

czas mi spadł z 1.45 ms do 0.50 ms
a mój kod w pętli zwraca 47 obiektów ogłoszeń, ponieważ tyle mam kategorii, no i ani jednego zapytania o ogłoszenie wszystkie obiekty są pobierane z proxy dlatego, mam jeszcze 3 inne zapytania, nie związane z pętlą Encji Category
foreach($allCategories as $category){
dump($category->getClassifieds()->first());
}
Query Metrics pokazuje:
4 Database Queries
0.50 ms Query time
nospor
8.09.2021, 16:28:29
No dobrze, jesli pobrales wszystko przed petla to nic dziwnego ze w petli ci nie leci juz do bazy. To tez ci pisalem.
Autor ma 1000 kategorii powiedzmy i dla kazdej chce pobrac po jednym artykule. Twoje rozwiazanie pobiera wszystkie kategorie i wszystkie artykuly za jednym zamachem mimo ze autor nadal potrzebuje tylko malego ulamka z tego. Tak, to jest nieoptymalne

Kolejna sprawa to twoje dane testowe. No sorki, ale 47 kategorii to nie jest baza testowa. Zrob z tego 1000 kategorii, kazda z 1000 artykulow i wtedy bedziesz widzial czy to dziala czy nie

A najlepiej po 10000
LowiczakPL
8.09.2021, 16:43:31
w pętli to leciało i nadal jest to 1 zapytanie do bazy
jednak dla tej ilości kategorii czyli ponad 3k z ogłoszeniami -> zapytanie skoczyło do 27 ms,
obstawiam że jeśli chodzi o artykuły to max kilkanaście kategorii starczy
nospor
8.09.2021, 16:47:11
Sorki, moze to przez to zycie, ale nie wierze ze mowimy o tym samym albo ze napewno wszystko poprawnie sprawdzasz.
Tak czy siak, sama petla bez magii wczesniej nie zadziala, a tej magii wczesniej nadal nie pokazales

ps: spadam offline. jak cos sprawdze jutro jesli cos nowego sie pojawi
gornik
8.09.2021, 20:20:00
Sprawdziłem z ciekawości bo akurat kodze sklepik który ma kategorie i produkty.
W kontrolerze wywołałem:
$em = $this->getDoctrine()->getManager();
$categories = $em->getRepository(ProductCategory::Class)
->findBy(['isActive' => true]);
foreach($categories as $category){
$item = $category->getProducts()->first();
if($item)
echo "#{$item->getId()} <br />"; }
Categories i products są powiązane relacją many to many
Wynik:
44 - Database Queries
86.60 ms - Query time
Następny test:
$em = $this->getDoctrine()->getManager();
$products = $em->getRepository( Product::Class)
->findAll();
foreach($products as $product){
$image = $product->getImages()->first();
dump($image);
}
Tutaj już posiadam relacje produkt -> obrazki OneToMany->ManyToOne
Wynik:
37 - Database Queries
42.77 ms - Query time
Nospor ma rację.
LowiczakPL
9.09.2021, 07:05:03
Twój test mówi tylko tyle że próbowałeś sprawdzić pewien stereotyp, który się potwierdził i nic więcej.
Dla mnie to tak jak pytanie się dwóch uczniów jak Wam poszło na klasówce
pierwszy odpowie bardzo słabo bo pomyliłem się w 3 zadaniach
drugi odpowie mi super, napisałem wszystko
Jesteś tym drugim uczniem, który wszystko napisałem poprawnie.
gornik, przecież nospor napisałem już to wcześniej że jeśli paczkę danych załadujesz przed pętlą to w pętli nie odwołujesz się do bazy.
W tym przypadku jest identycznie dane są w proxy i każdy obiekt w pętli jest zaciągany z proxy a nie z bazy, obojętnie jak głęboka to jest relacja
jest wykonane zapytanie z joinami, dane siedzą w proxy i z niego są zaciągane,
ma to sens w przypadku małej ilości danych, w tym poście było mowa o kategoriach artykułów, ja u siebie mam 4 kategorie artów, sprawdza się to super nie kombinuje z repozytoriami tylko odwołuje się w ten sposób
nospor
9.09.2021, 09:58:51
Cytat
ma to sens w przypadku małej ilości danych, w tym poście było mowa o kategoriach artykułów, ja u siebie mam 4 kategorie artów, sprawdza się to super nie kombinuje z repozytoriami tylko odwołuje się w ten sposób
No wlasnie. A ja zawsze zakladam ze mamy duza ilosc danych bo predzej czy pozniej jesli aplikacja wypali to te dane rosna i potem zonk. Dlatego nadal uwazam ze to jest nieoptymalne. Zgadza sie, przy malej ilosci danych nie zauwazysz problemu ale zawsze tak jest ze przy malej ilosci danych nigdy nie widac problemu

Raz robilem audyt dla firmy bo im jedna stronka sie odpalala 2minuty. Sie okazalo ze koles robil drzewko kategorii. Wczytywal wszystkie dane na raz, obrabial je, zaciagal dodatkowe dane itd... ale w danej chwili bylo potrzebne tylko mala czesc danych. Wszystko bylo fajnie jak bylo tylko pare kategorii... po paru latach aplikacja sie rozrosla, doszlo w pyte kategorii i krach. Dlatego ja jestem przeczulony by pisac aplikacje porzadnie od razu a nie "dziala" wiec dobrze.
@LowiczakPL tylko trzymanie danych całych obiektów w tym proxy dla 1000 rekordów nadal jest nieoptymalne(nawet z lazy loadingiem), ORMa używa się do Write Modelu, do Read modelu DBAL
LowiczakPL
10.09.2021, 05:38:57
com, a czy to nie jest przypadkiem tak z ORM i DBAL
DBAL (DataBase Abstraction Layer) to oprogramowanie,
które upraszcza interakcję z bazami danych SQL,
umożliwiając korzystanie z nich bez martwienia się o specyficzne dialekty lub różnice między różnymi dostawcami DBMS.
Zasadniczo pozwala na uruchamianie zapytań SQL względem DBMS bez pisania SQL specyficznego dla dostawcy.
ORM (Object Relational Mapper) to narzędzie,
które daje wrażenie pracy ze strukturą danych w pamięci,
reprezentowaną jako graf obiektów z powiązanymi obiektami.
Upraszcza logikę aplikacji związaną z operacjami SQL poprzez usunięcie całego kodu SQL
i przekształcenie go w logikę OOP. Doctrine 2 ORM po prostu obsługuje ładowanie i utrzymywanie POPO (zwykłych starych obiektów PHP).
nospor
10.09.2021, 09:13:42
Lowiczak my to wiemy. Ale wszystko z umiarem. A ty uzywasz Docrtine do pobrania calej bazy i trzymania jej w pamieci... Nadal nie rozumiesz jak zle to jest?
Pyton_000
10.09.2021, 09:20:31
Beton, beton widzę....
@LowiczakPL Operujesz na Doctrine wiec Ci dałem rozwiązanie w tym ekosystemie, ORM śledzi zmiany w Write Modelu dlatego tam się go używa, do odczytu danych nie potrzebujesz tego, w C# możesz sobie to wyłączyć o ile pamiętam, w Doctrine tego nie ma, w DBAL robisz RAW Query do bazy i wtedy ono jest wydajne, a jak zostawiasz to ORM, to on zrobi sobie pod spodem tyle zapytań ile uzna za stosowne i niekoniecznie tak optymalne jakby się chciało, bo to jest narzędzie uniwersalne, a nie najbardziej wydajne. Wiec tak jak
@nospor napisał

ORM Doctrine i wiele innych jest zbudowany na Doctrine DBAL uściślając, ale chodzi własnie o to żeby do Read Modelu używać RAW Query, gdzie samemu sobie wyciągasz to co potrzeba a nie dane całych obiektów do Proxy, by użyć tylko jednej kolumny z nich
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę
kliknij tutaj.