[SQL] Najwyższe wynagrodzenie w danym dziale

Witam.

Mam jedną tabelę, w której jest imię pracownika, jego wynagrodzenie i dział, w którym pracuje.

Chcę napisać zapytanie, które pokaże mi który pracownik zarabia najwięcej w danym dziale i jaka to kwota w formie:

Obecnie wymodziłem coś takiego ale najwyraźniej coś robię źle, i byłbym bardzo wdzięczny za jakąkolwiek pomoc.

SELECT dzial, wynagrodzenie, imie

FROM (SELECT dzial, MAX(wynagrodzenie)

      FROM pracownik

      GROUP BY dzial)

Kombinujesz dobrze :slight_smile:

Można to zrobić, np. tak:

SELECT t1.Dzial, t1.Imie, t1.Wynagrodzenie

FROM SomeTable AS t1

WHERE t1.Wynagrodzenie = (

	SELECT MAX(t2.Wynagrodzenie)

	FROM SomeTable AS t2

	WHERE t2.Dzial = t1.Dzial

)
SELECT t1.Dzial, t1.Imie, t1.Wynagrodzenie

FROM SomeTable AS t1, (

   SELECT t.Dzial AS Dzial, MAX(t.Wynagrodzenie) AS MaxWynagrodzenie

   FROM SomeTable AS t

   GROUP BY t.Dzial

) AS t2

WHERE t1.Dzial = t2.Dzial AND t1.Wynagrodzenie = t2.MaxWynagrodzenie

Wybierz to co będzie szybsze. Chyba wersja nr 2.

Dziękuję za odpowiedź.

Może, źle rozumuję, ale z tego co widzę używasz dwóch tabel podczas gdy ja mam wszystko w jednej i nie bardzo rozumiem co się dzieje w tym kodzie.

Używam jednej. Nazwałem ją sobie SomeTable. Musisz podmienić tą nazwę na nazwę swojej tabeli. Nie wiem przecież jak ona się u Ciebie nazywa, bo podałeś tylko nazwy kolumn.

Czym w takim razie jest t2? Z tego co wywnioskowałem SELECT tworzy nową tabelę dla której utworzyłeś alias t2. Wybacz takie pytanie ale jestem dość nowy w SQL i sporo rzeczy jeszcze nie rozumiem.

Sposób nr 1:

Potrzebujesz wyciągnąć informacje o pracownikach, którzy zarabiają najwięcej w obrębie poszczególnych działów. Problem w tym, że takiej danej, tzn. jakie są maksymalne zarobki w obrębie danego działu, nie masz. Gdybyś jednak miał, to zapytanie mogłoby wyglądać następująco:

SELECT t1.Dzial, t1.Imie, t1.Wynagrodzenie

FROM SomeTable AS t1

WHERE t1.Wynagrodzenie = t1.MaxWynagrodzenie

(t1 to jak słusznie zauważyłeś alias) Trzeba, więc coś podstawić w miejsce t1.MaxWynagrodzenie. Gdybyś chciał zdobyć informację o maksymalnym wynagrodzeniu w obrębie danego działu, to użyłbyś zapewne takiego zapytania:

SELECT MAX(t2.Wynagrodzenie)

FROM SomeTable AS t2

WHERE t2.Dzial = 1 -- Np. Dział nr 1

Pytanie jak powiązać to z powyższym zapytaniem? Żeby to zrobić, trzeba użyć czegoś, co nazywa się podzapytanie skorelowane (nie wiem kto to tak nazwał :slight_smile: ). Nazwa wzięła się stąd, że podzapytanie to korzysta z parametrów przekazanych z (nazwijmy to) nadrzędnego zapytania. U nas tym parametrem przekazanym z nadrzędnego zapytania będzie numer działu (t1.Dzial). W efekcie całość wygląda następująco:

SELECT t1.Dzial, t1.Imie, t1.Wynagrodzenie

FROM SomeTable AS t1

WHERE t1.Wynagrodzenie = (

   SELECT MAX(t2.Wynagrodzenie)

   FROM SomeTable AS t2

   WHERE t2.Dzial = t1.Dzial

)

Reasumując działa to tak, że dla każdego rekordu wyciąganego z tabeli SomeTable wykonywane jest podzapytanie, które sprawdza, jaka jest maksymalna wysokość wynagrodzenia w dziale, do którego odnosi się dany rekord. Sposób nr 2: Jeśli zrozumiałeś, to o czym pisałem wyżej (jeśli nie zrozumiałeś, to jest to moja wina), to zapewne zauważyłeś, że tamto zapytanie ma jedną wadę. Wielokrotnie jest sprawdzane, jaka jest maksymalna wysokość wynagrodzenia w danym dziale. Dokładniej mówiąc … tyle ile masz rekordów w tabeli SomeTable, tyle razy zostanie wykonane ww. podzapytanie skorelowane. Lepszym sposobem jest wyciągnięcie tych danych (maksymalnych wynagrodzeń) na samym początku, żeby nie powielać wielokrotnie tej samej czynności. Żeby osiągnąć taki efekt należy utworzyć sobie tabelę tymczasową. Kod jaki użyłem do utworzenia tej tabeli jest następujący:

SELECT t.Dzial AS Dzial, MAX(t.Wynagrodzenie) AS MaxWynagrodzenie

FROM SomeTable AS t

GROUP BY t.Dzial

Jak działa Group By znajdziesz w Internecie (ja tu się nie będę produkował :slight_smile: ). Cel tego zapytania jest taki, że zwróci ono dane w postaci: Dzial | MaxWynagrodzenie Mając to można zapisać takie zapytanie:

SELECT *

FROM SomeTable AS t1, (

   SELECT t.Dzial AS Dzial, MAX(t.Wynagrodzenie) AS MaxWynagrodzenie

   FROM SomeTable AS t

   GROUP BY t.Dzial

) AS t2

(aliasy nie muszą się nazywać t1 i t2, możesz wpisać co chcesz). Najlepiej wykonaj je i zobacz jakie dostaniesz wyniki. Dlaczego otrzymałeś takie, a nie inne? Jeśli w klauzuli FROM podasz kilka nazw tabel oddzielonych przecinkami, to między rekordami tych tabel, wykonany zostanie iloczyn kartezjański. Mówiąc po ludzku, wynikowa tabela będzie zawierała wszystkie możliwe kombinacje rekordów. Oczywiście nas takie rozwiązanie zupełnie nie zadowala. Dany rekord z tabeli oznaczonej aliasem t1 potrzebujemy powiązać tylko i wyłącznie z jednym konkretnym rekordem z tabeli tymczasowej oznaczonej aliasem t2 (a nie z każdym z nich). Żeby te rekordy powiązać używamy nazwy działu, która pojawia się zarówno w tabeli oznaczonej aliasem t1, jak i tabeli tymczasowej oznaczonej aliasem t2. Wykonaj następujące zapytanie i przeanalizuj wyniki:

SELECT *

FROM SomeTable AS t1, (

   SELECT t.Dzial AS Dzial, MAX(t.Wynagrodzenie) AS MaxWynagrodzenie

   FROM SomeTable AS t

   GROUP BY t.Dzial

) AS t2

WHERE t1.Dzial = t2.Dzial

Wszystko już jest prawie gotowe. Pozostało dodać jeszcze tylko jeden warunek do klauzuli WHERE i ostatecznie otrzymujemy:

SELECT t1.Dzial, t1.Imie, t1.Wynagrodzenie

FROM SomeTable AS t1, (

   SELECT t.Dzial AS Dzial, MAX(t.Wynagrodzenie) AS MaxWynagrodzenie

   FROM SomeTable AS t

   GROUP BY t.Dzial

) AS t2

WHERE t1.Dzial = t2.Dzial AND t1.Wynagrodzenie = t2.MaxWynagrodzenie

Jesteś wielki. Dziękuję. Myślę, że wszystko zrozumiałem. Mam nadzieję, że nie nadużywam uprzejmości, i zadam jeszcze dwa pytanka. Czy alias może być użyty zanim zostanie stworzony? I czy, tak jak wczesniej napisałem, SELECT tworzy tymczasową tabelę?

Jeszcze raz dziękuję. Nie spodziewałem się takiego wykładu :slight_smile:

  1. Alias musi być jawnie zadeklarowany (a więc odpowiedź na Twoje pytanie brzmi nie). Przy czym aliasy możesz tworzyć dla nazw tabel, jak i dla nazw kolumn.

  2. Zależy, o którym SELECT mówimy. W tym pierwszym sposobie nie jest tworzona tabela tymczasowa. Ten SELECT ma jedynie za zadanie wyciągnąć pojedynczą wartość, która istnieje tylko na potrzeby danego rekordu. W tym drugim sposobie jest tworzona tabela tymczasowa.

Poza tym nie ma za co.

Jeżeli alias nie może być użyty przed zadeklarowaniem, dlaczego to działa?

SELECT t1.Dzial, t1.Imie, t1.Wynagrodzenie

FROM SomeTable AS t1

A to się jednak nie zrozumieliśmy :slight_smile: To jest prawidłowo zadeklarowany alias. Zakładasz, że parsowanie zapytania SQL odbywa się od góry do dołu, a wcale tak przecież nie musi być (jak to dokładnie wygląda to Ci nie powiem, bo sam nie wiem).

Więc teoretycznie alias może być zadeklarowany 20 linijek dalej i nadal będzie działać. Dzięki :wink: