Pytania z SQL na rozmowach, z odpowiedziami, które możesz uruchomić
Większość rozmów o SQL wraca do tego samego kilkunastu pomysłów. Poniżej są te najczęstsze, z krótką odpowiedzią i zapytaniem działającym w PostgreSQL. Przykłady używają małej bazy piekarni (customers, products, orders i order_items), więc każdy możesz wypróbować w lekcji na tej stronie.
Znajdź drugą najwyższą wartość
Prosta odpowiedź to sortowanie malejąco i pominięcie jednego wiersza. Zwraca jeden wiersz, nawet jeśli kilka wierszy ma tę samą drugą cenę.
Jeśli remisy mają znaczenie, numeruj wiersze funkcją DENSE_RANK i zachowaj pozycję 2. DENSE_RANK nie zostawia luk po remisach, więc pozycja 2 to zawsze druga różna wartość.
SELECT name, unit_price FROM ( SELECT name, unit_price, DENSE_RANK() OVER (ORDER BY unit_price DESC) AS r FROM products ) AS ranked WHERE r = 2;
Wynik: Chocolate cake slice, za 4.20.
Znajdź duplikaty
Pogrupuj według kolumny, która powinna być unikalna, i zachowaj grupy, w których jest więcej niż jeden wiersz.
SELECT order_date, COUNT(*) AS orders FROM orders GROUP BY order_date HAVING COUNT(*) > 1 ORDER BY order_date;
Wynik: Trzy daty, każda z dwoma zamówieniami: 1, 2 i 6 września.
Znajdź wiersze bez dopasowania
Użyj LEFT JOIN i zachowaj wiersze, w których prawa strona ma NULL, albo użyj NOT EXISTS. Oba sposoby odpowiadają na pytanie „którzy klienci nigdy nie zamówili?”.
Unikaj NOT IN z podzapytaniem, które może zwrócić NULL: pojedynczy NULL sprawia, że cały warunek jest nieznany i nie dostajesz żadnych wierszy.
SELECT c.first_name, c.last_name FROM customers AS c LEFT JOIN orders AS o ON o.customer_id = c.customer_id WHERE o.order_id IS NULL ORDER BY c.customer_id;
Wynik: Grace Kim i Hugo Meyer.
Najlepszy wiersz w grupie
Ponumeruj wiersze w każdej grupie za pomocą PARTITION BY, a potem w zapytaniu zewnętrznym zachowaj pozycję 1. Użyj RANK, aby zachować remisy, lub ROW_NUMBER, aby zostawić dokładnie jeden wiersz na grupę.
SELECT category, name, units
FROM (
SELECT p.category, p.name, SUM(oi.quantity) AS units,
RANK() OVER (PARTITION BY p.category ORDER BY SUM(oi.quantity) DESC) AS rk
FROM products AS p
JOIN order_items AS oi ON oi.product_id = p.product_id
GROUP BY p.category, p.name
) AS ranked
WHERE rk = 1
ORDER BY category, name;Wynik: Najlepiej sprzedający się produkt z każdej kategorii. Cake pokazuje dwa produkty, bo mają remis na poziomie 3 sztuk.
Suma narastająca
Użyj SUM jako funkcji okna z ORDER BY. Sortuj po kolumnie bez remisów, bo w przeciwnym razie wiersze o tej samej wartości zostaną zsumowane razem.
SELECT order_id,
SUM(quantity) AS units,
SUM(SUM(quantity)) OVER (ORDER BY order_id) AS running_units
FROM order_items
GROUP BY order_id
ORDER BY order_id;Wynik: Liczba sztuk na zamówienie i suma dotychczasowa, kończąca się na 56.
WHERE a HAVING
WHERE filtruje wiersze przed grupowaniem, więc nie może użyć funkcji agregującej. HAVING filtruje grupy po agregacji. W przykładzie WHERE najpierw usuwa tanie produkty, a HAVING zostawia kategorie z co najmniej dwoma z pozostałych produktów.
SELECT category, COUNT(*) AS products FROM products WHERE unit_price > 2.50 GROUP BY category HAVING COUNT(*) >= 2 ORDER BY category;
Wynik: Bread i cake, każda z dwoma produktami powyżej 2.50. Pastry i drinks mają tylko jeden produkt powyżej 2.50 i odpadają.
UNION a UNION ALL
UNION usuwa zduplikowane wiersze, co wymaga dodatkowej pracy. UNION ALL zachowuje wszystko i jest szybszy. Używaj UNION ALL, chyba że potrzebujesz usunąć duplikaty.
SELECT city FROM customers UNION SELECT city FROM customers ORDER BY city;
Wynik: Pięć miast, każde raz. Przy UNION ALL byłoby 16 wierszy.
Częste pytania
- Jaka jest różnica między DELETE, TRUNCATE i DROP?
- DELETE usuwa wybrane przez ciebie wiersze (WHERE) i można go cofnąć. TRUNCATE szybko opróżnia całą tabelę i zachowuje jej strukturę. DROP usuwa samą tabelę wraz ze strukturą i danymi.
- Jaka jest różnica między INNER JOIN a LEFT JOIN?
- INNER JOIN zostawia tylko wiersze, które pasują po obu stronach. LEFT JOIN zostawia każdy wiersz z lewej tabeli i uzupełnia prawą stronę wartością NULL tam, gdzie nic nie pasuje.
- Jaka jest różnica między RANK, DENSE_RANK i ROW_NUMBER?
- Wszystkie trzy numerują wiersze w kolejności. RANK nadaje remisującym wierszom ten sam numer i pomija kolejne numery. DENSE_RANK nadaje ten sam numer remisom i niczego nie pomija. ROW_NUMBER nigdy nie powtarza numeru.
- Którego dialektu SQL używają te odpowiedzi?
- PostgreSQL. LIMIT/OFFSET, funkcje okna, CTE i złączenia działają prawie wszędzie. W SQL Server użyj TOP lub OFFSET FETCH zamiast LIMIT, a w Oracle użyj FETCH FIRST.