SQL od podstaw dla .NET developera — SELECT, WHERE, GROUP BY

Entity Framework Core generuje SQL za Ciebie — dopóki zapytanie nie jest wolne, a Ty nie potrafisz przeczytać, co ORM naprawdę wysłał do bazy. Albo dopóki rekruter nie mówi: „napisz zapytanie, które zwróci liczbę zamówień na klienta”. LINQ Cię tam nie uratuje — potrzebujesz SQL-a. Ten post to fundament: wyciąganie, filtrowanie, sortowanie i grupowanie danych, każdy element z odpowiednikiem w LINQ, żebyś widział, gdzie te dwa światy się spotykają.
SQL w jednym zdaniu
SQL to deklaratywny język zapytań do relacyjnej bazy danych — opisujesz *co* chcesz dostać, a silnik bazy sam decyduje *jak* to policzyć. To ta sama filozofia co LINQ: piszesz Where(p => p.Cena > 100), nie pętlę przeszukującą tabelę.
Wszystkie przykłady poniżej działają na dwóch tabelach sklepu:
-- Produkty(Id, Nazwa, Kategoria, Cena, Stan)
-- Zamowienia(Id, KlientId, ProduktId, Ilosc, Data)SELECT — wybór kolumn
SELECT mówi, które kolumny chcesz zobaczyć:
SELECT Nazwa, Cena FROM Produkty; -- dwie kolumny
SELECT * FROM Produkty; -- wszystkie (wygodne, ale nie w produkcji — o tym niżej)
SELECT Nazwa AS Produkt, Cena AS Brutto FROM Produkty; -- aliasy kolumnW LINQ to odpowiednik Select:
db.Produkty.Select(p => new { p.Nazwa, p.Cena });WHERE — filtrowanie wierszy
WHERE zawęża wynik do wierszy spełniających warunek:
SELECT Nazwa, Cena FROM Produkty WHERE Cena > 100;
SELECT * FROM Produkty WHERE Kategoria = 'Peryferia' AND Stan > 0;
SELECT * FROM Produkty WHERE Cena BETWEEN 50 AND 200;
SELECT * FROM Produkty WHERE Kategoria IN ('Peryferia', 'Kable');
SELECT * FROM Produkty WHERE Nazwa LIKE 'Klaw%'; -- zaczyna się od "Klaw"Operatory, które musisz znać: porównania (=, <>, <, >), logika (AND, OR, NOT), przynależność (IN), zakres (BETWEEN), dopasowanie wzorca (LIKE z % = dowolny ciąg, _ = jeden znak). To dokładnie warunki, które w LINQ trafiają do Where:
db.Produkty.Where(p => p.Cena > 100 && p.Stan > 0);ORDER BY — sortowanie
SELECT Nazwa, Cena FROM Produkty ORDER BY Cena DESC; -- malejąco
SELECT * FROM Produkty ORDER BY Kategoria ASC, Cena DESC; -- po dwóch kolumnachASC (rosnąco) jest domyślne, DESC odwraca. W LINQ: OrderByDescending(p => p.Cena).
Agregaty — liczby z wielu wierszy
Funkcje agregujące redukują zbiór wierszy do jednej wartości:
SELECT COUNT(*) FROM Produkty; -- ile produktów
SELECT AVG(Cena) FROM Produkty; -- średnia cena
SELECT MIN(Cena), MAX(Cena) FROM Produkty; -- najtańszy i najdroższy
SELECT SUM(Stan) FROM Produkty WHERE Kategoria = 'Kable';GROUP BY — agregaty per grupa
Samo AVG(Cena) daje jedną liczbę dla całej tabeli. GROUP BY liczy ją osobno dla każdej grupy:
SELECT Kategoria, COUNT(*) AS Ile, AVG(Cena) AS SredniaCena
FROM Produkty
GROUP BY Kategoria;Wynik: jeden wiersz na kategorię, z liczbą produktów i średnią ceną w tej kategorii. To pytanie „ile zamówień na klienta” z hooka:
SELECT KlientId, COUNT(*) AS LiczbaZamowien
FROM Zamowienia
GROUP BY KlientId;W LINQ odpowiada temu GroupBy:
db.Zamowienia
.GroupBy(z => z.KlientId)
.Select(g => new { KlientId = g.Key, LiczbaZamowien = g.Count() });HAVING — filtrowanie grup
WHERE filtruje wiersze przed grupowaniem. Żeby filtrować po agregacji (np. „tylko klienci z więcej niż 5 zamówieniami”), służy HAVING:
SELECT KlientId, COUNT(*) AS LiczbaZamowien
FROM Zamowienia
GROUP BY KlientId
HAVING COUNT(*) > 5;Mapa SQL ↔ LINQ
Dla .NET developera najszybsza droga do SQL-a to zobaczyć go jako LINQ pisany od tyłu:
| SQL | LINQ | Uwaga |
|---|---|---|
SELECT | Select | w SQL na początku, w LINQ na końcu |
WHERE | Where | filtr wierszy |
ORDER BY | OrderBy / OrderByDescending | sortowanie |
GROUP BY | GroupBy | grupowanie |
HAVING | Where po GroupBy | filtr po agregacji |
COUNT/SUM/AVG | Count/Sum/Average | agregaty |
Kolejność wykonania w SQL jest inna niż zapisu: baza najpierw bierze FROM, potem WHERE, GROUP BY, HAVING, a SELECT i ORDER BY na końcu. Dlatego aliasu z SELECT nie użyjesz w WHERE — w momencie filtrowania jeszcze nie istnieje.
SELECTpiszesz pierwszy, ale baza wykonuje go prawie ostatni. Ta różnica między kolejnością zapisu a kolejnością wykonania tłumaczy większość „dlaczego to nie działa” u początkujących.
Pułapki rekrutacyjne
Pułapka #1 — = NULL nigdy nie zadziała. WHERE Kategoria = NULL zwróci zero wierszy, zawsze — bo w SQL NULL oznacza „nieznane”, a nieznane nie równa się niczemu, nawet innemu NULL. Poprawnie: WHERE Kategoria IS NULL. To klasyczne pytanie na rozmowie.
Pułapka #2 — WHERE vs HAVING. WHERE Cena > 100 filtruje produkty. HAVING COUNT(*) > 5 filtruje grupy. Użycie agregatu w WHERE (WHERE COUNT(*) > 5) to błąd składni — agregat nie istnieje przed grupowaniem. Rekruterzy pytają o tę różnicę, żeby sprawdzić, czy rozumiesz kolejność wykonania.
Pułapka #3 — kolumna spoza GROUP BY. Jeśli grupujesz po Kategoria, w SELECT możesz mieć tylko Kategoria i agregaty. SELECT Nazwa, Kategoria, COUNT(*) ... GROUP BY Kategoria jest błędem (albo, w MySQL, zwróci przypadkowe Nazwa) — bo dla jednej kategorii jest wiele nazw, a silnik nie wie, którą pokazać.
Pułapka #4 — SELECT * w produkcji. Wygodne przy nauce, szkodliwe w kodzie: pobiera kolumny, których nie potrzebujesz (więcej danych przez sieć), psuje się przy zmianie schematu i uniemożliwia bazie użycie indeksu pokrywającego. W realnym zapytaniu wymieniaj kolumny jawnie — to samo, co robisz w LINQ przez Select.
Pułapka #5 — COUNT(*) vs COUNT(kolumna). COUNT(*) liczy wszystkie wiersze. COUNT(Kategoria) liczy wiersze, gdzie Kategoria nie jest NULL. Różnica bywa pytaniem-pułapką i realnym źródłem błędnych raportów.
Checklist — zanim uznasz zapytanie za gotowe
SELECTwymienia konkretne kolumny, nie*NULLsprawdzasz przezIS NULL, nie= NULL- filtr wierszy w
WHERE, filtr grup wHAVING - każda niezagregowana kolumna z
SELECTjest wGROUP BY ORDER BYtam, gdzie kolejność wyniku ma znaczenie- wiesz, jak to samo zapisać w LINQ
Podsumowanie
SQL to deklaratywny język, w którym SELECT wybiera kolumny, WHERE filtruje wiersze, ORDER BY sortuje, a GROUP BY z funkcjami agregującymi (COUNT, SUM, AVG) liczy wartości per grupa — z HAVING do filtrowania już po agregacji. Dla .NET developera każdy z tych elementów ma bliźniaka w LINQ, więc nie uczysz się nowego sposobu myślenia, tylko nowej składni dla tego samego. Najczęstsze potknięcia — = NULL, mylenie WHERE z HAVING, kolumny spoza GROUP BY — wynikają z jednej rzeczy: kolejność wykonania w SQL jest inna niż kolejność zapisu. Kto to rozumie, przestaje zgadywać.
Co dalej
Jedna tabela to dopiero początek — prawdziwa siła SQL-a to łączenie danych z wielu tabel. Pokazuje to JOIN w SQL — łączenie tabel. Gdy zapytania zaczną zwalniać, zajrzyj do indeksów i wydajności, a jak bezpiecznie wysyłać SQL z aplikacji — SQL injection i parametryzacja w .NET. Wszystko to fundament pod Entity Framework Core, który ten SQL generuje za Ciebie.
🚀 Co dalej?
Zobacz to w praktyce na wideo i pobierz darmową roadmapę, żeby ułożyć naukę w spójną ścieżkę do pierwszej pracy.
- 🗺️ Pobierz darmową roadmapę Junior .NET Developer — 12 kroków od podstaw C# do pierwszej pracy: dev-hobby.pl
- 🎬 Subskrybuj kanał YouTube — nowe filmy co tydzień.
Zamień wiedzę w umiejętności
Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.
Pobieram roadmapę →
1 comment