Witaj Gościu! ( Zaloguj | Rejestruj )

Forum PHP.pl

> (Nie)Eleganckie zapytanie tablicowe., Prośba o pomoc w sformułowaniu zapytania…
Morfina
post
Post #1





Grupa: Zarejestrowani
Postów: 14
Pomógł: 0
Dołączył: 4.09.2013

Ostrzeżenie: (0%)
-----


Witam,
Mam problem w odpytaniu Bazy danych w sposób „elegancki”, już tłumaczę o co chodzi.
Dane:
(IMG:http://imageshack.com/a/img827/9602/ncvm.jpg)

Zadanie:
Wyszukaj pracowników zarabiających najmniej na danym etacie.

Rozwiązanie, którego bym oczekiwał i uważam za eleganckie powinno mieć postać:

SELECT nazwisko FROM pracownicy
WHERE ( Etat , Placa_pod ) IN (SELECT Etat, MIN(Placa_pod) FROM pracownicy GROUP BY etat);

Niestety nie działa ono na MS SQL.

Rozwiązanie, które działa ale jest wg. mnie gorsze (nieeleganckie) ma postać:

SELECT dbo.Pracownicy.Nazwisko, dbo.Pracownicy.Imie
FROM dbo.Pracownicy INNER JOIN
(SELECT MIN(Placa_Pod) AS Min, Etat
FROM dbo.Pracownicy AS Pracownicy_1
GROUP BY Etat) AS P_1 ON dbo.Pracownicy.Placa_Pod = P_1.Min AND dbo.Pracownicy.Etat = P_1.Etat

Czy któryś z szanownych forumowiczów ma pomysł na rozwiązanie bez użycia JOIN ?

Z góry dziękuję Morf.



Witam ponownie,
po nocnych przemyśleniach:

SELECT Nazwisko, Imie
FROM dbo.Pracownicy AS T1
WHERE (Placa_Pod =
(SELECT MIN(Placa_Pod) AS Low
FROM dbo.Pracownicy
WHERE (Etat = T1.Etat)))

Poziom elegancji 7 / 10 i dodatkowy punkcik za bark JOIN czyli 8 /10 :-)

w zasadzie jest OK chyba, że...

Pozdrawiam Morf




Go to the top of the page
+Quote Post
 
Start new topic
Odpowiedzi
pmir13
post
Post #2





Grupa: Zarejestrowani
Postów: 282
Pomógł: 89
Dołączył: 12.04.2011

Ostrzeżenie: (0%)
-----


Z odpowiednimi indeksami rozwiązanie klasyczne jest wystarczająco szybkie dla stosunkowo niedużej bazy danych, a zapewne taka jest baza pracowników, bo nawet biorąc pod uwagę wszystkich zatrudnionych na naszych uczelniach pracowników mielibyśmy ilość rekordów rzędu 100k, a to jest liczba, którą może obsłużyć testowy serwer postawiony na leciwym laptopie. Oczywiście zakładając prawidłowe zaindeksowanie, bo bez indeksów nawet przy tej niewielkiej ilości danych serwer może się zmęczyć.

Dopiero gdy dane idą w miliony trzeba myśleć o rozwiązaniach dedykowanych dla silnika i wykorzystujących specyfikę rozkładu danych, mówiąc wcześniej o takim rozwiązaniu dla sql servera z wykorzystaniem CTE i CROSS APPLY chodziło mi o coś w tym stylu:

  1. WITH etaty AS
  2. (
  3. SELECT MIN(etat) AS etat
  4. FROM pracownicy
  5. UNION ALL
  6. SELECT eo.etat
  7. FROM
  8. (
  9. SELECT p.etat, ROW_NUMBER() OVER (ORDER BY p.etat) AS nr_etatu
  10. FROM etaty e
  11. JOIN pracownicy p
  12. ON p.etat > e.etat
  13. WHERE e.etat IS NOT NULL
  14. ) eo
  15. WHERE eo.nr_etatu = 1
  16. )
  17. SELECT pd.*
  18. FROM etaty e
  19. CROSS APPLY
  20. (
  21. SELECT TOP 1 pr.*
  22. FROM pracownicy pr
  23. WHERE pr.etat = e.etat
  24. ORDER BY placa_pod, id
  25. ) pd
  26. OPTION (MAXRECURSION 0)


Czyli wykorzystanie rekurencji CTE do szybkiego znalezienia z indeksu wszystkich różnych etatów a następnie CROSS APPLY by w zależnym podzapytaniu móc umieścić własne TOP i ORDER BY, dzięki czemu dostajemy dla każdego etatu wykorzystując ten sam indeks najniższą płacę, co przy dość zaawansowanym optymalizatorze sql servera pozwala uniknąć pełnego skanu indeksu (a skan całej tabeli to w ogóle byłby horror), a zamiast tego szukamy tylko tyle ile mamy różnych etatów ( czyli w miarę ograniczona, można nawet założyć stała ilość - nawet O(log(N)) jest przecież dużo gorsze niż O(1)).

Polecam do testowania na nieco większej bazie, dla ułatwienia:
  1. CREATE TABLE pracownicy (
  2. id INT NOT NULL PRIMARY KEY,
  3. nazwisko VARCHAR(64),
  4. imie VARCHAR(64),
  5. etat VARCHAR(16),
  6. placa_pod DECIMAL(20,2)
  7. )
  8. GO
  9.  
  10. CREATE INDEX etat_placa_id ON pracownicy( etat, placa_pod, id );
  11. GO
  12.  
  13. BEGIN TRANSACTION
  14. DECLARE @i INT
  15. SET @i = 1+FLOOR(RAND(1))
  16. WHILE @i < 10000000
  17. BEGIN
  18. INSERT INTO pracownicy( id, nazwisko, imie, etat, placa_pod )
  19. VALUES(
  20. @i,
  21. 'Nazwisko_'+CAST(@i AS VARCHAR(9)),
  22. 'Imie_'+CAST(@i AS VARCHAR(9)),
  23. 'Etat_'+CAST(FLOOR(RAND()*100)+1 AS VARCHAR(3)),
  24. (FLOOR(RAND()*300000+100000))/100
  25. )
  26. SET @i = @i+1;
  27. END
  28. COMMIT

Mamy tutaj 10M losowych rekordów dla 100 różnych etatów i prawidłowy indeks, z trzecim członem dla ewentualnego rozstrzygania remisów w płacach wg najniższego id. Samo tworzenie tabeli zajmuje kilka-kilkanaście minut na starym kompie, ale zapytanie wciąż pozostaje w obszarze milisekund. Dla porównania klasyczne rozwiązanie powinno trwać kilka sekund.
Go to the top of the page
+Quote Post

Posty w temacie


Reply to this topicStart new topic
2 Użytkowników czyta ten temat (2 Gości i 0 Anonimowych użytkowników)
0 Zarejestrowanych:

 



RSS Aktualny czas: 7.10.2025 - 19:44