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.

Ćwicz to