Ankete i testovi

Thursday, 25.09.2008 – Zidar

 Automatska obrada podataka pocela je kada je gospodin po imenu Herman Hollerith za potrebe americke vlade obradio prikupljene podatke o popisu stanovnistva. Tada jos nije bilo kompjutera, sve se desava davne 1890. godine.  Gospodin Hollerith je zatim osnovao firmu pod imenom Inetrnational Business Machines, dobro nam poznati IBM.

Za ljubitelje istorije, evo interesantan link:
 http://wiki.answers.com/Q/When_did_herman_hollerit_invented_the_tabulating_machine

I naravno wikipedia clanak  http://en.wikipedia.org/wiki/Herman_Hollerith

Tema ovog posta nije Herman Hollerith, niti IBM. Tema je obrada anketa i upitnika na racunarima,  odnosno primena relacionih baza podataka . Kako se generalno resavaju ovakvi problemi?  Postoje naravno bar dva moguca pristupa – relacioni i nerelacioni. Pokazacemo da  nerelacioni pristup izgleda u pocetku mnogo laksi, ali se znacajnio komplikuje cim se odmaknemo od unosa podataka. Relacion nacin moze da bude zbunjujuci na pocetku i nesto je tezi za unos podataka, ali se kasnije stvari znatno lakse resavaju od nerelacionog nacina. Pokazacemo oba nacina, da se vide prednosti i mane na obe strane.

Pretpostavimo se da nam je neko narucio obradu ankete.  Podaci se prikupljaju, mi ih spakujemo nekako u kompjuter i vratiom naruciocu obradjene. Anketa/upitnik se sastoji od pitanja koja nude vise odgovora, a bira se tacno jedan.  Treba isporuciti tabelu/izvestaj gde su prikazane frekvencije izabranih odgovora. Za svako pitanje treba utvrditi koji odgovor je koliko puta izabran.

Anketa ili upitnik

Uzmimo najprostiji moguci slucaj. Dat je anketni listic/upitnik sa odredjenim brojem pitanja. Ucesnici ankete odgovaraju na pitanja zaokruzivanjem jednog od ponudjenih odgovora. Sva pitanja su tipa “zaokruzi jednu od ponudjenih opcija”. Da pojednostavimo problem, broj ponudhjenih opcija ce uvek biti cetiri  i opcije su ‘a’,’b’,’c’,’d’.  Moramo da dodamo jos dva znaka. Jedan za  slucaj kada je izbarano vise od jedneog odgovora i kada nije izabrano nista. Neka to budu ‘#’ za vise od jednog odgovora i ‘-‘ (minus) za slucaj kada nije izabrano nista.

Process prikupljanja odgovora i pretvaranje odgovora u elektronski oblik u danasnje vreme ide obicno ovako:

Odstampaju se listovi sa pitanjima. Svaki se list oznaci jedinstvenim brojem, koji je prikazan u vidu barkoda. Listovi se podele ucesnicima. Ucesnici oznace izabrane odgovore. Ucesnici vrate listove.  Ako ucesnika ima malo, sve se moze ukucati u nekakvu bazu podataka. Ako ucesnika ima mnogo, mogu se listovi provuci kroz skenere. Skeneri ‘znaju’ odakle da citaju odgovore i  na kraju izbace tekst fajl gde je svaki red jedan upitnik, a odgovori su polja u tom redu, onicno odvojena zarezom. Pitanje je – sta dalje? Drugim recima, kako treba da izgleda baza podataka u koju cemo da a) ukucamo podatke ili b) smestimo podatke koje je procitao skener. Cesto imamo kobinaciju oba metoda – vecina listova ide kroz skenere, a zakasneli i zaostali odgovori unose se rucno. Sta da se radi?

Prva ideja koja ljudskim bicima pada na pamet je da se napravi tabela koja ima jednu kolonu za identifikacioni broj lista, to bi bio ujedno i primarni kljuc, i po jednu kolonu za odgovore. Vrlo elegantno na prvi pogled.  Napraviti formu za unos u ovakvu tabelu je zaista trivijalno, u nekakvom Accesu ili VB ili bilo cemu drugom. Import iz tekstualnih fajlova koje dobijemo od skenera je veoma jednostavan. Na prvi pogled. Tabela bi mogla da izgleda ovako:

ID Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q8 Q10 Q11 Q12
1 a b c a a b d c d a b d
2 b c d a a c d a b c b a
3 c D c a a d c d a c c c

Sta ne valja? Prvo, kako obezbediti da se unose samo dozvoljene vrednosti?  Jednostavno, u dizajnu tabele stavite za svaku kolonu da su dozvoljene vrednosti samo a,b,c,d.  Ej, ali to moramo da uradimo za svaku kolonu, dakle  jos 11 puta. Nije strasno, ukoliko se setimo toga odmah. Iz iskustva znam da kad se izabere resenja sa ovakvom tabelom, ljudi su toliko ushiceni sto je tabela elegantna i ceo posao lak za programiranje, da zaborave na sve ostalo, pa se ova ogranicenja nikad ne postave…

Znaci, ovako bismo kreirali tabelu i uneli test podatke:

  1. IF OBJECT_ID('tempdb..#Anketa'IS NOT NULL DROP TABLE #Anketa  
  2. GO  
  3. CREATE TABLE #Anketa  
  4. (  
  5. ID INT PRIMARY KEY  
  6. , Q1 VARCHAR(1) NOT NULL CHECK (Q1 IN ('a','b','c','d','#','-'))  
  7. , Q2 VARCHAR(1) NOT NULL CHECK (Q2 IN ('a','b','c','d','#','-'))  
  8. , Q3 VARCHAR(1) NOT NULL CHECK (Q3 IN ('a','b','c','d','#','-'))  
  9. , Q4 VARCHAR(1) NOT NULL CHECK (Q4 IN ('a','b','c','d','#','-'))  
  10. , Q5 VARCHAR(1) NOT NULL CHECK (Q5 IN ('a','b','c','d','#','-'))  
  11. , Q6 VARCHAR(1) NOT NULL CHECK (Q6 IN ('a','b','c','d','#','-'))  
  12. , Q7 VARCHAR(1) NOT NULL CHECK (Q7 IN ('a','b','c','d','#','-'))  
  13. , Q8 VARCHAR(1) NOT NULL CHECK (Q8 IN ('a','b','c','d','#','-'))  
  14. , Q9 VARCHAR(1) NOT NULL CHECK (Q9 IN ('a','b','c','d','#','-'))  
  15. , Q10 VARCHAR(1) NOT NULL CHECK (Q10 IN ('a','b','c','d','#','-'))  
  16. , Q11 VARCHAR(1) NOT NULL CHECK (Q11 IN ('a','b','c','d','#','-'))  
  17. , Q12 VARCHAR(1) NOT NULL CHECK (Q12 IN ('a','b','c','d','#','-'))  
  18. )  
  19. GO  
  20. INSERT INTO #Anketa   
  21. VALUES (1, 'a''b''c''a''a',  'b''d''c''d''a''b''d')  
  22. INSERT INTO #Anketa   
  23. VALUES (2, 'c''a''a''a''a',  'b''d''c''c''a''-''c')  
  24. INSERT INTO #Anketa   
  25. VALUES (3, 'c''c''c''a''a',  'a''d''c''a''a''a''d')  
  26. INSERT INTO #Anketa   
  27. VALUES (4, 'a''d''c''a''a',  'b''d''a''b''b''a''c')  
  28. INSERT INTO #Anketa   
  29. VALUES (5, '#''a''c''a''a',  '-''d''c''d''a''b''-')  
  30. INSERT INTO #Anketa   
  31. VALUES (6, 'a''b''c''a''a',  'a''a''b''-''a''#''-')  
  32. INSERT INTO #Anketa   
  33. VALUES (7, 'b''c''c''a''a',  'c''d''c''c''c''c''-')  
  34. INSERT INTO #Anketa   
  35. VALUES (8, 'c''d''c''a''c',  'b''d''c''b''a''b''#')  
  36. INSERT INTO #Anketa   
  37. VALUES (9, '-''#''c''a''a',  'b''d''d''#''a''a''d')  
  38. INSERT INTO #Anketa   
  39. VALUES (10, 'a''c''c''a''a''c''d''c''a''#''-''#')  
  40. INSERT INTO #Anketa   
  41. VALUES (11, 'a''b''c''a''a''c''d''c''d''a''b''d')  
  42. INSERT INTO #Anketa   
  43. VALUES (12, 'c''a''a''a''a''b''d''c''c''a''-''c')  
  44. INSERT INTO #Anketa   
  45. VALUES (13, '#''a''c''a''a''-''d''c''d''a''b''-')  
  46. INSERT INTO #Anketa   
  47. VALUES (14, 'c''c''c''a''a''a''d''c''a''a''a''d')  
  48. INSERT INTO #Anketa   
  49. VALUES (15, '#''a''c''a''a''-''d''c''d''a''b''-')   

 Recimo da je sve u redu, niko nije uneo sta ne sme da se unese, podaci su ‘cisti’. Kako cemo da ih obradimo? Ono sto se trazi za anketu jeste ‘za svako pitanje, koliko ljudi je odgovorili a,b,c, ili d i koliko ih je zaokruzilo dva i vis eodgovora ili nije izabralo ni jedan. To se zove ‘izracunati frekvencije za pitanja ankete’. Elem, za pitanje Q1 imali bismo:

  1. SELECT Q1, COUNT(*) AS Freq  
  2. FROM #Anketa  
  3. GROUP BY Q1   
  4.   
  5. Q1   Freq  
  6. ---- -----------  
  7. #    3  
  8. -    1  
  9. a    5  
  10. b    1  
  11. c    5    

 

Za pitanje Q2 bilo bi:

  1. SELECT Q2, COUNT(*) AS Freq  
  2. FROM #Anketa  
  3. GROUP BY Q2   
  4.   
  5. Q2   Freq  
  6. ---- -----------  
  7. #    1  
  8. a    5  
  9. b    3  
  10. c    4  
  11. d    2   
  12.   
  13. (5 row(s) affected)   

I tako za sva ostala pitanja, njih 12 ukupno. Ako malo dalje pogledam rezultate, vidim da je naziv prve kolone ujedno i naziv pitanja. Zasto ovo ne valja? Pa ako hocu da vidim sva pitanja odjednom, mogu recimo da uradim UNION kveri, ovako:

  1. SELECT Q1, COUNT(*) AS Freq  
  2. FROM #Anketa  
  3. GROUP BY Q1  
  4. UNION  
  5. SELECT Q2, COUNT(*) AS Freq  
  6. FROM #Anketa  
  7. GROUP BY Q2  
  8. GO  
  9. Q1   Freq  
  10. ---- -----------  
  11. #    1  
  12. #    3  
  13. -    1  
  14. a    5  
  15. b    1  
  16. b    3  
  17. c    4  
  18. c    5  
  19. d    2   
  20.   
  21. (9 row(s) affected)   

Rezultat je necitak. Ne zna se sta je sta? A ako probate UNION ALL dobicete 10 reodova u rezultatu umesto 9. Znaci, nije bas tako jednostavno.   Ajde da napravimo da radi:

  1. SELECT 'Q1' AS Pitanje, Q1 AS Odgovor, COUNT(*) AS Freq  
  2. FROM #Anketa  
  3. GROUP BY Q1  
  4. UNION   
  5. SELECT 'Q2' AS Pitanje, Q2 AS Odgovor, COUNT(*) AS Freq  
  6. FROM #Anketa  
  7. GROUP BY Q2  
  8. GO  
  9. Pitanje Odgovor Freq  
  10. ------- ------- -----------  
  11. Q1      #       3  
  12. Q1      -       1  
  13. Q1      a       5  
  14. Q1      b       1  
  15. Q1      c       5  
  16. Q2      #       1  
  17. Q2      a       5  
  18. Q2      b       3  
  19. Q2      c       4  
  20. Q2      d       2   
  21.   
  22. (10 row(s) affected)  

 Sada se lepse vidi sta je sta, naziv pitanaj je u rezultatu kverija. Mali problem je sto treba prosiriti UNION, na svih 12 pitanja. Sta ce biti kad bude 200 pitanaj u anketi?

Jos jedna stvar ne valja, a ne mora da se vid na prvi pogled. Na pitanje Q1 niko nije odgovorio ‘d’. stoga se ‘d’ nije pojavilo u rezultatu. Ne moze da se GROUP BY po necemu cega nema. Nazalost,  narucioci posla hoce da vide da je na pitanje Q1 0 (nula) ucesnika izabralo opciju ‘d’. Sad ce kveri da se zaista zakomplikuje. Kako da dobijete a,b,c,d,#,= na levoj strani? To mirise na nekakav LEFT JOIN. Dobro, LEFT JOIN – ali sa cim? Jedina tabela koju imamo jeste Anketa. Bas zato je resenje elegantno. I programiranje lako, i unos jednostavan. Do ovog momenta.

Sad uvidjamo da bi nam dobro dosla jos jedna tabela, da sadrzi moguce odgovore. Lako cemo dodati jos jednu tabelu za odgovore. Malo je doduse nepravilna praksa da se tabele dodaju u momentu obrade podataka. Ankete smo stampali pre dva meseca, skupljali ih i skenirali jos mesec dana, znaci projekat je poceo pre tri meseca. I programer koga smo zaposlili na ugovor da nam uradi key-entry aplikaciju je odavno otisao. Sta cemo sad? Pa da napravimo jos jednu tabelu, ko te pita za pravilnu praksu, daj da odradimo posao:

  1. IF OBJECT_ID('tempdb..#MoguciOdgovori'IS NOT NULL DROP TABLE #MoguciOdgovori  
  2. GO  
  3. CREATE  TABLE #MoguciOdgovori    
  4. (  
  5. MoguciOdgovor  VARCHAR(1) PRIMARY KEY  
  6. , Opis VARCHAR(50) NOT NULL  
  7. )  
  8. GO  
  9. INSERT INTO #MoguciOdgovori VALUES ('a','Izabran prvi odgovor')  
  10. INSERT INTO #MoguciOdgovori VALUES ('b','Izabran drugi odgovor')  
  11. INSERT INTO #MoguciOdgovori VALUES ('c','Izabran treci odgovor')  
  12. INSERT INTO #MoguciOdgovori VALUES ('d','Izabran cetvrti odgovor')  
  13. INSERT INTO #MoguciOdgovori VALUES ('#','Izabrano vise odgovore')  
  14. INSERT INTO #MoguciOdgovori VALUES ('-','Nije izabran ni jedan odgovor')    

Kako nam ovo pomaze? PA mozemo da napisemo nesto ovako:

  1. SELECT M.MoguciOdgovor, Pitanje,  Freq  
  2. FROM #MoguciOdgovori AS M  
  3. LEFT JOIN  
  4. (  
  5. SELECT 'Q1' AS Pitanje, Q1 AS Odgovor, COUNT(*) AS Freq  
  6. FROM #Anketa  
  7. GROUP BY Q1  
  8. AS Q  
  9. ON M.MoguciOdgovor = Q.Odgovor  
  10. GO  
  11. MoguciOdgovor Pitanje Freq  
  12. ------------- ------- -----------  
  13. #             Q1      3  
  14. -             Q1      1  
  15. a             Q1      5  
  16. b             Q1      1  
  17. c             Q1      5  
  18. d             NULL    NULL   
  19.   
  20. (6 row(s) affected)  

Dobili smo ‘d’ ali imamo na dva mesta NULL koji treba da eliminisemo.  Trazi se da pise 0 (nula) a ne NULL za pitanja na koja niko nije odgovorio. I ne sme ostati NULL u koloni Pitanje. Kad napravimo UNION za svih 12 pitanja, nece se znati koji NULL pripada kom pitanju. Upit se dodatno komplikuje.

NULL u koloni Freq se lako resava upotrebom funkcije Coalesce, a naziv pitanja mozemo prosto da unesemo kako kostantu:

  1. SELECT   
  2.    'Q1' AS Pitanje  
  3.    , M.MoguciOdgovor  
  4.    ,  COALESCE(Freq,0) AS Freq  
  5. FROM #MoguciOdgovori AS M  
  6. LEFT JOIN  
  7. (  
  8. SELECT 'Q1' AS Pitanje, Q1 AS Odgovor, COUNT(*) AS Freq  
  9. FROM #Anketa  
  10. GROUP BY Q1  
  11. AS Q  
  12. ON M.MoguciOdgovor = Q.Odgovor   
  13.   
  14. Pitanje MoguciOdgovor Freq  
  15. ------- ------------- -----------  
  16. Q1      #             3  
  17. Q1      -             1  
  18. Q1      a             5  
  19. Q1      b             1  
  20. Q1      c             5  
  21. Q1      d             0   
  22.   
  23. (6 row(s) affected)   

 

Sada naravno ovo treba da uradimo za ostalih 11 pitanja.  Sad vec postoji znacajna verovatnoca da ce se negde napraviti greska u Cut/Paste operacijama. U svakom slucaju, posle nekoliko proba/greski dobicemo nesto ovako:

  1. SELECT   
  2.    'Q1' AS Pitanje  
  3.    , M.MoguciOdgovor  
  4.    ,  COALESCE(Freq,0) AS Freq  
  5. FROM #MoguciOdgovori AS M  
  6. LEFT JOIN  
  7. (  
  8. SELECT 'Q1' AS Pitanje, Q1 AS Odgovor, COUNT(*) AS Freq  
  9. FROM #Anketa  
  10. GROUP BY Q1  
  11. AS Q  
  12. ON M.MoguciOdgovor = Q.Odgovor  
  13. UNION ALL  
  14. SELECT   
  15.    'Q2' AS Pitanje  
  16.    , M.MoguciOdgovor  
  17.    ,  COALESCE(Freq,0) AS Freq  
  18. FROM #MoguciOdgovori AS M  
  19. LEFT JOIN  
  20. (  
  21. SELECT 'Q2' AS Pitanje, Q2 AS Odgovor, COUNT(*) AS Freq  
  22. FROM #Anketa  
  23. GROUP BY Q2  
  24. AS Q  
  25. ON M.MoguciOdgovor = Q.Odgovor   
  26.   
  27. Pitanje MoguciOdgovor Freq  
  28. ------- ------------- -----------  
  29. Q1      #             3  
  30. Q1      -             1  
  31. Q1      a             5  
  32. Q1      b             1  
  33. Q1      c             5  
  34. Q1      d             0  
  35. Q2      #             1  
  36. Q2      -             0  
  37. Q2      a             5  
  38. Q2      b             3  
  39. Q2      c             4  
  40. Q2      d             2   

Za svih 12 pitanja, skripta bi bila 6 puta duza. Milina jedna za trazenje gresaka i kontrolu kvaliteta. Sta bi bilo za 200 pitanja?

Sta ce biti ako narucilac posla pozeli da vidi podatke pivotizovano, tako da svako pitanje bude u jednom redu a ab,c,d,#,- budu kolone? Dakle, kako dobiti ovakav rezultat:

  1. Pitanje    A   B   C   D   #   -  
  2. ----------------------------------  
  3. Q1         5   1   5   0   3   1  
  4. Q2         5   3   4   2   1   0   

Postoji li elegantan nacin da ovakav rezultat obezbedi SQL? Naravno da moze da se napise kod ADO/DAO, SQL kursor i slicno, ali moze li pomocu SQL upita? Naravno da moze. Ali nije jednostavno, kveri postaje zaista veliki. Jedna varijanta je ova:

  1. WITH Frekvence AS  
  2. (  
  3. SELECT   
  4.    'Q1' AS Pitanje  
  5.    , M.MoguciOdgovor  
  6.    ,  COALESCE(Freq,0) AS Freq  
  7. FROM #MoguciOdgovori AS M  
  8. LEFT JOIN  
  9. (  
  10. SELECT 'Q1' AS Pitanje, Q1 AS Odgovor, COUNT(*) AS Freq  
  11. FROM #Anketa  
  12. GROUP BY Q1  
  13. AS Q  
  14. ON M.MoguciOdgovor = Q.Odgovor  
  15. UNION ALL  
  16. SELECT   
  17.    'Q2' AS Pitanje  
  18.    , M.MoguciOdgovor  
  19.    ,  COALESCE(Freq,0) AS Freq  
  20. FROM #MoguciOdgovori AS M  
  21. LEFT JOIN  
  22. (  
  23. SELECT 'Q2' AS Pitanje, Q2 AS Odgovor, COUNT(*) AS Freq  
  24. FROM #Anketa  
  25. GROUP BY Q2  
  26. AS Q  
  27. ON M.MoguciOdgovor = Q.Odgovor  
  28. )  
  29. SELECT   
  30.    Pitanje  
  31.    , a = SUM(CASE WHEN MoguciOdgovor = 'a' THEN Freq ELSE 0 END)  
  32.    , b = SUM(CASE WHEN MoguciOdgovor = 'b' THEN Freq ELSE 0 END)  
  33.    , c = SUM(CASE WHEN MoguciOdgovor = 'c' THEN Freq ELSE 0 END)  
  34.    , d = SUM(CASE WHEN MoguciOdgovor = 'd' THEN Freq ELSE 0 END)  
  35.    , '#' = SUM(CASE WHEN MoguciOdgovor = '#' THEN Freq ELSE 0 END)  
  36.    , '-' = SUM(CASE WHEN MoguciOdgovor = '-' THEN Freq ELSE 0 END)  
  37. FROM Frekvence  
  38. GROUP BY Pitanje  
  39. GO  
  40. Pitanje a           b           c           d           #           -  
  41. ------- ----------- ----------- ----------- ----------- ----------- -----------  
  42. Q1      5           1           5           0           3           1  
  43. Q2      5           3           4           2           1           0   
  44.   
  45. (2 row(s) affected)  

Za svih 12 pitanja deo pod WITH klauzulom bio bi sest puta duzi. Sat bi bilo za 200 pitanja, ne smem ni da mislim. Deo posle WITH je interesantan. To je jedan nacin da se u T-SQL uradi pivotizacija tabele (crosstab kveri u Accessu)

Sve u svemu, nekako dodjosmo do rezultata koji bi trebao da zadovolji narucioca posla. Malo mukotrpno, ali sta se moze. Verovatno pogadjate da postoji bolji nacin da se ovo uradi. Postoji, ali necu ga pokazati u ovom momentu. Prikazani upiti mogu nekume da daju ideju da tabela koja sadrzi jedan red po anketnom listu zaista moze da funkcionise. Veoma slicnu situaciju imamo sa testiranjem ucenika i studenata.

 

Skolski test = anketa sa tacnim odgovorima

Zamislite da je u pitanju skolski test. Tada se ne traze frekvencije odgovora (traze se , ali nije primarno). Trazi sa da vidimo koliko tacnih odgovora ima svaki ucesnik testa.  Na osnovu broja tacnih odgovora odredjuje se ocena.

Test izgleda isto kao i anketa. Ponudjeno je vise odgovora,  treba zaokruziti onaj za koji mislite da je tacan. Za svakog ucesnika, odgovor na svako pitanje se poredi sa tacnim odgovorom i rezultat pamti. Kad se prodje kroz sva pitanja, prebroji se koliko ima tacnih odgovora. Na osnovu broja tacnih odgovora, daje se ocena.

Mozemo da koristimo obe tabele iz prethodnog primera. Kako napisati upit  koji broji tacne odgovore? Ako bismo pisali program, pseudo kod bi isao otprilike ovako:

Za svaki red u tabeli Ankete
  UcesnikTesta = Ankete .ID
  BrojTacnihOdgovora = 0
  If Ankete .Q1 = ‘c’ then BrojTacnihOdgovora= BrojTacnihOdgovora+1
  If Ankete .Q2 = ‘a’ then BrojTacnihOdgovora= BrojTacnihOdgovora+1
  If Ankete .Q3 = ‘a’ then BrojTacnihOdgovora= BrojTacnihOdgovora+1
  If Ankete .Q4 = ‘b’ then BrojTacnihOdgovora= BrojTacnihOdgovora+1
  If Ankete .Q5 = ‘d’ then BrojTacnihOdgovora= BrojTacnihOdgovora+1
  If Ankete .Q6 = ‘c’ then BrojTacnihOdgovora= BrojTacnihOdgovora+1
  If Ankete .Q7 = ‘b’ then BrojTacnihOdgovora= BrojTacnihOdgovora+1
  If Ankete .Q8 = ‘c’ then BrojTacnihOdgovora= BrojTacnihOdgovora+1
  If Ankete .Q9 = ‘a’ then BrojTacnihOdgovora= BrojTacnihOdgovora+1
  If Ankete .Q10 = ‘c’ then BrojTacnihOdgovora= BrojTacnihOdgovora+1
  If Ankete .Q11 = ‘a’ then BrojTacnihOdgovora= BrojTacnihOdgovora+1
  If Ankete .Q12 = ‘b’ then BrojTacnihOdgovora= BrojTacnihOdgovora+1 
  Upisi (UcesnikTesta ,BrojTacnihOdgovora) u tabelu RezultatiTesta
  Predji na novi red

Lepo.  Hard-kodirali smo tacne odgovore. Nije bas najbolja praksa, ali radi. Vecina aplikacija koja resava problem testova radi bas ovako. Koliko testova, toliko i aplikacija. Ili barem procedura za brojanje tacnih odgovora.  Milina jedna za trazenje gresaka i kontrolu kvaliteteta. Mozemo li dakle da napisemo upit? Mozda ovako (samo za prvih 6 pitanja, tek da vidimo ideju) :

  1. WITH OdgovoriNaPitanja AS  
  2. (  
  3. SELECT Ucesnik = ID, Pitanje = 'Q1', TacanOdgovor = CASE WHEN Q1='c' THEN 1 ELSE 0 END  
  4. FROM #Anketa  
  5. UNION  
  6. SELECT Ucesnik = ID, Pitanje = 'Q2', TacanOdgovor = CASE WHEN Q2='a' THEN 1 ELSE 0 END  
  7. FROM #Anketa  
  8. UNION  
  9. SELECT Ucesnik = ID, Pitanje = 'Q3', TacanOdgovor = CASE WHEN Q3='a' THEN 1 ELSE 0 END  
  10. FROM #Anketa  
  11. UNION  
  12. SELECT Ucesnik = ID, Pitanje = 'Q4', TacanOdgovor = CASE WHEN Q4='b' THEN 1 ELSE 0 END  
  13. FROM #Anketa  
  14. UNION  
  15. SELECT Ucesnik = ID, Pitanje = 'Q5', TacanOdgovor = CASE WHEN Q5='d' THEN 1 ELSE 0 END  
  16. FROM #Anketa  
  17. UNION  
  18. SELECT Ucesnik = ID, Pitanje = 'Q6', TacanOdgovor = CASE WHEN Q6='c' THEN 1 ELSE 0 END  
  19. FROM #Anketa  
  20. )  
  21. SELECT Ucesnik, COUNT(TacanOdgovor) AS BrojTacnihOdgovora  
  22. FROM OdgovoriNaPitanja  
  23. WHERE TacanOdgovor = 1  
  24. GROUP BY Ucesnik  
  25. GO  
  26. Ucesnik     BrojTacnihOdgovora  
  27. ----------- ------------------  
  28. 2           3  
  29. 3           1  
  30. 5           1  
  31. 7           1  
  32. 8           1  
  33. 10          1  
  34. 11          1  
  35. 12          3  
  36. 13          1  
  37. 14          1  
  38. 15          1   
  39.   
  40. (11 row(s) affected)  

Lepo, ali mukotrpno. I podlozno greskama.  Svi znamo da nije dobra ideja da se podaci ugradjuju u programski kod. A mi smo upravo to uradili. A to nije ni dobro programiranje ni dobra upotreba SQL-a. A nedostaju i neki ucesnici testa – oni koji nemaju ni jedan odgovor. DObro, LEFT JOIN iz sada znate i sami da postavite….

Narucioci posla imaju obicaj da daju pogresne podatke o tacnim odgovorima, pa saznamo u zadnji cas da tacan odgovor na pitanje 5 nije ‘d’ nego ‘b’. A mi vec odradili kod, napunili tabelu rezultata. Sad treba da menjamo kod. U brzini, programer umesto pitanja 5 promeni pitanje 6….

U nastavku, pokusacu da pokazem kako se ovaj problem moze resiti relaciono. Pa da uporedimo resenja. Za najprostiji slucaj.

🙂

  1. 4 Responses to “Ankete i testovi”

  2. Eeee svaka cast Zidar – veoma koristan i poucan tekst!

    Sto se tice problema sa naknadnm izmjenama podataka, za tu svrhu bi se mogla koristiti neka lookup tabela, koja bi sadrzavala poveznicu izmedju pitanja i odgovora…

    By Dejan on Sep 26, 2008

  3. Havla 🙂
    Ovo je us tvari bilo kako NE TREBA raditi, iako je moguce raditi i ovako. U pravu si za lookup tabelu, ali to je razmisljanje na visem nivou. Sledeci post bice o tome – kako u stvari treba raditi.

    Nego, zasto danas kod izgleda mnogo lepse nego juce kad sam ga postovao? Da li ga ti formatiras naknadno? Ako da, hvala ti od srca. Ako mi kazes kako se formatira, ustedecemo tvoj trud i vreme, a ja cu vise da pisem. Pravo da ti kazem, plasi me editor i najgore mi je da prenesem kod. Potrosim vise vremena na te tehnicke stvari nego na stvarno pisanje. Valjda sam prestar pa mi teko ide da nuacim tehnicke stvari potrebna za blogovanje.

    🙂

    By Zidar on Sep 26, 2008

  4. @Zidar: Da, ja sam formatirao SQL upite naknadno, radi bolje preglednosti, a i ljepse izgleda. 🙂
    Znas kako ja radim – tekst pisem normalno u Visual rezimu, a SQL upite prvo napisem u Notepadu, pa onda na sajtu iz Visual rezima predjem u HTML rezim i copy/pasteujem taj SQL kôd izmedju <pre clas=”sql” name=”code”> i </pre>tagova.

    Znaci, ovako npr.:
    <pre clas=”sql” name=”code”>
    SELECT neka_kolona
    FROM neka_tabela
    WHERE druga_kolona = ‘SQL formatiranje’;
    </pre>

    By Dejan on Sep 26, 2008

  5. Dejane, puno hvala. Naravno da lepse izgleda. Potrudicu se da naucim ovo i da ti ustedim vreme u buducnosti.

    🙂

    By Zidar on Sep 29, 2008

Post a Comment