[ Zidar @ 07.11.2011. 21:09 ] @
Nasao sam veliki izbor gotovih mozgalica, tako da ne treba da ih izmisljam. Malo prevedem i to je sve . Za svaku mozgalicu dacemo opis problema, ulazne podatke, kako treba da izgleda rezultat, pravila igre i skriptu za kreiranje ulaznih podatka. Svake dve-tri nedelje postavicemo novu mozgalicu, ako bude interesovanja.

Molimo vas da kacite samo resenja koja daju isporavan rezultat. Ne ponavljajte ulazne podatke, samo dajte vas SQL iskaz.

Ovu mozgalicu nasao sam na jednom sajtu kao prvu u grupi pocetnickih mozgalica - znaci, za one koji su tek naucili SQL sintaksu. Sajt je o MS SQL, pa sam je stavio na MS SQL forum. Da li je bas pocetnicka, prosudite sami

Mozgalica: Data je tabela #Zaposleni u kojoj cuvamo podatke o radnim jedinicama, radnicim a i njihovim platama. Treba pokazati za svaku radnu jedinicu radnike koji imaju drugu najvecu platu u radnoj jedinici. Ako tacno jedan radnik ima drugu najvecu platu, prikazati tog radnika. Ako vise radnika ima drugu najvecu platu, pokazati ih sve. Konacni rezultat da bude sortiran po kolonama RadnaJedinica a ako ima vise radnika u radnoj jedinici da oni budu u okviru radne jedinice poredjani po abecedi ili azbuci, svejedno.

Ulazni podaci:
Code:

RadnaJedinica   Radnik          Plata
--------------- --------------- ---------------------------------------
Klasicna        D Zubovic       25000.00
Klasicna        J JOvanovic     18000.00
Klasicna        R Bakocevic     15000.00
Klasicna        O Njego         10000.00
Narodnjaci      M Ilic          40000.00
Narodnjaci      D Zivkovic      25000.00
Narodnjaci      T Zdravkovic    20000.00
Narodnjaci      P Gojkovic      15000.00
Rokeri          G Bregovic      80000.00
Rokeri          R M Tocak       50000.00
Rokeri          B Djordjevic    50000.00
Rokeri          Dz Stulic       40000.00

(12 row(s) affected)



Pravil igre: Rezultat mora biti kveri koji radi u MS QL verzijama 2005 i navise, koji pocinje sa SELECT ili WITH. Znaci, nema kursora i temp tabela, cisti SELECT izraz, koji moze da pocinje sa WIDTH.

Rezultat treba da izgleda ovako:
Code:
RadnikID    Radnik          RadnaJedinica   Plata
----------- --------------- --------------- ---------------------------------------
10          J JOvanovic     Klasicna        18000.00
2           D Zivkovic      Narodnjaci      25000.00
8           B Djordjevic    Rokeri          50000.00
7           R M Tocak       Rokeri          50000.00

(4 row(s) affected)



Skripta za kreirenje ulaznih podataka:
Code:

IF Object_ID('tempdb..#Zaposleni') IS NOT NULL DROP TABLE #Zaposleni
;
CREATE TABLE #Zaposleni (
    RadnikID INT IDENTITY,
    Radnik VARCHAR(15),
    RadnaJedinica VARCHAR(15),
    Plata NUMERIC(16,2)
)

INSERT INTO #Zaposleni(Radnik, RadnaJedinica, Plata)
VALUES('M Ilic','Narodnjaci', 40000)
INSERT INTO #Zaposleni(Radnik, RadnaJedinica, Plata)
VALUES('D Zivkovic','Narodnjaci', 25000)
INSERT INTO #Zaposleni(Radnik, RadnaJedinica, Plata)
VALUES('T Zdravkovic','Narodnjaci', 20000)
INSERT INTO #Zaposleni(Radnik, RadnaJedinica, Plata)
VALUES('P Gojkovic','Narodnjaci', 15000)
;

INSERT INTO #Zaposleni(Radnik, RadnaJedinica, Plata)
VALUES('G Bregovic','Rokeri', 80000)
INSERT INTO #Zaposleni(Radnik, RadnaJedinica, Plata)
VALUES('Dz Stulic','Rokeri', 40000)
INSERT INTO #Zaposleni(Radnik, RadnaJedinica, Plata)
VALUES('R M Tocak','Rokeri', 50000)
INSERT INTO #Zaposleni(Radnik, RadnaJedinica, Plata)
VALUES('B Djordjevic','Rokeri', 50000)
;

INSERT INTO #Zaposleni(Radnik, RadnaJedinica, Plata)
VALUES('D Zubovic','Klasicna', 25000)
INSERT INTO #Zaposleni(Radnik, RadnaJedinica, Plata)
VALUES('J JOvanovic','Klasicna', 18000)
INSERT INTO #Zaposleni(Radnik, RadnaJedinica, Plata)
VALUES('R Bakocevic','Klasicna', 15000)
INSERT INTO #Zaposleni(Radnik, RadnaJedinica, Plata)
VALUES('O Njego','Klasicna', 10000)
;

-- Ulazni podaci:
SELECT RadnaJedinica, Radnik, Plata 
FROM #Zaposleni 
ORDER BY RadnaJedinica, Plata DESC
;




Uzivajte!
[ mmix @ 08.11.2011. 10:45 ] @
Nema zainteresovanih?

Code (tsql):

select RadnaJedinica, Radnik, Plata
from #Zaposleni
inner join (select RadnaJedinica as RJ2, Max(Plata) as DrugaPlata
            from #Zaposleni
            inner join (select RadnaJedinica as RJ, MAX(Plata) as MaxPlata
                        from  #Zaposleni
                        group by RadnaJedinica) as TMaxPlata on #Zaposleni.RadnaJedinica = TMaxPlata.RJ
            where Plata < MaxPlata
            group by RadnaJedinica) as TSecPlata on #Zaposleni.RadnaJedinica = TSecPlata.RJ2
where Plata = DrugaPlata
order by RadnaJedinica, Radnik;
 


3 loopa i 2 full scan-a. Mozda bi jedan scan mogao da se skine...
[ mmix @ 08.11.2011. 11:00 ] @
Evo i jedan malcice jeftiniji (2 scana ali samo 1 loop). Pretpostavljam da je zbog ovoga ubaceno SQL2005+ ;) jer je u toj verziji dosao OVER

Code (tsql):

select RadnaJedinica, Radnik, Plata
from #Zaposleni
inner join (select RadnaJedinica as RJ, Plata as rangPlata, ROW_NUMBER() over (partition by RadnaJedinica order by Plata desc) as pindex
            from #Zaposleni) as TRPlate on #Zaposleni.RadnaJedinica = TRPlate.RJ and #Zaposleni.Plata = TRPlate.rangPlata
where pindex = 2
order by RadnaJedinica, Radnik;
 
[ vujkev @ 08.11.2011. 11:08 ] @
Pretekao si me za malo :)
Isto to samo napisano malo čitljivije (barem meni)
Code (tsql):
with e as (
select RadnaJedinica
     , Plata
     , row_number() over (partition by RadnaJedinica order by Plata desc) rb
     from #Zaposleni     
)
select z.RadnaJedinica, z.Radnik, z.Plata  from e
inner join #Zaposleni z on z.Plata = e.Plata and z.RadnaJedinica = e.RadnaJedinica
     where rb = 2
     order by z.radnajedinica, z.radnik
[ djoka_l @ 08.11.2011. 11:34 ] @
Uh, pretekli me.

Evo jedno rešenje koje je na Oracle bazi i koristi analitičku RANK() funkciju

Code (sql):

SELECT RadnikID, RadnaJedinica, Radnik, Plata
  FROM (SELECT RadnikID,
               RadnaJedinica,
               Radnik,
               Plata,
               Rank() OVER(partition BY RadnaJedinica ORDER BY plata DESC) "Rank"
          FROM tmp_Zaposleni)
 WHERE "Rank" = 2
 ORDER BY RadnaJedinica, Radnik
 


a evo i rešenja bez RANK() (ok, malo sam zakomplikovao ali radi)

Code (sql):


SELECT z.radnikid, z.radnajedinica, z.radnik, z.plata
FROM tmp_zaposleni z, (
SELECT radnajedinica, MAX(plata) plata
  FROM (SELECT plata, radnajedinica, SUM(broj_vecih) veci
          FROM (SELECT z1.plata,
                       z1.radnajedinica,
                       CASE
                         WHEN z1.plata < z2.plata THEN
                          1
                         ELSE
                          0
                       END broj_vecih
                  FROM tmp_Zaposleni z1, tmp_zaposleni z2
                 WHERE z1.radnajedinica = z2.radnajedinica)
         GROUP BY plata, radnajedinica)
 WHERE veci <> 0
 GROUP BY radnajedinica) x
 WHERE z.radnajedinica = x.radnajedinica
 AND z.plata=x.plata
 ORDER BY radnajedinica, radnik
 
[ mmix @ 08.11.2011. 12:26 ] @
Mada mislim da se nijedno od ovih resenja ne skalira lepo sa velicinom tabele, u rangiranju filteri se uvek primenjuju nakon rangiranja na celom setu (zbog particija) a 3-loop resenjeuvek ima dva puna scan-a kako god da obrnes. Mada, ako se indexi ubace u igru looping varijanta bi mogla da izadje kao pobednik jer sve tri grane mogu da se iseku kroz index scan, rangiranje svejedno mora da prodje pun ciklus.

[ Zidar @ 08.11.2011. 16:13 ] @
Zahvaljujem! Imamo St-SQL i Oracle resenja. Ovo je bilo moje resenje, za svalki slucaj. Slicno kao Vuklev i Mmix, ali je funkcija DENSE_RANK:
Code:

;
WITH RankByRadnaJedinica AS
     ( SELECT DISTINCT 
               RadnaJedinica ,
               Plata ,
               PlataOrder = dense_rank() OVER (PARTITION BY RadnaJedinica ORDER BY Plata DESC)
        FROM  #Zaposleni
     )
SELECT   E.RadnikID     ,
         E.Radnik       ,
         E.RadnaJedinica,
         E.Plata
FROM     #Zaposleni               AS E
         JOIN RankByRadnaJedinica AS R
         ON       R.RadnaJedinica = E.RadnaJedinica
         AND      R.Plata         = E.Plata
WHERE    R.PlataOrder             = 2
ORDER BY RadnaJedinica,
         E.Radnik ;


Ako neko ima vremena da primeni data resenja na recimo AdventureWorks, mozda saznamo koje u stvari bolje radi o drugih za velike tabele.
[ mmix @ 08.11.2011. 17:51 ] @
* head slap *

naravno, ranking

evo ga najoptimalnije resenje

Code (tsql):
 
with TRangirano as (select RadnaJedinica, Radnik, Plata, RANK() over (partition by RadnaJedinica order by Plata desc) as redPlate
             from #Zaposleni)
select RadnaJedinica, Radnik, Plata
from TRangirano
where redPlate = 2
order by RadnaJedinica, Radnik;



1 table scan, 0 loops

za razliku od dense_rank(), rank() ne inkrementira rank za identicne elemente


Sta vise rownumber i denserank bez distinct su nespravni jer ne rade u situaciji kad dva ili vise zaposlena imaju istu najvecu platu.

[Ovu poruku je menjao mmix dana 08.11.2011. u 19:02 GMT+1]
[ MarkoBalkan @ 08.11.2011. 20:01 ] @
Code:


mysql> select * from radnici;
+----------+--------+-------+
| radna    | radnik | plata |
+----------+--------+-------+
| klasicna | ivo    | 2500  |
| klasicna | marko  | 5500  |
| klasicna | marko  | 6500  |
| klasicna | josip  | 7500  |
| klasicna | josip1 | 7500  |
| klasicna | mirko  | 8500  |
| rokeri   | mirko  | 8500  |
| rokeri   | marko  | 7500  |
| rokeri   | ivo    | 6500  |
+----------+--------+-------+
9 rows in set (0.00 sec)

mysql> select radna,radnik,plata from radnici where ( select count(*) from radnici as t where radnici.radna=t.radna and t.plata>radnici.plata)=1;
+----------+--------+-------+
| radna    | radnik | plata |
+----------+--------+-------+
| klasicna | josip  | 7500  |
| klasicna | josip1 | 7500  |
| rokeri   | marko  | 7500  |
+----------+--------+-------+
3 rows in set (0.00 sec)

mysql> 



ako stavimo 0, dobivamo najvećeg iz grupe, ako stavimo 2, dobivamo druge najveće iz grupe itd....

ako stavimo manje od 2 dobivamo prva dva najveća iz svake grupe.

ako u selectu kod count okrenemo znak, dobivamo od najnižeg prema većem, s time da tada kreće od 0.

npr

select radna,radnik,plata from radnici where ( select count(*) from radnici as t where radnici.radna=t.radna and t.plata<radnici.plata)<=1.

dobivamo prva dva najmanja iz svake frupe.

ovaj select vrijedi za bilo koju bazu.
[ Zidar @ 08.11.2011. 20:07 ] @
Marko, mozes li da koristis originalne skripte za tabelu i kolone? Da se tabela zove #Zaposleni (ili bar Zaposleni, ako ne moze #) i da su kolone
(RadnikID, Radnik, RadnaJedinica, Plata) onako kako smo ih definisali na pocetku? Jedino tako mozemo da proverimo skriptu, ako vec SELECT vredi za svaku bazu.
[ MarkoBalkan @ 08.11.2011. 20:13 ] @
Code (tsql):

select RadnikID, Radnik, RadnaJedinica, Plata  
from #Zaposleni where ( select count(*) from #Zaposleni as t where #Zaposleni.RadnaJedinica = t.RadnaJedinica and t.Plata > #Zaposleni.Plata) = 1;
 


[Ovu poruku je menjao mmix dana 08.11.2011. u 22:12 GMT+1]
[ mmix @ 08.11.2011. 20:26 ] @
Isto zanimljivo resenje, 2 table scans, 1 loop. Na ovom malom setu je cak brzi od rank() resenja.


Mada, pati od istih ogranicenja kao i ostala non-rank() resenja. Probaj da stavis da dva coveka imaju istu najvecu platu i nece raditi.
[ MarkoBalkan @ 08.11.2011. 21:10 ] @
Citat:
mmix: Isto zanimljivo resenje, 2 table scans, 1 loop. Na ovom malom setu je cak brzi od rank() resenja.


Mada, pati od istih ogranicenja kao i ostala non-rank() resenja. Probaj da stavis da dva coveka imaju istu najvecu platu i nece raditi.


no problemo

Code (tsql):

select *
from #Zaposleni
where plata in (select distinct(Plata)
                from #Zaposleni
                where (select count(*)
                       from #Zaposleni as t
                       where #Zaposleni.RadnaJedinica = t.RadnaJedinica and t.Plata > #Zaposleni.Plata) = 1);
[ mmix @ 08.11.2011. 21:16 ] @
Ne znam kako tebi, ali meni ovaj posledni query ne vraca ispravan result (plus to se bukvalno svodi na najsporije resenje, 3 table scana i dva loopa). Cisto letimice mislim da ti ni koncept ovog query-a nije dobar, join na platu po iznosu mi ne izgleda ok, sta ako u dve grupe postoje isti iznosi ali sa drugim rangom?

Code:

RadnikID    Radnik          RadnaJedinica   Plata
----------- --------------- --------------- ---------------------------------------
2           D Zivkovic      Narodnjaci      25000.00
7           R M Tocak       Rokeri          50000.00
8           B Djordjevic    Rokeri          50000.00
9           D Zubovic       Klasicna        25000.00
10          J JOvanovic     Klasicna        18000.00
[ Zidar @ 08.11.2011. 21:54 ] @
Markova poslednja varijanta radi OK kad imamo dve osobe sa drugom najvecom platom. Medjutim, kad imamo samo jednu osobu, ond ne radi dobro - vraca prvu i drugu platau.
Code (tsql):

SELECT RadnaJedinica, Radnik, Plata
FROM #Zaposleni
ORDER BY RadnaJedinica, Plata DESC
;
select *
from #Zaposleni
where plata in (select distinct(Plata)
        from #Zaposleni
        where (select count(*)
               from #Zaposleni as t
               where #Zaposleni.RadnaJedinica = t.RadnaJedinica and t.Plata > #Zaposleni.Plata) = 1);
 

Code:

RadnaJedinica   Radnik          Plata
--------------- --------------- ---------------------------------------
Klasicna        D Zubovic       25000.00
Klasicna        J JOvanovic     18000.00
Klasicna        R Bakocevic     15000.00
Klasicna        O Njego         10000.00
Narodnjaci      M Ilic          40000.00
Narodnjaci      D Zivkovic      25000.00
Narodnjaci      T Zdravkovic    20000.00
Narodnjaci      P Gojkovic      15000.00
Rokeri          G Bregovic      80000.00
Rokeri          R M Tocak       50000.00
Rokeri          B Djordjevic    50000.00
Rokeri          Dz Stulic       40000.00

(12 row(s) affected)

RadnikID    Radnik          RadnaJedinica   Plata
----------- --------------- --------------- ---------------------------------------
2           D Zivkovic      Narodnjaci      25000.00
7           R M Tocak       Rokeri          50000.00
8           B Djordjevic    Rokeri          50000.00
9           D Zubovic       Klasicna        25000.00
10          J JOvanovic     Klasicna        18000.00

(5 row(s) affected)

Interesantno je da smo za narodnjake dobili tacan rezultat - D Zivkovic ima platu 25,000, drugi po redu medju narodnjacima, jedini drugi
Rokeri su OK - Tocak i B Djordjevic su drugi sa po 50,000, imaju obojica drugu najvecu platu.
Kod klasicne muzike je problem - dobijamo i najvecu i drugu najvecu platu. Treba da se vidi samo J Jovanovic sa platom 18,000 ali ne i D Zubovic sa platom 25,000.

Zasto kveri radi za narodnjake a nece za pevace klasicne muzike?



[Ovu poruku je menjao mmix dana 09.11.2011. u 10:21 GMT+1]
[ Zidar @ 08.11.2011. 22:00 ] @
IZvinjavam se, to je bio pretposlednji Markov kveri. Ovaj radi kako treba:
Code (tsql):

 --- Najnovije Markovo resenje:              
SELECT radnajedinica,
       radnik,
       plata
FROM   (SELECT *
        FROM   #zaposleni
        WHERE  plata IN (SELECT DISTINCT( plata )
                         FROM   #zaposleni
                         WHERE  (SELECT COUNT(*)
                                 FROM   #zaposleni AS t
                                 WHERE  #zaposleni.radnajedinica =
                                        t.radnajedinica
                                        AND t.plata > #zaposleni.plata) = 1)) p
WHERE  (SELECT COUNT(*)
        FROM   (SELECT *
                FROM   #zaposleni
                WHERE  plata IN (SELECT DISTINCT( plata )
                                 FROM   #zaposleni
                                 WHERE  (SELECT COUNT(*)
                                         FROM   #zaposleni AS t
                                         WHERE  #zaposleni.radnajedinica =
                                                t.radnajedinica
                                                AND t.plata > #zaposleni.plata)
                                        = 1))
               p1
        WHERE  p.radnajedinica = p1.radnajedinica
               AND p.plata > p1.plata) = 0
ORDER  BY radnajedinica,
          radnik;


Code:

RadnikID    Radnik          RadnaJedinica   Plata
----------- --------------- --------------- ---------------------------------------
2           D Zivkovic      Narodnjaci      25000.00
7           R M Tocak       Rokeri          50000.00
8           B Djordjevic    Rokeri          50000.00
10          J JOvanovic     Klasicna        18000.00

(4 row(s) affected)




[Ovu poruku je menjao mmix dana 09.11.2011. u 10:24 GMT+1]
[ MarkoBalkan @ 08.11.2011. 22:01 ] @
Code (tsql):

SELECT radnajedinica,
       radnik,
       plata
FROM   (SELECT *
        FROM   #zaposleni
        WHERE  plata IN (SELECT DISTINCT( plata )
                         FROM   #zaposleni
                         WHERE  (SELECT COUNT(*)
                                 FROM   #zaposleni AS t
                                 WHERE  #zaposleni.radnajedinica =
                                        t.radnajedinica
                                        AND t.plata > #zaposleni.plata) = 1)) p
WHERE  (SELECT COUNT(*)
        FROM   (SELECT *
                FROM   #zaposleni
                WHERE  plata IN (SELECT DISTINCT( plata )
                                 FROM   #zaposleni
                                 WHERE  (SELECT COUNT(*)
                                         FROM   #zaposleni AS t
                                         WHERE  #zaposleni.radnajedinica =
                                                t.radnajedinica
                                                AND t.plata > #zaposleni.plata)
                                        = 1))
               p1
        WHERE  p.radnajedinica = p1.radnajedinica
               AND p.plata > p1.plata) = 0
ORDER  BY radnajedinica,
          radnik;
 


provjerite ovosa više podataka.


[Ovu poruku je menjao mmix dana 09.11.2011. u 10:23 GMT+1]
[ mmix @ 09.11.2011. 09:16 ] @
Imas 6 punih table scana i 5 loopova, nema ni teorijske sanse da ovo skalira lepo sa velicinom tabele :)

I btw, nije ni malo zanimljivo ovako jer se ne postuju pravila.
Koristite imena tabele/a iz postavke kao i imena polja i postujte zahtev rezultata (polja, sort, itd) jer i oni uticu na performanse query-a, nije fer da drugi moraju da ispravljaju query-e da bi proradili na zajednickom setu podataka. Isto bi bilo lepo i da malo formatirate svoje query-je sa indentacijom zbog citljivosti i razumevanja i da koristitie tsql ili sql code tagove
Ako vam je smor da formatirate rucno, postoje onlien servisi za to, npr http://www.dpriver.com/pp/sqlformat.htm


A sem toga query i dalje ne radi. Promeni u setu da bude ('D Zivkovic','Narodnjaci', 40000) i rezultat nece biti dobar, narodnjaci nece ni uci u izbor a mora da izadje t Zdravkovic sa platom 20000. Ovako napamet, negde prelinkuje preko plate u drugoj radnoj jedinici, samo je query suvise veliki i suvise isprepleten.