🔥 Zapisy zamknięte, ale możesz pobrać Roadmapę .NET i dołączyć do listy oczekujących — Pobierz i dołącz do Listy VIP →

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

SQL od podstaw — zapytanie SELECT, WHERE i GROUP BY z mapowaniem na LINQ w .NET

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 kolumn

W 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 kolumnach

ASC (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:

SQLLINQUwaga
SELECTSelectw SQL na początku, w LINQ na końcu
WHEREWherefiltr wierszy
ORDER BYOrderBy / OrderByDescendingsortowanie
GROUP BYGroupBygrupowanie
HAVINGWhere po GroupByfiltr po agregacji
COUNT/SUM/AVGCount/Sum/Averageagregaty

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.

SELECT piszesz 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

  • SELECT wymienia konkretne kolumny, nie *
  • NULL sprawdzasz przez IS NULL, nie = NULL
  • filtr wierszy w WHERE, filtr grup w HAVING
  • każda niezagregowana kolumna z SELECT jest w GROUP BY
  • ORDER BY tam, 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.

👨‍💻
Mariusz Jurczenko
Senior .NET Developer · 10+ lat doświadczenia komercyjnego

Programista .NET z doświadczeniem komercyjnym w firmach takich jak NFZ, Kamsoft, Diagnostyka, Hermes Reply Polska czy Etisoft Smart Solutions. Twórca kursów, z których skorzystało już ponad 11 000 osób w Strefie Kursów i ponad 1 000 kursantów na dev-hobby.pl.

Specjalizacja: Clean Code, Clean Architecture i uczenie programowania tak, żeby dało się je naprawdę zrozumieć — nie wykuć.

🚀 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.

1 comment

Dodaj komentarz

czytanie to początek

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ę →