Andmebaasipõhiste veebirakenduste
arendamine Microsoft Visual Studio 2005 ja
SQL Server 2005 baasil
Tallinn
2007
Sisukord
Sisukord 2
Eessõna 8
Sissejuhatus 9
Õppematerjali kasutamise juhis 10
Microsoft .NET platvorm 12
C# 15
Sissejuhatus 15
Põhivõimalused 15
Käivitamine 18
Suhtlus arvutiga 19
Arvutamine 20
Valikud 21
Kordused 23
Korrutustabel 25
Alamprogramm 26
Massiivid 27
Käsud mitmes failis 33
Tekst 34
Tekstifailid 36
Juhuarv 39
Omaloodud andmestruktuur 40
Objektorienteeritud programmeerimine 43
Tutvustus 43
Dokumenteerivad kommentaarid 49
Pärilus 51
Ülekate 54
Liidesed 55
Abstraktne klass 58
Meetodite asendus 59
Omadused 61
Indekseering 64
Struktuurne andmestik 66
Operaatorite üledefineerimine 72
Abivahendid 80
Erindid 80
Enum 85
Andmekollektsioonid 86
Mallid 91
Atribuudid 94
Andmebaasiliides 98
Ühenduse loomine, päring 98
Andmete lisamine 102
SQL-parameeter 102
Salvestatud protseduur 103
Ülesandeid 104
Funktsiooni delegaadid 104
Funktsioonide komplekt 105
Sündmused 106
Ilmajaamad 107
Graafiline liides 109
Visual Studio C# Expressi install 111
Esimese rakenduse loomine 116
Kokkuvõte 122
SQLi keel 123
Microsoft SQL Server 2005 124
SQL Server 2005 perekond 125
SQL Server 2005 Express Edition 125
Install ja seadistus 125
Töö alustamine 127
Andmebaasi loomine 129
Tabeli loomine 130
Andmetüübid 131
Primaarvõti 132
Andmete sisestus 133
Harjutus (tabeli loomine) 134
Lihtsamad päringud 136
Agregaatfunktsioonid 140
Muutmine 143
Kustutamine 145
Harjutus (lihtsad päringud) 146
Pikemad päringud 151
LIKE 151
Tingimuste kombineerimine 152
IN 153
NOT 154
TOP, päringu algusosa 154
Grupeerimine 157
ROLLUP, gruppide koondinfo 159
CUBE, täiendatud koondinfo 161
Harjutused (pikemad päringud) 162
Mitu tabelit 164
Tabelite ühendamine päringutes 165
LEFT ja RIGHT JOIN 167
CROSS JOIN 168
Seos sama tabeliga 169
Päringutulemuste ühendamine 170
Harjutused (tabelite ühendamine) 171
Alampäringud 175
Tabeli asendaja 175
Väärtuse asendaja 175
Sarnase pikkusega lapsed 176
Veeru asendaja 178
Tekkinud tabelite ühendamine 178
Harjutused (Alampäringud) 179
Lisavõimalused 180
Andmetüüpide loomine 180
Andmete ühtsuse tagamine 181
Piirangud 181
Indeksid 181
Päästikprotsess 182
Ajutiste tabelite kasutamine 183
Tsükkel, valik 184
Muutujasse lugemine 185
Kursor 185
Schema 187
Common Table Expression 188
Ülesandeid 191
PIVOT JA UNPIVOT 191
APPLY 193
Nummerdamised 193
Vaade 194
Ülesandeid 195
Salvestatud protseduur 196
Loomine 196
Ülesandeid 197
Transaktsioonid 197
Ülesandeid 199
XML andmete kasutamine 201
FOR XML 201
OPENXML 204
Ülesandeid 207
Varukoopia 209
Loomine 209
Taastamine 209
Ülesandeid 210
Kokkuvõte 211
Andmetele ligipääs 212
Andmeallika külge ühendumine 214
Töötamine andmebaasiga ühendatud keskkonnas 216
XxxCommand 216
Parameetrite kasutamine 217
Ridade lugemine väljundist (DataReader) 218
Transaktsioonid 219
Töötamine ühenduseta keskkonnas (DataSets) 219
Ülesandeid 222
Olemasolevate andmete põhjal DataSeti loomine 222
Ülesandeid 226
XML 227
XML’i kirjutamise reeglid 227
Reeglid 227
XML’i elemendid 228
Atribuudid 229
XHTML 229
Nimeruum 230
XML’i valideerimine 230
XML skeemid 230
Ülesandeid 231
XMLi kasutamine SqlServeris 231
XMLi genereerimine relatsioonilistest andmetest 231
Ülesandeid 234
XML andmetüübi kasutamine 234
Ülesandeid 235
XML andmete kasutamine .NET raamistikus 236
XMLi parsimine 236
Ülesandeid 239
XMLi valideerimine 239
Ülesandeid 241
XMLi salvestamine 241
Ülesandeid 242
ASP.NET 243
Visual Studio paigaldamine 243
Lihtsa veebilehestiku loomine 244
Esimene veebileht 244
Ülesandeid 246
Esimene veebileht Visual Studio abil 247
Pilt 251
Seotud lehed 254
Ülesandeid 259
Astmelised laadilehed (CSS) 260
Näite kopeerimine 260
Pildi suuruse muutmine 260
Käskluste valik 261
Valmisnäidete kasutamine 262
Määramine päises 264
Laadileht eraldi failis 265
Kujundusklass 268
Ülesandeid 270
Programmeeritavad veebilehed 270
Kellaaeg 271
Arvutav veebileht 273
Tehtevalikuga kalkulaator 276
Ülesandeid 278
Andmebaasipõhise veebirakenduse loomine 279
Andmetabeli näitamine veebilehel 279
Lähtekood 285
Ülesandeid 288
Andmete muutmine ja lisamine veebilehelt 288
Andmete lisamine 291
Lähtekood 292
Ülesandeid 296
Otsing 297
Ülesandeid 306
Lehe kaitsmine parooliga 306
Ülesandeid 316
Andmed mitmes tabelis 316
Tabelite loomine ja sidumine 317
Ülesandeid 322
Andmete vaatamine 322
Ülesandeid 326
Andmete lisamine 326
Ülesanne 332
Andmete muutmine 333
Ülesandeid 337
ASP.NETi tehnilised killukesed 339
Lihtsaim veebileht 339
Ülesandeid 341
Seadistamine (Web.config) 341
Rakenduse jälgimine (Trace) 342
Vigade haldamine 346
Rakenduse veebist eemaldamine 346
Globaalne veakontroll 347
Keskne veakontroll 347
Veakontroll lehel 348
Veakontroll koodis 349
Ülesandeid 349
Lokaliseerimine 349
Lokaalsed ressursid 350
Globaalsed ressursid 352
Programselt keele muutmine 352
Ülesandeid 354
Master Pages 354
Objektid lehel 355
Standardsed serveri kontrollid 355
Html 359
Valideerimiskontrollid 359
Ülesandeid 361
Navigeerimiskontrollid 361
Veebilehestikul navigeerimine 362
Omaloodud elemendid 363
Ülesandeid 365
Veebilehtede kujundamine kasutades nägusid (Themes) 365
Ülesandeid 367
Väärtuste tööaegne meelespidamine 367
Application 368
Cache 368
Session 369
ViewState 370
Ülesandeid 370
Veebisaidi turvamine 370
Üldised seadistused 371
Ülesandeid 372
Login kontrollid 372
Andmete kasutamine ASP.NET 2.0 keskkonnas 372
Andmete kuvamine lihtsate loetelude abil 373
Andmete kuvamine keerukate loetelude abil 374
Hierarhiliste andmete kuvamine 374
WebParts 374
Veebiteenused 376
Veebiteenuste tegemine 376
Veebiteenuse kasutamine 376
Lisad 380
Ressursside hoidmine SQL Serveris – Resource Provider 380
Kasutajatunnuste hoidmine andmebaasis - MembershipProvider 384
Kasutajagruppide hoidmine andmebaasis – RoleProvider 387
Eessõna
Hea õpilane!
Microsofti arenduspartnerid ja kliendid vajavad andekaid koodimeistreid, kes oskaksid
arendada tarkvara kiiresti populaarsust võitval .NET platvormil. Kui Sulle meeldib
programmeerida, siis loodame, et saame Sulle pakkuda huvipakkuvat materjali.
Alljärgnevast materjalist leiad tihedalt praktilist ja kasulikku infot tunnustatud
professionaalide sulest. Siin on infot nii .NET aluste kohta kui ka juhiseid rakenduste
loomiseks, näidetest ja ülesannetest rääkimata.
Kogu õpe on välja töötatud vabavaraliste Microsoft Visual Studio ja SQL Server 2005
Express versioonide baasil, mis on spetsiaalselt mõeldud õpilastele ja asjaarmastajatele
Microsofti platvormiga tutvumiseks. Sellesama materjali põhjal on edukalt testinud oma
teadmisi Eesti kõige tegijamad arvutiõpetajad, sh ka Sinu kooli õpetaja!
Kui Sul tekib küsimus, miks Microsoft toetab taoliste materjalide koostamist, siis vastus on
lihtne: tahame tuua kokku uusimat tehnoloogiat tundva põlvkonna, kes ületaks meie partnerite
senised ootused ja tõstaks programmeerimise lati tasemele, kuhu me ise ulatunud pole.
Niisiis, koodimeistrid, edu teile!
Rain Laane
Microsoft Eesti juht
Sissejuhatus
Käesolev juhend on mõeldud kasutamiseks õppematerjalina Veebistuudiumis. Juhendis
antakse edasi põhiteadmised, mis on vajalikud andmebaasipõhiste ASP.NET 2.0
veebirakenduste loomiseks.
Koostades alustasime põhitõdedest ning väga keerulisi konstruktsioone ei käsitle. Selle
juhendiga töötamiseks piisab, kui on olemas huvi programmeerimise vastu.
Kuigi .NET raamistik võimaldab koodi kirjutamist kümnetes erinevates keeltes, piirdume siin
juhendis C# keelega, kui keelega, mis on spetsiaalselt loodud .NET raamistiku tarbeks.
Andmebaaside osas vaatleme SQL Server 2005 võimalusi ning XML failide kasutamist.
Õppematerjali väljatöötamist toetasid Microsoft Eesti, AS BCS Koolitus ja Tiigrihüppe
Sihtasutus.
Avastamisrohkeid õpinguid!
Erki Savisaar
Jaagup Kippar
Õppematerjali kasutamise juhis
Siinses materjalis on hulgem lehekülgi ja peatükke. Esimese hooga võib see tekst väljatrükituna
kätte võttes suisa ära ehmatada. See pole aga sugugi autorite eesmärk. Kirjutist koostades on
mõeldud nii algajate kui edasijõudnute peale. Et eesti keeles pole vähemasti siiani (2008ks
aastaks) .NETi kohta midagi põhjalikumat kirjutatud, siis püüab see õppematerjal sobida
võimalikult paljudele, kel teema kohta huvi või vajadus. Eks edasijõudnud suudavad juba ise
sobivaid teemasid leida ning ka veebist ja suurematest targematest raamatutest juurde otsida.
Siin aga peaksid siiski nii veebirakenduste, andmebaaside kui ka „puhta“ programmeerimise
kohta olema sees põhitõed, mille abil on enamik ettetulevatest olukordadest võimalik ära
lahendada ning nende oskuste põhjal olla piisavalt tasemel, et suuta soovi korral arvutifirmasse
praktikale minna ja mõne ajaga sealsesse töösse sulanduda.
Päris algajatele on valida kaks sisendpunkti, kust peaks saama alustada „tavainimese“
arvutialaste teadmiste – ehk siis teksti kirjutamise ja failide salvestamise oskusega. Lihtsam,
ilusam ja värvilisem on ASP.NETi peatüki algusosa, kus saab enesele veebilehe kokku panna ja
seda soovide järgi kujundada. Edasi sealt juba oskused andmete lugemiseks ja salvestamiseks.
Kel aga tahtmist kohe tõsisemalt programmeerimismaailma siseneda, nende algajate
jaoks peaks ka C# peatükk täiesti õpitav olema. Võrreldes veebimaailmaga on sealne
kasutajaliides tunduvalt mustvalgem, aga selle eest on jälle tunduvalt vähem kohti, kuhu
programmeerimise käigus eksida võiks. Esimese mõnekümne leheküljega saab ülevaate
programmeerimise põhimõistetest ja –võimalustest. Edasi suuremaks väljakutseks on
objektorienteeritusega seonduv, millest tänapäeval vähegi suurema programmi kirjutamisel ei
saa üle ega ümber. Viimased punktid C# juures aga on pigem edasijõudnutele erilisematel
juhutudel hakkama saamise või siis sertifikaadieksamiks valmistumise tarbeks. Ka on
kõrgkoolidel võimalus kasutada C# materjali oma programmeerimise põhikursuse alusena –
senised katsed on olnud edukad.
Veebistuudiumi materjali muud peatükid on ka üles ehitatud põhimõttel, et lihtsamast
ja tuttavamast keerukama ja võõrama poole. Andmebaaside juures alustamiseks on baasi ja
tabeli loomine hädavajalik tegevus. Lihtsama ülesehitusega päringud kuuluvad iga rakenduse
juurde ning sageli saab nendega küllalt palju ära teha. Kui aga andmed keerukamad, siis
paratamatult tuleb tabeleid siduda ning sobivate väljavõtete saamiseks ka ridu grupeerida ning
nende pealt tulemusi kokku arvutada. Praktiliste andmebaasirakenduste koostamiseks võiks
siinne materjal päris hea aluse anda. Kui aga peetava kursuse eesmärgiks on anda ka
andmebaaside projekteerimise põhialused (nagu kõrgkoolides kombeks), siis on vaja lisaks
tutvuda veel andmemudelite, normaalkujude jm. andmebaasiteooria alla kuuluvate teemadega.
Iga osa lõpus on ülesanded. Neid on püütud sättida nõnda, et keskmisel õppuril oleks
paras jutt läbi lugeda, läbi mõelda, mõni näide ka järele proovida. Ning siis ülesanded ette
võtta ja nende abil kogu lugu otsast peale uuesti läbi teha. Kui aga tundub mõni ülesanne juba
tõsiselt tuttav ja liialt lihtne, eks selle või siis vahele jätta. Või kiiresti läbi proovida, et kas ikka
on nii lihtne kui pealtnäha tundub. Samuti, kui pärast ülesannete läbitegemist kipub vastav
peatükk ikka segaseks jääma, tuleb kindlasti kasuks, kui enesele või oma õpilastele mõned
teemakohased toimetused juurde mõelda ning sealtkaudu kogu lugu veel korra läbi katsetada.
Kordamine on tarkuse ema. Ning eriti hiljem aluseks olevate teemade puhul tuleb ikka kasuks,
kui see veelkord pulkadeni läbi näritud ja enesele selgeks tehtud on.
Soovitavat õppematerjali kasutusvõimalust saab vaadata ka järgneva skeemi pealt:
Microsoft .NET platvorm
Microsoft .NET raamistik on platvorm programmide loomiseks. Enne .NETi oli võimalik
programme luua mitmetele erinevatele platvormidele nt DOS, Win32, Linux jne. .NET
platvormi loomise eesmärk on võtta programmeerija õlult kohustus tagada programmi
ühilduvus erinevate protsessorite ja operatsioonisüsteemidega, andes rohkem aega
programmiga põhifunktsionaalsusega tegelemiseks. Riistvara ja operatsioonisüsteemidega
ühilduvuse tagamine on jäetud raamistiku loojate hooleks.
Lisaks Microsoftile, kes pakub raamistiku Windowsi operatsioonisüsteemidele, arendatakse
Novelli toetusel analoogset platvormi ka Linux, Solaris, Max OS X ja Unix
operatsioonisüsteemile (lisaks on toetatud ka Windowsi operatsioonisüsteem). Platvormi
nimeks on mono ning selle kohta saab rohkem lugeda aadressilt
http://www.mono-project.com. Mõlemad lahendused baseeruvad ühtedel ja samadel
ECMA/ISO poolt kinnitatud avatud standarditel.
Kõige aluseks nende platvormide loomisel on CLI e. Common Language Infrastructure, mis
kirjeldab ära, millised peavad olema keeled, mida platvormil saab kasutada ning millisele
kujule peavad nendes keeltes kirjutatud koodid kompileeruma. CLI üks huvitav omadus on
see, et enam pole vahet, millises keeles programmeerid, sest kompileeritud kood on lõpuks
ikkagi sama. Seega saab iga programmeerija valida endale just selle keele, mis kõige
hingelähedasem.
Järgmine tase on CLR e. Common Language Runtime (MONO puhul CLI virtual machine),
mis pakub hallatud (turvalist) keskkonda programmeerimiseks. Selles hallatud keskkonnas on
keskse kontrolli all klasside laadimise e. mälu hõivamine, mälu vabastamine (Garbage
collector) ning vahekoodi kompilaator masinkoodiks. Lisaks pakub CLR programmi loomiseks
vajalikke klasse. Lisaks klasside kasutamise võimalusele on paljud neist ka päritavad e.
nendest on võimalik pärimise teel luua oma klasse.
Kõrval asuvalt diagrammilt on näha, et raamistik lisab uue kihi programmi ja riistvara vahele,
mis loomulikult ei mõju hästi programmi jõudlusele. Hea uudis on see, et Microsofti .NET
raamistik integreerub üsna ilusti Windowsi sisse muutudes osaks Windowsist. Enamgi veel,
kui võtta Microsofti viimane Windows e. Vista siis raamistik ongi Windows! See tähendab
seda, et kõik klassid ning meetodid, mida Microsoft kasutab oma rakenduste loomisel, on
kasutamiseks ka kõigile teistele arendajatele, mis omakorda annab kätte väga
võimalusterohked vahendid kiiresti funktsionaalsete programmide ehitamiseks.
CLR rakenduste kompileerimine käib kahes faasis:
* Esimese sammuna programmeerija kompileerib oma lähtekoodi vahekeelde. Microsofti
raamistiku puhul on selleks MSIL (Microsoft Intermediate Language).
* Teise sammuga käivitamise hetkel CLRi kooseisus olev kompilaator kompileerib
MSILi masinkoodi, mida protsessor hakkab täitma. Kompileeritakse vaid need osad
programmist, mida kasutatakse e. kompileerimine on kiire, kuigi esimene käivitamine
võib olla aeglasem kui kohe binaarsel kujul oleval programmil.
Viimasel hetkel kompileerimise eelis seisneb selles, et protsessorile käivitamiseks mõeldud
binaarset koodi on võimalik optimeerida täpselt selle protsessori jaoks, mis hakkab programmi
jooksutama. Ei ole vahet, kas protsessor on 32 või 64 bitine jne.
Tulles nüüd tagasi Microsofti .NET platvorm juurde siis see pole mitte üksnes CLR, vaid
toodete kogumik, mis sisaldab kõiki vajalikke vahendeid jagatud rakenduste ehitamiseks,
pakkudes keelest sõltumatut, ühtset programmeerimise mudelit programmi kõigi kihtide jaoks.
.NET platvorm toetab täielikult Interneti platvormist sõltumatuid ja standardseid
tehnoloogiaid nagu HTML, XML ja SOAP.
.NET platvormiga seotud tooted võib jagada 4 kategooriasse:
* .NET raamistik – baseerub Common Language Runtime’l (CLR). CLR pakub
baasteenuseid ja klasse programmide loomiseks, sõltumata keelest ja programmi kihist.
* .NET My Services – on kogumik kasutajatele suunatud XML veebiteenuseid. Nende
teenuste hulka kuuluvad: .NET Passport autentimine, meeldetuletuste saatmine ja
vastuvõtmine, personaalse info ja andmete salvestamise võimalus
* .NET Enterprise Servers – pakuvad lisafunktsioone ärirakenduste tarbeks. Enterprise
serverid on:
? Microsoft SQL Server – Võimaldab Transact-SQL keele abil manipuleerida nii
relatsiooniliste andmetega kui ka XMLiga ning samuti andmetele paindlikku ja
turvalist veebipõhist ligipääsu.
? Microsoft BizTalk Server – Võimaldab integreerida erinevaid süsteeme/rakendusi
? Microsoft Host Integration Server – Võimaldab integreerida erinevaid kasutaja
andmebaase ning selle abil astuda sammu lähemale Single-Sign-On ideele
? Microsoft Exchange Server – Pakub funktsionaalsust meilide ja uudiste
vahetamiseks seda nii PCde kui ka mobiilsete seadmete jaoks.
? Microsoft Application Server – Missioonikriitiliste veebisaitide haldamise vahend –
veebifarmid
? Microsoft Internet Security and Acceleration Server – Mitmetasemeline tulemüür
võrgu perimeetri kaitsmiseks, mis võib töötada ka veebipuhvrina.
? Microsoft Commerce Server – Raamistik programmidele koos analüüsi ja tagasiside
võimalustega
? Microsoft SharePoint Portal Server – Võimaldab luua veebiportaale koos
dokumendihalduse ja rühmatöö vahenditega
* Visual Studio .NET – Vahend .NET rakenduste loomiseks. Lisainfot Visual Studio kohta
saab Microsoft veebist http://msdn.microsoft.com/vstudio. Visual Studio 2005st on olemas
mitmeid versioone. Programmeerimistee alustamiseks ja katsetamiseks on loodud
spetsiaalsed tasuta versioonid Visual Studio Express näol. Express versioone on võimalik
tõmmata aadressilt http://msdn.microsoft.com/vstudio/express/default.aspx. Selle kursuse
raames on kasu kahest versioonist Visual Web Developer 2005 Express – ASP.NET 2.0
veebirakenduste loomiseks ja Visual C# Express Edition lihtsalt C# keeles
programmeerimise jaoks.
Selleks, et .NET platvormile loodud programme käivitada, on vaja .NET raamistikku. Juhul,
kui Teil seda arvutis veel ei ole, on seda võimalik tõmmata mitmelt veebilehelt, sh Windows
Update'st.
Programmeerimiskeeltena on vaikimisi võimalik kasutada (kompilaatorid sisalduvad .NET
raamistikus) C#, hallatud C++, VB.NET ja J# keeli. Lisaks nendele pakutakse erinevate
tootjate poolt veel ca 40 .NET keelt.
Microsoft on .NET raamistikust loonud juba mitmeid versioone. Selle materjali loomise
hetkeks lasti välja versioon 3.0. Hea uudis nende paljude versioonide juures on see, et igas
uues versioonis sisaldub ka vana e. midagi tuleb juurde, kuid kõik see, mis oli, töötab edasi!
C#
Sissejuhatus
Mõnigi võib ohata, et jälle üks uus programmeerimiskeel siia ilma välja mõeldud. Teine jälle
rõõmustab, et midagi uut ja huvitavat sünnib. Kolmas aga hakkas äsja veebilahendusi
kirjutama ja sai mõnegi ilusa näite lihtsasti kokku. Oma soovide arvutile selgemaks tegemise
juures läheb varsti vaja teada, "mis karul kõhus on", et oleks võimalik täpsemalt öelda, mida ja
kuidas masin tegema peaks. Loodetavasti on järgnevatel lehekülgedel kõigile siia sattunute
jaoks midagi sobivat. Mis liialt lihtne ja igav tundub, sellest saab kiiresti üle lapata. Mis
esimesel pilgul paistab arusaamatu, kuid siiski vajalik, seda tasub teist korda lugeda. Ning
polegi loota, et kõik kohe lennult külge jääks.
Selle jaoks on teksti sees koodinäited, mida saab kopeerida ja arvutis tööle panna. Ning
mõningase muutmise ja katsetamise peale avastada, mis mille jaoks on ning kuidas seda oma
kasuks rakendada saab. Töötav näide on üks hea kindel tugipunkt nagu üks suur puu
lagendikul, kuhu oskab alati tagasi minna. Juhul, kui muutmistega on õnnestunud oma
koodilõik nii sõlme keerata, et see sugugi enam töötada ei taha, saab alati võtta materjalist taas
algse töötava näite ning sealt juurest katsetama hakata.
Kes pikemalt mitmesuguseid rakendusi kirjutab, avastab mõne aja pärast, et samas keeles
kirjutatud programm võib vähemalt esmapilgul mõnevõrra erinev välja näha sõltuvalt sellest,
kas programm käivitatakse veebist, tegutsetakse nuppudega ja tekstiväljadega aknas,
väljundiks on mobiiltelefon või piirdub kogu tegevus tekstiaknaga. Esimesel korral võib
tunduda, et oleks nagu täiesti eri keeltes ja eri moodi kirjutamine. Ühes kohas on alati koodi
juures salapärane button1_click, teises public static void Main ning kolmandas veel
midagi muud. Aga sellest ei tasu ennast väga häirida lasta. Ehkki .NETi ja C# juures on
püütud eri kohtades käivituvate rakenduste loomist sarnasemaks muuta, tuleb siiski
kirjutamisel arvestada käivitumiskoha võimalustega. Siin materjalis keskendume C# keele
ülesehitusega seotud teemadele, mis on ühised kõigi käivitumiskohtade puhul. Ning
kasutajaliidesena pruugime programmeerimisõpikute traditsioonilist lihtsat ning väheste
(eksimis)võimalustega tekstiakent - nii jääb rohkem aega tähelepanu pühendada keele enese
konstruktsioonidele, mida siis edaspidi julgesti veebirakenduste juures ja soovi korral mujalgi
pruukida.
Põhivõimalused
Kui rakendus juba mingitki elumärki annab, on see tunduvalt rohkem, kui lihtsalt hulk koodi,
mis peaks "midagi arukat" tegema. Tunne, et suutsin programmi ise, omade roosade kätega
käima panna, on hea. Ja annab kindlustunde, et järgmisel korral saab asi ainult paremaks
minna. Kui käima on lükatud, siis edasi võib mõtelda juba juurde panemise peale. Nii nagu
talumees, kes omale krati oli ehitanud, sai hakata talle ülesandeid andma alles siis, kui kratt
hinge sisse võttis. Muul juhul on tegemist palja põhuhunnikuga, millele teivas sisse löödud ja
vanad kartulikorvid külge riputatud - olgu need nii suured ja vägevad tahes. Tööle
hakkamiseks on hinge vaja. Aga hinge ei saa enne sisse puhuda, kui väikegi tervik olemas.
Ning C# puhul näeb lühim tervikprogramm välja ligikaudu järgmine:
using System;
class Tervitus{
public static void Main(string[] arg){
Console.WriteLine("Tere");
}
}
Esmapilgul võib tunduda, et suur hulk tundmatuid sõnu on ekraanile ritta seatud. Ning veel
mõned sulud ka. Ning kui teada saada, et kõik see pikk jutt on vaid väikese programmi
loomiseks, mis oma töö tulemusena ütleb "Tere", siis võib paista, et tegemist on ilmse
raiskamisega. Eriti, kui mõni on varem tutvunud Basicu või Pythoniga, kus sama tulemuse
saamiseks tuleb lihtsalt kirjutada
print "Tere"
Seetõttu veel 2000ndate aastate alguses tutvustati inimesi programmeerimisega mitmeski
koolis just Basicu kaudu, sest algus on "nii lihtne". Ning kui programmid "väga suureks" - ehk
siis tuhandete ridade pikkuseks - ei kasva, võib julgesti lihtsa algusega keele juurde jäädagi.
Kõik vajalikud matemaatikaülesanded, kirjutamised ja arvutamised saavad tehtud.
Aga millegipärast on programmidel kombeks paisuda. Ning et suureks paisunud rakenduse
seest sobiv toiming üles otsida, peab lisama vajalikule käsule üha uusi ja uusi kesti. Nii nagu
üheainukese vajaliku sidekanali puhul võib korraldada nii, et telefonitoru tõstes ühendatakse
rääkija kohe õigesse kohta. Kümnekonna puhul piisab telefoninumbritest, mis telefoni külge
kleebitud või kiirklahvidele salvestatud. Kui aga raamatupidaja peab vajalikul hetkel kätte
saama kõik temaga viimastel aastatel suhelnud kliendid, siis läheb juba tarvis korralikumat
kataloogi.
Nõnda on C# ja ka mitme muu keele loojad otsustanud juba alguses programmikoodi osad
korralikult süstematiseerida, et poleks vaja piltlikult öeldes pärast kataloogi tehes nuputada,
millist värvi paberilipikule kirjutatud Mati telefoninumber just selle õige Mati oma on.
Mõnevõrra läheb tekst selle peale küll pikemaks, aga loodetavasti piisavalt vähe, et ikka julgete
C# võlusid tundma õppida.
C# kood jaotatakse üksteise sees olla võivatesse plokkidesse. Iga ploki ümber on looksulud.
Siinses näites on välimiseks plokiks klass nimega Tervitus ning tema sees alamprogramm
nimega Main. Plokke võib vahel tunduvalt rohkem olla. Omaette terviklikud toimingud
paigutatakse üldjuhul alamprogrammidesse. Nende sees võivad olla plokid tingimuste ja
korduste tarbeks. Klassi moodustab üheskoos toimivate või sarnaste alamprogrammide
komplekt (näiteks matemaatikafunktsioonid) - sellest pikemalt aga hiljem objektinduse juures.
Suuremate rakenduste juures jagatakse klassid veel omakorda nimeruumidesse. Nii on lootust
ka pikemate rakenduste puhul midagi hiljem koodist üles leida.
Mõned salapärased kohad on veel jäänud. Esimene rida
using System;
teatab, et nimeruumist System pärinevaid klasse saab kergesti kasutada - piisab vaid klassi
nimetamisest. C# standardpaketis leidub tuhandeid kasutamisvalmis klasse. Lisaks veel
loendamatu hulk lisapakette, mis muud programmeerijad on valmis kirjutanud. Kindlasti
leidub nende hulgas korduvaid klassinimesid - sest mõttekaid ja kõigile arusaadavaid
klassinimetusi lihtsalt ei saa olla nii palju. Kui aga samanimelised klassid on eri nimeruumides,
siis nad üksteist üldjuhul ei sega. Nii võib igaüks oma nimeruumis nimetada klasse kuidas
tahab. Ja using-lausega märgitakse, millist klasside komplekti parajasti kasutatakse. Edasi siis
class Tervitus{
mis praegusel juhul annab programmile ka nime ning
public static void Main(string[] arg){
näitab alamprogrammi Main, kust käsurearakendus oma tööd alustab. Muudest sõnadest siin
rea peal ei tasu end veel liialt häirida lasta. Kiirülevaatena: public näitab, et pole seatud
käivitusõiguse tõkkeid, static - et alamprogramm Main on olemas ja käivitusvalmis kohe
rakenduse töö alguses. void näitab, et ei raporteerita operatsioonisüsteemile programmi töö
edukuse kohta. Ja ümarsulgudes oleva abil saab mõnes rakenduses kasutaja omi andmeid ette
anda - siin seda võimalust aga ei kasutata.
Järgneb kasutajale nähtav toiming, ehk
Console.WriteLine("Tere");
Console klass asub nimeruumis System ja on üleval märgitud using lause tõttu kasutatav.
Klassi käsklus WriteLine lubab kirjutada konsoolile ehk tekstiekraanile. Praegu piirdutakse
ühe väikese teretusega. Jutumärgid on ümber selleks, et arvuti saaks aru, et tegemist on
tekstiga - mitte näiteks käskluse või muutuja (märksõna) alla salvestatud andmetega.
}
}
Kaks sulgu lõpus lõpetamas eespool avatud sulgusid. Iga sulg, mis programmikoodi sees
avaneb, peab ka kusagil lõppema - muidu ei saa arvuti asjast aru, hing ei tule sisse ja programm
ei hakka tööle. Tühikud ja reavahetused on üldjuhul vaid oma silmailu ja pildi korrastuse
pärast. Kompilaatori jaoks võiks kõik teksti rahumeeli ühte ritta jutti kirjutada, enesele kasvaks
aga selline programm varsti üle pea. Siin näites paistab, et alamprogramm Main'i sulg on sama
kaugel taandes kui alamprogrammi alustav rida ise. Ning klassi sulg on sama kaugel kui klassi
alustava rea sulg. Nõnda saab programmi pikemaks kasvamisel kergemini järge pidada, millises
plokis või millistes plokkides vaadatav käsk asub.
Nõnda on esimene väike programm üle vaadatud ja loodetavasti tekib natuke tuttav tunne, kui
vaadata järgnevaid ridu:
using System;
class Tervitus{
public static void Main(string[] arg){
Console.WriteLine("Tere");
}
}
Käivitamine
Edasi tuleb tervitusrakendus käima saada. Töö tulemusel tekkinud pildi võib
arenduskeskkonnas (näiteks Visual Studio 2008) ette saada kergesti - vajutad lihtsalt
käivitusnuppu ja midagi ilmubki. Kuidas rakendus paigaldada, seda saab lugeda C# osa
lõpust. Et aga "karu kõhust" tekiks parem ülevaade, püüame tervitaja käsurealt tööle saada.
Sealt paistab paremini välja, millised etapid enne läbi käiakse, kui töötava programmini
jõutakse. See on kasulik näiteks vigade otsimisel ja neist aru saamisel.
C# programmi kood salvestatakse laiendiga cs. Faili nime võib panna loodud programmi
klassiga samasuguseks, kuigi otsest kohustust ei ole. Aga nõnda on kataloogist omi programme
ehk kergem üles leida, kui klass ja fail on sama nimega.
Käsureale pääsemiseks on tarvilik see avada. Tavaliseks mooduseks on Start -> run ja sinna
sisse käsklus cmd. Et sealtkaudu kompilaatorile ligi pääseda, on lisaks vaja panna
otsinguteesse kompilaatori csc.exe kataloogi aadress, mis ühe konkreetse installatsiooni puhul
on näiteks C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727.
Lihtsam moodus on kasutada Microsoft .NET Framework SDK-ga kaasa tulevat SDK
Command Prompt'i, kus vastav seadistus juba paigas.
Edasi tuleb käsuaknaga liikuda kataloogi, kuhu programmikoodifail salvestatud sai. Siinpuhul
on selleks C ketta Projects kataloogi alamkataloogi oma alamkataloog näited. Käsureal
liikumiseks saab ketast valida käsuga, mis koosneb kettatähest ja temale järgnevast koolonist.
Kataloogides liikumiseks sobib käsklus cd. Nii et siin puhul tuleb ketta valimiseks kirjutada
C:
ning sealt seest kataloogi valimiseks
cd \Projects\oma\naited
Nõnda jõudsingi sobivasse asukohta. Kataloogi sisu nägemiseks sobib käsklus dir. Tulemus:
C:\Projects\oma\naited>dir
Volume in drive C has no label.
Volume Serial Number is E4C5-C191
Directory of C:\Projects\oma\naited
30.05.2006 10:52
.
30.05.2006 10:52 ..
30.05.2006 10:52 116 Tervitus.cs
1 File(s) 116 bytes
2 Dir(s) 69 704 065 024 bytes free
Siit paistab, et kataloogis on üks fail. Selle nimeks on Tervitus.cs ning pikkuseks 116 baiti.
Käivitamiseks tuleb meie loodud programmikoodiga tekstifail kõigepealt ära kompileerida.
Kompileerimise käigus tehakse käsklused masinale kergemini loetavamaks. C#
käsurearakenduse puhul on kompileerimise tulemuseks .exe - laiendiga fail, mis oma
käivitumiseks vajab, et .NET keskkond oleks masinas juba olemas. Kompileerimiseks
kirjutatakse kompilaatorprogrammi nimi (csc) ning programmikoodifaili nimi (praegu
Tervitus.cs). Kui programmikood on masinale arusaadavalt kirja pandud, veateateid ei anta
ning kataloogi tekib juurde käivitamisvalmis Tervitus.exe
C:\Projects\oma\naited>csc Tervitus.cs
Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.42
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.
Käima panekuks tuleb vaid selle .exe faili nimi kirjutada ning võimegi tulemust näha.
C:\Projects\oma\naited>Tervitus
Tere
Ülesandeid
* Muuda väljatrükitavat teksti
* Kirjuta ekraanile kaks rida (kaks järjestikust Console.WriteLine käsklust, kumbki omal real)
* Tekita programmi sisse väikeseid vigu, ja vaata, mis kompilaator selle peale teatab. Paranda
tagasi ja veendu, et programm töötab jälle. Näiteks kaota ära n-täht sõnast Console, üks
jutumärk "Tere" ümbert, üks sulg, semikoolon, muuda klassi nime. Ja igal korral jäta meelde
või soovi korral suisa märgi üles, kas ja milline viga oli tegelikult ning millise veateate andis
kompilaator. Sellised on kõige tüüpilisemad kompileerimisel tekkivad vead. Kord väikese
programmi juures läbi katsetatuna on kergem sellistest veateadetest ka suures rakenduses aru
saada.
Suhtlus arvutiga
Enamik programme vajavad andmeid kasutajalt - muul juhul ei teaks ju arvuti, mida meil vaja
on. Kui just programmi ainsaks ülesandeks polegi kellaaja teatamine, sest sellisel juhul tõesti
piisab vaid programmi enese käivitamisest.
Sisendite võimalused sõltuvad programmi kasutuskohast. Veebis näiteks saame pruukida
veebilehel töötavaid graafikakomponente - nuppe, tekstivälju, rippmenüüsid jne. Windows
Formide ehk arvutis iseseisvalt (mitte veebi kaudu) töötavate graafiliste rakenduste puhul on
tavalised graafikakomponendid suhteliselt sarnased veebis nähtavatega. Vaid mõnevõrra
vabamalt pääseb oma komponente juurde tegema ning mitmekülgsemaid võimalusi kasutama.
Tekstiakna rakenduste juures piirdub suhtlus arvutiga loetava ja trükitava tekstiga. Lihtsaim
dialoogi pidav programm näeb välja järgmine:
using System;
class Sisend{
public static void Main(string[] arg){
Console.WriteLine("Palun eesnimi:");
string eesnimi=Console.ReadLine();
Console.WriteLine("Tere, "+eesnimi);
}
}
Ning töö paistab välja nii:
C:\Projects\oma\naited>Sisend
Palun eesnimi:
Juku
Tere, Juku
Esmane "Palun eesnimi" trükitakse välja sarnaselt nagu lihtsaimaski tervitavas programmis.
Edasine Console.ReadLine() jääb kasutajalt sisestust ootama. Kõik, mis kasutaja kuni
reavahetuseni kirjutab, püütakse kokku üheks tekstiks ning selle saab arvutisse meelde jätta.
Märksõnaks ehk muutuja nimeks sai "eesnimi" ning andmetüübiks "string", mis inimkeeli
tähendab teksti. Järgmisel real trükitakse tulemus välja. Nõnda sõltub programmi vastus
küsimise peale sisestatavast nimest.
Arvutamine
Arvutamine teadupärast arvuti põhitöö - vähemalt arvutustehnika algaastatel. Et siin lahkesti
kasutaja antud arve liita/lahutada saaks, tuleb kõigepealt hoolitseda, et need ka arvuti jaoks
arvud ja mitte sümbolite jadad oleksid. Kõigepealt annab ReadLine kätte numbriliste
sümbolitega teksti. Ning käsklus int.Parse muudab selle arvutuste jaoks kõlbulikuks. Tüüp
int (sõnast integer) tähistab täisarvu. Kui on vaja komakohtadega ümber käia, siis sobib
selleks tüüp double. Teise arvu puhul on andmete lugemine ning arvuks muundamine ühte
käsklusesse kokku pandud. Nii võib ka.
Väljatrüki juures näete kolme looksulgudesse paigutatud arvu. Nõnda on võimalik
andmeid trükkides algul määrata ära trükkimise kohad ning alles pärast loetellu kirjutada
tegelikud väärtused. Juhul, kui väärtuste arvutamine on pikk (näiteks arv1*arv2), aitab see
programmikoodi pilti selgemana hoida. Muul juhul tuleks hulk pluss- ja jutumärke väljatrüki
juurde. Jutumärgid tekstide eristamiseks ning plussmärgid üksikute osade kokku liitmiseks.
Samuti on sellisest asukohanumbritega paigutamisest kasu juhul, kui rakendust tõlgitakse.
Keele lauseehituste tõttu võib sõnade järjestus lauses muutuda. Selliselt looksulgude vahel
olevate arvudega mängides aga saab lihtsamalt tõlkida ilma, et peaks selleks programmikoodis
märgatavaid muutusi tegema.
using System;
class Arvutus{
public static void Main(string[] arg){
Console.WriteLine("Esimene arv:");
string tekst1=Console.ReadLine();
int arv1=int.Parse(tekst1);
Console.WriteLine("Teine arv:");
int arv2=int.Parse(Console.ReadLine());
Console.WriteLine("Arvude {0} ja {1} korrutis on {2}",
arv1, arv2, arv1*arv2);
}
}
C:\Projects\oma\naited>Arvutus
Esimene arv:
3
Teine arv:
5
Arvude 3 ja 5 korrutis on 15
Ülesandeid
* Küsi kahe inimese nimed ning teata, et täna on nad pinginaabrid
* Küsi ristkülikukujulise toa seinte pikkused ning arvuta põranda pindala
* Leia 30% hinnasoodustusega hinna põhjal alghind
Valikud
Ehk võimalus otsustamiseks, kui on vaja, et programm käituks kord üht-, kord teistmoodi.
Allpoololev näide koos väljundiga võiks näidata, kuidas tingimuslause abil tehtud valik
toimib.
using System;
public class Valik1{
public static void Main(string[] arg){
Console.WriteLine("Palun nimi:");
string eesnimi=Console.ReadLine();
if(eesnimi=="Mari"){
Console.WriteLine("Tule homme minu juurde!");
} else {
Console.WriteLine("Mind pole homme kodus.");
}
}
}
Väljund:
D:\kodu\0606\opikc#>Valik1
Palun nimi:
Juku
Mind pole homme kodus.
Nagu näha - Jukut külla ei kutsutud. C# juures, nii nagu selle aluseks oleva C-keele puhul
kasutatakse võrdlemise juures kahte võrdusmärki. Üks võrdusmärk on omistamine ehk
kopeerimine. Arvude puhul saab kasutada ka võrdlusi < ja > ehk suurem kui ja väiksem kui.
Näiteks
if(vanus>14){
Console.WriteLine("Tuleb osta täispilet");
}
Samuti kehtivad võrdlused >= ja <= ehk suurem või võrdne ning väiksem või võrdne. Kui
uurida, kas arv jääb soovitud vahemikku, tuleb järjest panna kaks võrdlust. Näiteks:
if(vanus>6 && vanus <=14){
Console.WriteLine("Sinu jaoks on lapsepilet");
}
Kaks &-märki tähendab, et kogu tingimus on tõene ainult siis, kui mõlemad võrdlused on
tõesed. Teistpidi saab ka.
if(vanus<7 || vanus>14){
Console.WriteLine("Sulle lapsepilet ei sobi");
}
&&märke saab lugeda sõnaga "ja", || märke sõnaga "või". Ehk siis: kui vanus on alla seitsme
või suurem neljateistkümnest, sel juhul lapsepilet ei sobi.
Kommentaarid
Vahel on kasulik enesele ja teistele selgituseks programmi sisse kommentaare lisada. Teksti
sisse kirjutades pannakse kommentaari ette kaks kaldkriipsu. Selliselt kirjutatud teksti pole
kompilaatori jaoks olemas. Enesel on aga vahel päris hea meenutada, mida mõne lausega kirja
panna taheti.
double summa=kogus*pirnihind; //korrutatakse ühe pirni hinnaga
Kommentaar võib olla ka üle mitme rea. Sellisel juhul pannakse kommentaari algusesse /*
ning lõppu */ Siin on näiteks programmi väljund sarnaselt kaldkriips-tärni ning tärn-
kaldkriipsu vahele paigutatud - et ka ilma käivitamata võib juba näha, mis konkreetsel juhul
tehti.
Lisaks märkmete jätmisele on kommenteerimine ka heaks võimaluseks programmi testimisel ja
vigade otsimisel. Kui kuidagi viga üles ei leia, siis saab algul kahtlase lause või lausete ploki
vahel tervikuna välja kommenteerida. Seejärel veenduda, et programm ilma selle osata ilusti
töötab. Ning edasi võib asuda juba kindlama tundega sellest väljakommenteeritud plokist viga
otsima teades, et ta kindlasti seal peab olema. Omakorda saab lauseid ükshaaval võtta
kommenteeritud osast välja ja lõpuks nõnda kõik ilusti tööle saada. Aga nüüd näide ise.
using System;
public class Valik1{
public static void Main(string[] arg){
double pirnihind=1.70;
Console.WriteLine("Mitu pirni ostad?");
double kogus=double.Parse(Console.ReadLine());
double summa=kogus*pirnihind; //korrutatakse ühe pirni hinnaga
Console.WriteLine("Kas kilekotti ka soovid? (jah/ei)");
string vastus=Console.ReadLine();
if(vastus=="jah"){
summa=summa+0.70;
}
Console.WriteLine("Kogusumma: "+summa);
}
}
/*
D:\kodu\0606\opikc#>Valik2
Mitu pirni ostad?
3
Kas kilekotti ka soovid? (jah/ei)
jah
Kogusumma: 5,8
*/
Kui soovida üksikutest osadest summat kokku arvutada nagu ülal näites, siis on küllalt
tavaline, et iga küsimuse peal otsustatakse, kas ja mida selle juures teha. Siin nagu näha - juhul
kui kilekott ostetakse lisaks, siis pannakse hinnale 70 senti juurde.
summa=summa+0.70
tähendab just seda.
Ülesandeid
* Küsi temperatuur ning teata, kas see on üle kaheksateistkümne kraadi (soovitav toasoojus
talvel).
* Küsi inimese pikkus ning teata, kas ta on lühike, keskmine või pikk (piirid pane ise paika)
* Küsi inimeselt pikkus ja sugu ning teata, kas ta on lühike, keskmine või pikk (mitu
tingimusplokki võib olla üksteise sees).
* Küsi inimeselt poes eraldi kas ta soovib osta piima, saia, leiba. Löö hinnad kokku ning teata,
mis kõik ostetud kraam maksma läheb.
Kordused
Arvutist on enamjaolt kasu siis, kui ta meie eest mõned sellised tööd ära teeb, kus enesel
tarvis ükshaaval ja üksluiselt sama toimetust ette võtta. Ühest hinnast paarikümne protsendi
maha arvutamisega saab ka käsitsi mõne ajaga hakkama. Kui aga hindu on mitukümmend, siis
on päris hea meel, kui arvuti selle töö meie eest ära teeb. Järgnevalt näide, kuidas arvuti vastu
tulevale viiele matkajale tere ütleb. Täisarvuline muutuja nimega nr näitab, mitmenda matkaja
juures parajasti ollakse. Käskluse while juurde kuuluvat plokki saab korrata. Plokk läbitakse
üha uuesti juhul, kui ümarsulgudes olev tingimus on tõene. Et kordusi soovitud arv saaks, on
juba programmeerija hoolitseda. Selleks on siin igal korral pärast tervitust käsklus nr=nr+1,
ehk siis suurendatakse matkaja järjekorranumbrit. Kui see suurendamine kogemata tegemata
jääks, siis jääkski arvuti igavesti esimest matkajat teretama (proovi järele). Nüüd aga töötav
korduste näide:
using System;
public class Kordus1{
public static void Main(string[] arg){
int nr=1;
while(nr<=5){
Console.WriteLine("Tere, {0}. matkaja!", nr);
nr=nr+1;
}
}
}
Ning tema väljund:
D:\kodu\0606\opikc#>Kordus1
Tere, 1. matkaja!
Tere, 2. matkaja!
Tere, 3. matkaja!
Tere, 4. matkaja!
Tere, 5. matkaja!
Heal lapsel mitu nime. Ehk ka korduste kirja panekuks on mitu moodust välja mõeldud. Algul
näidatud while-tsükkel on kõige universaalsem, selle abil on võimalik iga liiki kordusi kokku
panna. Sama tulemuse aga saab mõnevõrra lühemalt kirja panna for-i abil. Levinud
kordusskeemi jaoks on välja mõeldud omaette käsklus, kus algul luuakse muutuja ja antakse
talle algväärtus; seejärel kontrollitakse, kas järgnevat plokki on vaja täita; lõpuks võetakse ette
toiming andmete ettevalmistamiseks uue ploki jaoks. nr++ on sama, mis nr=nr+1 - ehk siis
suurendatakse muutuja väärtust ühe võrra. Näha programminäide:
using System;
public class Kordus2{
public static void Main(string[] arg){
for(int nr=1; nr<=5; nr++){
Console.WriteLine("Tere, {0}. matkaja!", nr);
}
}
}
ning tema väljund:
D:\kodu\0606\opikc#>Kordus2
Tere, 1. matkaja!
Tere, 2. matkaja!
Tere, 3. matkaja!
Tere, 4. matkaja!
Tere, 5. matkaja!
Järelkontroll
Nii for-i kui while puhul kontrollitakse alati ploki algul, kas seda on vaja täita. Ning kui
matkajate arv poleks mitte 5, vaid hoopis 0, siis sellisel juhul ei täideta plokki ainsatki korda,
ehk kõik tered jäävad ütlemata. Mõnikord on aga teada, et plokk tuleb kindlasti läbida.
Lihtsalt pole teada, kas sama teed tuleb ka teist või kolmandat korda käia. Tüüpiline näide
selle juures on sisestuskontroll. Kui esimene kord toiming õnnestus, pole vaja kasutajalt
andmeid uuesti pärida. Juhtus aga äpardus, siis tuleb senikaua jätkata, kuni kõik korras on. Siin
küsitakse jälle tunninäitu. Sattus see arusaadavase vahemikku, minnakse rahus uue väärtusega
edasi. Kui aga midagi ebaõnnestus, siis on arvuti väga järjekindel uuesti ja uuesti küsima
lootuses, et kunagi ka midagi temale sobilikku jagatakse.
using System;
public class Kordus3{
public static void Main(string[] arg){
int tund;
do{
Console.WriteLine("Sisesta tund vahemikus 0-23");
tund=int.Parse(Console.ReadLine());
} while(tund<0 || tund>23);
Console.WriteLine("Tubli, sisestasid {0}.", tund);
}
}
/*
D:\kodu\0606\opikc#>Kordus3
Sisesta tund vahemikus 0-23
32
Sisesta tund vahemikus 0-23
11
Tubli, sisestasid 11.
*/
Ülesandeid
* Trüki arvude ruudud ühest kahekümneni
* Küsi kasutajalt viis arvu ning väljasta nende summa
* Ütle kasutajale "Osta elevant ära!". Senikaua korda küsimust, kuni kasutaja lõpuks ise
kirjutab "elevant".
Korrutustabel
... ehk näide, kuidas eelnevalt vaadatud tarkused ühe programmi sisse kokku panna ning mis
selle peale ka midagi tarvilikku teeb.
Algul on näha, kuidas otse programmi käivitamise juures ka mõned andmed sinna kätte anda.
Et kui kirjutan
Korrutustabel 4 5
siis saadakse sellest aru, et soovin korrutustabelit nelja rea ja viie veeruga. Nende käsurea
parameetrite püüdmiseks on alamprogramm Main-il ümarsulgudes koht string[]
argumendid. Kõik käsureale kirjutatud sõnad (ka üksik number on arvuti jaoks sõna) pannakse
sinna argumentide massiivi ehk jadasse, kust neid järjekorranumbri järgi kätte saab.
Andmetüüp string[] tähendabki, et tegemist on stringide ehk sõnede ehk tekstide
massiiviga. Kirjutades massiivi järgi .Length, saab teada, mitu elementi selles massiivis on -
mis praegusel juhul on võrdne lisatud sõnade arvuga käsureal. Kõik sõnad saab ka ükshaaval
järjekorranumbri järgi kätte. Arvestama peab ainult, et sõnu hakatakse lugema numbrist 0. Nii
et kui eeldatakse, et tegemist on kahe parameetriga, siis nende kättesaamiseks peame ette
andma numbrid null ja üks.
Nagu tingimusest on näha: juhul kui argumente pole täpselt kaks, siis kasutatakse vaikimisi
ridade ja veergude arvu ning joonistatakse korrutustabel suurusega 10 korda 10.
Tabeli trükkimiseks on kaks for-tsüklit paigutatud üksteise sisse. Selles pole midagi imelikku -
iga rea juures trükitakse kõik veerud esimesest kuni viimaseni. Ning selleks, et erinevate
numbrite arvuga arvud meie tabelit sassi ei lööks, on väljatrüki juurde vorminguks kirjutatud
{0, 5}. Ainsat Console.Write argumenti (järjekorranumbriga 0) trükitakse nõnda, et ta
võtaks alati viis kohta.
using System;
class Korrutustabel{
public static void Main(string[] argumendid){
int ridadearv=10, veergudearv=10;
if(argumendid.Length==2){
ridadearv=int.Parse(argumendid[0]);
veergudearv=int.Parse(argumendid[1]);
}
for(int rida=1; rida<=ridadearv; rida++){
for(int veerg=1; veerg<=veergudearv; veerg++){
Console.Write("{0, 5}", rida*veerg); //5 kohta
}
Console.WriteLine();
}
}
}
/*
C:\Projects\oma\naited>Korrutustabel
1 2 3 4 5 6 7 8 9 10
2 4 6 8 10 12 14 16 18 20
3 6 9 12 15 18 21 24 27 30
4 8 12 16 20 24 28 32 36 40
5 10 15 20 25 30 35 40 45 50
6 12 18 24 30 36 42 48 54 60
7 14 21 28 35 42 49 56 63 70
8 16 24 32 40 48 56 64 72 80
9 18 27 36 45 54 63 72 81 90
10 20 30 40 50 60 70 80 90 100
C:\Projects\oma\naited>Korrutustabel 4 5
1 2 3 4 5
2 4 6 8 10
3 6 9 12 15
4 8 12 16 20
*/
Alamprogramm
Nii nagu algul kirjas, nii ka siin tasub meeles pidada, et programmid armastavad paisuda ja
paisuda. Seepärast tuleb leida mooduseid, kuidas üha suuremaks kasvavas koodihulgas
orienteeruda. Alamprogramm on esmane ja hea vahend koodi sisse uppumise vältimiseks.
Lisaks võimaldab ta terviklikke tegevusi eraldi ning mitu korda välja kutsuda. Samuti on ühe
alamprogrammi tööd küllalt hea testida. Järgnevalt võimalikult lihtne näide, kuidas omaette
tegevuse saab alamprogrammiks välja tuua. Siin on selliseks tegevuseks korrutamine. Luuakse
käsklus nimega Korruta, talle antakse ette kaks täisarvu nimedega arv1 ja arv2 ning välja
oodatakse sealt ka tulema täisarv.
using System;
class Alamprogramm{
static int Korruta(int arv1, int arv2){
return arv1*arv2;
}
public static void Main(string[] arg){
int a=4;
int b=6;
Console.WriteLine("{0} korda {1} on {2}", a, b, Korruta(a, b));
Console.WriteLine(Korruta(3, 5));
}
}
/*
C:\Projects\oma\naited>Alamprogramm
4 korda 6 on 24
15
*/
Ülesandeid
* Koosta alamprogramm kahe arvu keskmise leidmiseks
* Koosta alamprogramm etteantud arvu tärnide väljatrükiks. Katseta.
* Küsi inimeselt kolm arvu. Iga arvu puhul joonista vastav kogus tärne ekraanile
Massiivid
Kuna arvuti on mõeldud suure hulga andmetega ümber käimiseks, siis on
programmeerimiskeelte juurde mõeldud ka vahendid nende andmehulkadega toimetamiseks.
Kõige lihtsam ja levinum neist on massiiv. Iga elemendi poole saab tema järjekorranumbri abil
pöörduda. Algul tuleb määrata, millisest tüübist andmeid massiivi pannakse ning mitu kohta
elementide jaoks massiivis on. Järgnevas näites tehakse massiiv kolme täisarvu hoidmiseks.
Kusjuures nagu C-programmeerimiskeele sugulastele kombeks on, hakatakse elemente lugema
nullist. Nii et kolme massiivielemendi puhul on nende järjekorranumbrid 0, 1 ja 2. Tahtes
väärtusi sisse kirjutada või massiivist lugeda, tuleb selleks kirja panna massiivi nimi (praeguse
juhul m) ning selle taha kandiliste sulgude sisse järjekorranumber, millise elemendiga suhelda
tahetakse.
using System;
class Massiiv1{
public static void Main(string[] arg){
int[] m=new int[3];
m[0]=40;
m[1]=48;
m[2]=33;
Console.WriteLine(m[1]);
}
}
/*
C:\Projects\oma\naited>Massiiv1
48
*/
Tsükkel andmete kasutamiseks
Massiivi kõikide elementidega kiiresti suhtlemisel aitab tsükkel. Siin näide, kuidas arvutatakse
massiivi elementidest summa. Algul võetakse üks abimuutuja nulliks ning siis liidetakse
kõikide massiivi elementide väärtused sellele muutujale juurde. Avaldis summa+=m[i] on
pikalt lahti kirjutatuna summa=summa+m[i] ning tähendab just olemasolevale väärtusele otsa
liitmist. for-tsükli juures kõigepealt võetakse loendur (sageli kasutatakse tähte i) algul nulliks,
sest nullist hakatakse massiivi elemente lugema. Jätkamistingimuses kontrollitakse, et on veel
läbi käimata elemente ehk loendur on väiksem kui massiivi elementide arv
(massiivinimi.Length). Pärast iga sammu suurendatakse loendurit (i++). Nõnda ongi
summa käes.
using System;
class Massiiv2{
public static void Main(string[] arg){
int[] m=new int[3];
m[0]=40;
m[1]=48;
m[2]=33;
int summa=0;
for(int i=0; iMassiiv2
121
*/
Massiiv ja alamprogramm
Nagu ennist kirjutatud, saab eraldiseisva toimingu kergesti omaette alamprogrammi tuua. Siin
on nõnda eraldatud summa leidmine. Massiive saab alamprogrammile samuti ette anda nagu
tavalisi muutujaid. Lihtsalt andmetüübi taga on kirjas massiivi tunnusena kandilised sulud.
using System;
class Massiiv3{
static int LeiaSumma(int[] mas){
int summa=0;
for(int i=0; iMassiiv3
121
*/
Algväärtustamine, järjestamine
Kui massiivi elementide väärtused on kohe massiivi loomise ajal teada, siis saab nad
loogelistes sulgudes komadega eraldatult kohe sisse kirjutada. Nii saab andmed lihtsalt
lühemalt kirja panna.
Kui elemendid on lihtsa võrdlemise teel järjestatavad nagu näiteks täisarvud, siis piisab nende
rittaseadmiseks klassi Array ainsast käsust Sort.
using System;
class Massiiv4{
public static void Main(string[] arg){
int[] m=new int[3]{40, 48, 33};
Array.Sort(m);
for(int i=0; iMassiiv4
33
40
48
*/
Osutid ja koopiad
Kui ühe hariliku täisarvulise muutuja väärtus omistada teisele, siis mõlemas muutujas on
koopia samast väärtusest ning toimingud ühe muutujaga teise väärtust ei mõjuta. Massiividega
ning tulevikus ka muude objektidega tuleb tähelepanelikum olla. Kui üks massiiv omistada
teisele, siis tegelikult kopeeritakse vaid massiivi osuti, mõlema muutuja kaudu pääsetakse ligi
tegelikult samadele andmetele. Nagu järgnevas näites: massiivid m2 ja m näitavad samadele
andmetele. Kui ühe muutuja kaudu andmeid muuta, siis muutuvad ka teise muutuja kaudu
nähtavad andmed nagu väljatrüki juures paistab. Algselt on massiivi m ja m2 elemendid 40, 48,
33. Pärast massiivi m elemendi number 1 muutmist 32ks, on ka massiivi m2 elemendid
muutunud - väärtusteks 40, 32, 33. Nõnda on suurte andmemassiivide juures teise muutuja
tegemine andmete juurde pääsemiseks arvuti jaoks kerge ülesanne. Samas aga peab vaatama,
et vajalikke andmeid kogemata ettevaatamatult ei muudaks.
int[] m=new int[3]{40, 48, 33};
int[] m2=m; //Viide samale massiivile
Tryki(m2);
m[1]=32;
Tryki(m2);
Kui soovida, et kaks algsetest andmetest pärit massiivi on üksteisest sõltumatud, siis tuleb teha
algsest massiivist koopia (kloon).
int[] m3=(int[])m.Clone(); //Andmete koopia
m[1]=20;
Tryki(m3);
Pärast kloonimist muutused massiiviga m enam massiivi m3 väärtusi ei mõjuta.
Soovides massiivi tühjendada, aitab klassi Array käsklus Clear, mis täisarvude puhul kirjutab
etteantud vahemikus (ehk praegusel juhul kogupikkuses, 0 ja Length-i vahel) täisarvude puhul
väärtusteks nullid.
Array.Clear(m3, 0, m3.Length); //Tühjendus
Massiivist andmete otsimiseks sobib käsklus IndexOf. Soovitud elemendi leidumise korral
väljastatakse selle elemendi järjekorranumber. Otsitava puudumisel aga -1.
Console.WriteLine(Array.IndexOf(m,33));
Console.WriteLine(Array.IndexOf(m,17)); //puuduv element
using System;
class Massiiv5{
static void Tryki(int[] mas){
for(int i=0; iMassiiv5
40
48
33
40
32
33
40
32
33
0
0
0
2
-1
*/
Massiiv alamprogrammi parameetrina
Massiivimuutuja omistamisel tekib võimalus kahe muutuja kaudu samadele andmetele ligi
pääseda. See võimaldab luua alamprogramme, mis massiivi elementidega midagi peale
hakkavad. Eelnevalt vaadeldud käsklus Sort tõstab massiivis elemendid kasvavasse
järjekorda. Siin on näha omatehtud alamprogramm KorrutaKahega, mis massiivi kõikide
elementide väärtused kahekordseks suurendab.
using System;
class Massiiv6{
static void KorrutaKahega(int[] mas){
for(int i=0; iMassiiv6
80
96
66
*/
foreach - tsükkel
Kui on vaja kogumi kõik elemendid läbi käia, kuid samas pole tähtis läbikäigu järjekord, siis
sellisel puhul aitab all näites paistev foreach-tsükkel. Selle abi saab näiteks summa arvutamise
juures pruukida.
using System;
class Massiiv7{
public static void Main(string[] arg){
int[] m=new int[3]{40, 48, 33};
foreach(int arv in m){
Console.WriteLine(arv);
}
}
}
/*
C:\Projects\oma\naited>Massiiv7
40
48
33
*/
Mitmemõõtmeline massiiv
Massiivis võib mõõtmeid olla märgatavalt rohkem kui üks. Kahemõõtmelist massiivi saab ette
kujutada tabelina, milles on read ja veerud. Kolmemõõtmelise massiivi elemendid oleksid nagu
tükid kuubis, mille asukoha saab määrata pikkuse, laiuse ja kõrguse kaudu. Harva läheb vaja
enam kui kolme mõõdet - siis on sageli juba otstarbekam ja arusaadavam oma andmestruktuur
ehitada. Kasutamine toimub aga nii, nagu allpool näites näha. Massiivi elementidega saab
ümber käia nagu tavaliste muutujatega. foreach-tsükliga saab soovi korral läbi käia ka
mitmemõõtmelise massiivi kõik elemendid.
using System;
class Massiiv8{
public static void Main(string[] arg){
int[,] m=new int[2,3]{
{40, 48, 33},
{17, 23, 36}
};
Console.WriteLine(m[0, 1]); //48
Console.WriteLine("M66dete arv: "+m.Rank);
Console.WriteLine("Ridade arv: "+m.GetLength(0));
Console.WriteLine("Veergude arv: "+m.GetLength(1));
//elemente mõõtmes nr. 1
int summa=0;
foreach(int arv in m){
summa+=arv;
}
Console.WriteLine("Summa: "+summa);
}
}
/*
C:\Projects\oma\naited>Massiiv8
48
M66dete arv: 2
Ridade arv: 2
Veergude arv: 3
Summa: 197
*/
Ülesandeid
* Küsi kasutaja käest viis arvu ning väljasta need tagurpidises järjekorras.
* Loo alamprogramm massiivi väärtuste aritmeetilise keskmise leidmiseks. Katseta.
* Loo alamprogramm, mis suurendab kõiki massiivi elemente ühe võrra. Katseta.
* Sorteeri massiiv ning väljasta selle keskmine element.
* Koosta kahemõõtmeline massiiv ning täida korrutustabeli väärtustega. Küsi massiivist
kontrollimiseks väärtusi.
Käsud mitmes failis
Suuremate programmide puhul on täiesti loomulik, et kood jagatakse mitme faili vahel. Nii on
hea jaotuse puhul kergem orienteeruda. Samuti on mugav terviklike tegevuste plokke
muudesse programmidesse ümber tõsta, kui see peaks vajalikuks osutuma. Siin näites on kaks
lihtsat arvutustehet omaette abivahendite klassis välja toodud.
class Abivahendid{
public static int korruta(int a, int b){
return a*b;
}
public static int liida(int a, int b){
return a+b;
}
}
Abivahendeid kasutav alamprogramm asub aga hoopis eraldi klassis ja failis.
using System;
class Abivahendiproov{
public static void Main(string[] arg){
Console.WriteLine(Abivahendid.korruta(3, 6));
}
}
Kui tahta, et kompilaator vajalikud osad üles leiaks, tuleb kompileerimisel ette anda kõikide
vajaminevate failide nimed.
C:\Projects\oma\naited>csc Abivahendid.cs Abivahendiproov.cs
Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.42
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.
Lõpliku, exe-faili nimi määratakse vaikimisi faili järgi, kus oli kirjas Main-meetod.
C:\Projects\oma\naited>Abivahendiproov
18
Ülesandeid
* Lisa käsklus täisarvude astendamiseks tsükli abil. Katseta
* Lisa kolmas fail paari tärnidest kujundeid joonistava funktsiooniga. Katseta peaprogrammis
mõlema abifaili funktsioonide väljakutseid.
Tekst
Teksti koostamise, analüüsimise ja muutmisega puutuvad kokku enamik arvutiprogramme.
Järgnevalt mõned näited, mille põhjal saab enamiku vajalikke tekstiga seotud toimetusi korda.
Teksti pikkuse saab kätte muutujast Length. Loetakse kokku kõik tekstis leiduvad sümbolid,
kaasaarvatud tühikud.
Tekstist lõigu eraldamiseks sobib Substring. Esimese parameetrina olev arv näitab,
mitmendast tähest hakatakse andmeid võtma, teine näitab, mitu tähte võetakse. String ehk
sõne algab tähega number 0. Nii lugedes ongi sõna "tuli" algustäht järjekorranumbriga 5.
IndexOf võimaldab tekstis tähte või sõna leida. Leidmise korral väljastatakse leitud
jupi algustähe järjekorranumber. Otsitava puudumise tulemusena väljastatakse -1.
using System;
class Tekst1{
public static void Main(string[] arg){
string s="Juku tuli kooli";
Console.WriteLine("Pikkus: "+s.Length);
Console.WriteLine(s.Substring(5, 4));
Console.WriteLine("'tuli' kohal "+s.IndexOf("tuli"));
}
}
/*
C:\Projects\oma\naited>Tekst1
Pikkus: 15
tuli
'tuli' kohal 5
*/
Muutmine
Teksti muutmiseks sobivad käsud Insert ja Remove. Esimene lisab soovitud kohale juurde
etteantud teksti. Lausest "Juku tuli kooli" sai üheksandale kohale sõna " vara" lisamisel lause
"Juku tuli vara kooli". Remove võimaldab sobivast kohast tähti välja võtta. Tehniliselt vaadates
käsud Insert ja Remove ei muuda algses muutujas olevat teksti, vaid luuakse uus tekstiplokk
mälus, mille poole on võimalik muutuja kaudu pöörduda. Muutujas s on endiselt "Juku tuli
kooli". Vaid s2 ja s3 on uute väärtustega.
Tekstiga ümber käimiseks on ka mitukümmend muud käsklust, millega lähemat tutvust teha
saab veebiaadressilt http://msdn2.microsoft.com/en-us/library/system.string_methods.aspx
Levinumatest on siin näha StartsWith alguse kontrolliks, IndexOf teksti otsinguks ja
Replace asendamiseks. Aja jooksul läheb aga vaja tõenäoliselt meetodit Compare järjestuse
kindlaks tegemisel, EndsWith lõpu kontrollimisel, Trim tühikute eemaldamisel, ToUpper ja
ToLower suur- ja väiketähtedeks muutmisel ning ToCharArray juhul, kui soovitakse tervikliku
teksti asemel tähemassiivi kasutada (näiteks tähtede massiliseks ümbertõstmiseks).
using System;
class Tekst2{
public static void Main(string[] arg){
string s="Juku tuli kooli";
Console.WriteLine("Pikkus: "+s.Length);
Console.WriteLine(s.Substring(5, 4));
Console.WriteLine("'tuli' kohal "+s.IndexOf("tuli"));
string s2=s.Insert(9, " vara");
Console.WriteLine(s2);
string s3=s.Remove(5, 5); //Kuuendast alates viis tähte
Console.WriteLine(s3);
if(s.StartsWith("Juku")){
Console.WriteLine("Algab Jukuga");
}
if(s.IndexOf("kala")==-1){
Console.WriteLine("Siin ei ole kala");
}
Console.WriteLine(s.Replace("tuli", "jooksis"));
}
}
/*
C:\Projects\oma\naited>Tekst2
Pikkus: 15
tuli
'tuli' kohal 5
Juku tuli vara kooli
Juku kooli
Algab Jukuga
Siin ei ole kala
Juku jooksis kooli
*/
Tükeldamine
Pika teksti osadeks jaotamiseks on mitmetes keeltes olemas vastavad käsud ja objektid. Nii ka
siin. Käsuga Split võib olemasoleva teksti määratud sümbolite koha pealt juppideks lõigata.
Kõikidest üksikutest tükkidest moodustatakse massiiv. Siin näiteks on eraldajaks koma, mis
tähemassiivi üksiku elemendina ette antakse. Aga nagu aimata võib, saab massiivi lisada ka
rohkem eraldajaid.
Kui on põhjust massiiv uuesti üheks pikaks tekstiks kokku panna, siis selle tarbeks leiab
käskluse Join.
using System;
class Tekst3{
public static void Main(string[] arg){
string s="Tallinn,Tartu,Narva";
string[] linnad=s.Split(new char[]{','});
foreach(string linn in linnad){
Console.WriteLine(linn);
}
Console.WriteLine(String.Join("; ", linnad));
}
}
/*
D:\kodu\0606\opikc#>Tekst3
Tallinn
Tartu
Narva
Tallinn; Tartu; Narva
*/
Ülesandeid
* Trüki inimese nime eelviimane täht
* Teata, kas sisestatud nimi algab A-ga
* Trüki sisestatud nimi välja suurtähtedega
* Teata, kas lauses leidub sõna "ja"
* Asenda olemasolu korral "ja" sõnaga "ning" ja teata asendusest
* Trüki välja lause kõige pikem sõna
Tekstifailid
Kui samu lähteandmeid on vaja korduvalt kasutada, siis on neid igakordse sissetoksimise
asemel mugavam sisse lugeda failist. Samuti on suuremate andmemahtude korral hea, kui
sisendi ja väljundi andmed jäävad alles ka masina väljalülitamise järel. Keerukama struktuuriga
andmete ning mitme üheaegse kasutaja korral (näiteks veebirakendustes) kasutatakse enamasti
andmebaasi. Käsurea- või iseseisva graafilise rakenduse puhul on tekstifail aga lihtne ja mugav
moodus andmete hoidmiseks. Samuti on ta tarvilik juhul, kui juba pruugitakse eelnevalt
tekstifaili kujul olevaid andmeid.
Kirjutamine
Andmete faili saatmiseks tuleb kõigepealt moodustada voog etteantud nimega faili
kirjutamiseks. Edasine trükk toimub juba sarnaselt ekraaniväljundiga Write ning WriteLine
käskude abil. Lõppu tuleb kindlasti lisada käsklus Close, et teataks hoolitseda andmete
füüsilise kettale jõudmise eest ning antakse fail vabalt kasutatavaks ka ülejäänud programmide
jaoks. Siinse näite tulemusena tekib käivituskataloogi lihtne fail nimega "inimesed.txt", kuhu
kirjutatakse kaks nime. Tekstiekraanil enesel pole käivitamise järel midagi näha - aga see veel
ei tähenda, nagu programm midagi ei teeks. Lihtsalt tema töö tulemus jõuab loodavasse faili.
using System;
using System.IO;
class Failikirjutus{
public static void Main(string[] arg){
FileStream f = new FileStream("inimesed.txt",
FileMode.Create, FileAccess.Write);
StreamWriter valja = new StreamWriter(f);
valja.WriteLine("Juku");
valja.WriteLine("Kati");
valja.Close();
}
}
Lisamine
Kui FileMode.Create asendada seadega FileMode.Append, siis jäävad kirjutades vanad
andmed alles. Uued read lihtsalt lisatakse olemasoleva teksti lõppu. Sellist lisamist läheb tarvis
näiteks sündmuste logi kirjutamise juures.
using System;
using System.IO;
class Faililisamine{
public static void Main(string[] arg){
FileStream f = new FileStream("inimesed.txt",
FileMode.Append, FileAccess.Write);
StreamWriter valja = new StreamWriter(f);
valja.WriteLine("Siim");
valja.WriteLine("Sass");
valja.Close();
}
}
Lugemine
Faili lugemisel on vood teistpidi. Create ja Write asemel on Open ja Read. Ning
StreamWriteri asemel StreamReader. Voost tuleva iga ReadLine tulemusena antakse üks
tekstirida failist. Kui faili andmed lõppesid, saabub ReadLine käsu tulemusena tühiväärtus
null. Selle järgi saab programmeerija otsustada, et fail sai läbi. Tahtes faili keerukamalt
töödelda - üksikute tähtede poole pöörduda või tervikuna ette võtta - selleks tuleb juba failiga
seotud õpetusi ise lähemalt uurida.
using System;
using System.IO;
class Faililugemine{
public static void Main(string[] arg){
FileStream f = new FileStream("inimesed.txt",
FileMode.Open, FileAccess.Read);
StreamReader sisse=new StreamReader(f);
string rida=sisse.ReadLine();
while(rida!=null){
Console.WriteLine(rida);
rida=sisse.ReadLine();
}
}
}
/*
C:\Projects\oma\naited>Faililugemine.exe
Juku
Kati
*/
Ülesandeid
* Tekita programmi abil fail, milles oleksid arvud ja nende ruudud ühest kahekümneni
* Tekstifaili igal real on müüdud kauba hind. Arvuta programmi abil nende hindade summa.
* Iga hinna kõrval on ka selle hinnaga müüdud kauba kogus. Korruta igal real hind kogusega
ning liida lõpuks summad kokku.
* Võrreldes eelmise ülesandega kirjuta teise faili igale reale esimese faili vastaval real oleva
hinna ja koguse korrutis.
Juhuarv
Kui soovida, et arvuti samade algandmete abil erinevalt käituks, tulevad appi juhuarvud.
Nende abil saab kasutajale ette anda juhusliku tervituse, muuta soovi järgi pildi värvi, või
näiteks kontrollida loodud funktsiooni toimimist mitmesuguste väärtuste juures. Kõigi nende
erinevate väljundite aluseks on arvuti poolt loodud juhuarvud. Neid aitab saada nimeruumi
System klassi Random eksemplar. Reaalarvu saamiseks on käsklus NextDouble. Kui soovida
mõnda muud vahemikku kui nullist üheni, tuleb saadud arv lihtsalt soovitud suurusega läbi
korrutada. Ühtlase jaotuse asemele normaal- või mõne muu jaotuse saamiseks tuleb mõnevõrra
enam vaeva näha - juhul kui see peaks tarvilikuks osutuma.
Täisarv luuakse käsuga Next, andes ette ülempiiri, soovi korral ka alampiiri. Ning sobiva nime,
anekdoodi või terviku saamiseks tuleb olemasolevad valitavad objektid paigutada massiivi,
leida juhuarv massiivi elementide arvu piires ning võibki sobiva väärtuse välja võtta.
using System;
public class Juhuarv1{
public static void Main(string[] arg){
Random r=new Random();
Console.WriteLine(r.NextDouble()); //Nullist üheni
Console.WriteLine(r.Next(20)); //Täisarv alla 20
Console.WriteLine(r.Next(50, 100)); //Viiekümnest sajani
string[] nimed={"Juku", "Kati", "Mati"};
Console.WriteLine(nimed[r.Next(nimed.Length)]); //Juhuslik nimi
}
}
/*
D:\kodu\0606\opikc#>Juhuarv1
0,74339002358885
11
95
Kati
*/
Ülesandeid
* Trüki juhuslike teguritega korrutamisülesanne
* Kontrolli, kas kasutaja pakutud vastus oli õige
* Sõltuvalt vastuse õigsusest lase arvutil pakkuda olemasolevate hulgast valitud kiitev või
julgustav kommentaar.
Omaloodud andmestruktuur
Standardandmetüüpe on .NET raamistikus kätte saada palju. Klasside arvu loetakse
tuhandetes. Sellegipoolest juhtub oma rakenduste puhul olukordi, kus tuleb toimetada
andmetega, mille hoidmiseks mugavat moodust pole olemas. Või siis on keegi kusagil selle
küll loonud, aga lihtsalt ei leia üles. Harilike muutujate ja massiivide abil saab küll kõike
arvutis ettekujutatavat hoida. Vahel aga on mugavam, kui pidevalt korduvate sarnaste
andmete hoidmiseks luuakse eraldi andmetüüp. Siis on teada, et kokku kuuluvad andmed
püsivad kindlalt ühes kohas koos ning pole nii suurt muret, et näiteks kahe firma andmed
omavahel segamini võiksid minna.
Järgnevas näites kirjeldatakse selliseks omaette andmestruktuuriks punkt tasandil, kaks
täisarvulist muutujat asukohti määramas.
struct Punkt{
public int x;
public int y;
}
Kui edaspidi programmis kirjutatakse
Punkt p1
siis on teada, et p1-nimelisel eksemplaril on olemas x ja y väärtused ning neid on võimalik
vaadata ja muuta. struct-iga kirjeldatud andmestruktuuri põhjal loodud muutuja omistamisel
teisele sama tüüpi muutujale kopeeritakse väärtused ilusti ära. Nii nagu ühe täisarvulise
muutuja väärtuse omistamisel teisele tekib sellest arvust koopia uude mälupiirkonda, nii ka
struktuurina loodud Punkti omistamisel teisele Punktile on mälus kaks eraldi ja sõltumatut
piirkonda, mõlemal x-i ja y-i teise x-i ja y-iga samasugused. Õieti ei peakski sellist kopeerimist
eraldi rõhutama - tundub ju nõnda omistamisel kopeerimine täiesti loomulik. Märkus on
toodud lihtsalt tähelepanu äratamiseks. Kui tulevikus ei kasutata andmetüübi loomisel mitte
sõna struct, vaid sõna class, siis käitutakse omistamisel mõnevõrra teisiti.
Nüüd aga programmi töö kohta seletus.
Punkt p1;
Luuakse muutuja nimega p1. Tal on mäluväljad nii x-i kui y-i jaoks.
p1.x=3;
p1.y=5;
Väljadele antakse väärtused
Punkt p2=p1;
Luuakse muutuja nimega p2. Ka temal on mäluväljad nii x-i kui y-i jaoks. Muutuja p1 x-i
väärtus kopeeritakse muutuja p2 x-i väärtuseks ning samuti ka y-iga. Praeguseks on siis mälus
kahte kohta kirjutatud kolm ning kahte kohta viis. Edasi muudetakse esimese punkti x-
koordinaat kaheks. Teise oma aga jääb endiselt kolmeks.
p1.x=2;
Et kui nüüd teise punkti koordinaadid välja trükkida, siis tulevad sealt rõõmsasti 3 ja 5.
Console.WriteLine(p2.x+" "+p2.y);
Võrreldes varasemate näidetega on koodi juurde tekkinud sõna namespace. Varasemate
väiksemate programmide puhul mõtles kompilaator neile juurde nimetu ja nähtamatu vaikimisi
nimeruumi. Kui aga klasse või struktuure on rohkem, on viisakas koos kasutatavad klassid
teise nimeruumi eraldada. Sel juhul pole nii palju karta, et keegi teine oma klassid meie
omadega sarnaselt nimetaks ning nad meie omadega tülli läheksid. Kui ka klassi nimi juhtub
sama olema, aga nimeruum on erinev, siis saab kompilaator aru, et need on erinevad ning ei
sega üksteist.
Töötav kood tervikuna.
using System;
namespace Punktid{
struct Punkt{
public int x;
public int y;
}
class Punktiproov{
public static void Main(string[] arg){
Punkt p1;
p1.x=3;
p1.y=5;
Punkt p2=p1; //Väärtused kopeeritakse
p1.x=2;
Console.WriteLine(p2.x+" "+p2.y);
}
}
}
/*
C:\Projects\oma\naited>Punktid
3 5
*/
Punktimassiiv
Oma andmetüüpi on enamasti põhjust luua juhul, kui seda tüüpi andmeid on rohkem kui paar-
kolm eksemplari. Suurema hulga samatüübiliste andmete jaoks kasutatakse sageli massiivi. Nii
ka omaloodud andmetüübi puhul.
Punkt[] pd=new Punkt[10];
teeb punktidest kümme eksemplari järjenumbritega nullist üheksani. Ning ilusti järjenumbri
järgi elemendi poole pöördudes saab sinna nii väärtusi panna kui küsida. Tsükli abil pannakse
iga elemendi y väärtuseks tema järjekorranumbri ruut. Ning väljatrükil
Console.WriteLine(pd[4].y);
trükitakse rõõmsasti ekraanile 16.
using System;
namespace Punktid2{
struct Punkt{
public int x;
public int y;
}
class Punktimassiiv{
public static void Main(string[] arg){
Punkt[] pd=new Punkt[10]; //mälu kohe olemas
for(int i=0; i<10; i++){
pd[i].x=i;
pd[i].y=i*i;
}
Console.WriteLine(pd[4].y);
}
}
}
/*
C:\Projects\oma\naited>Punktid2
16
*/
Ülesandeid
* Koosta struktuur riidelappide andmete hoidmiseks: pikkus, laius, toon
* Katseta loodud andmetüüpi paari eksemplariga.
* Loo lappidest väike massiiv, algväärtusta juhuarvude abil.
* Trüki välja lappide andmed, mille mõlemad küljepikkused on vähemalt 10 cm.
Objektorienteeritud programmeerimine
Tutvustus
struct-lausega loodud kirjed on mõeldud põhiliselt andmete hoidmiseks ning vajadusel
üksikute andmete (nt. sünniaasta abil vanuse) välja arvutamiseks. Toimingud andmetega
jäävad enamjaolt kirjest väljapool paikneva programmi ülesandeks. Objektide puhul aga
püütakse enamik konkreetse objektitüübiga seotud toiminguid ühise kesta sisse koondada. Piir
kirjete ja objektide vahel on mõnevõrra hägune ning mõnes keeles (nt. Java) polegi kirjete
jaoks eraldi keelekäsklust olemas. Samas on siiski hea eri lähenemised lahus hoida.
Traditsioonilise objektorienteeritud programmeerimise juures pole eksemplari muutujatele
sugugi võimalik otse väljastpoolt ligi saada. Samuti on vaid mõned alamprogrammid teistele
objektidele vabalt kasutavad, küllalt palju vahendeid võib olla loodud vaid objekti enese
toimimise tarbeks. Selline kapseldamine aitab suuremate programmide puhul järge pidada, et
vaid ühe teemaga seotud ning teemadevahelised lõigud üksteist segama ei hakkaks. Lisaks
väliste käskude vähemast arvust tulevale lihtsusele lubab muutujate ja alamprogrammide
varjamine teiste objektide eest hiljem oma objekti sisemist ülesehitust muuta ilma, et muu
programmi osa sellest häiritud saaks. Selline muutmisvõimalus on aga hästi tänuväärne
olukorras, kus tegijaid on palju, ja samu objekte kasutatakse tulevikus teisteski programmides.
Järgnevas näites on punkt loodud klassina ehk objektitüübina. Koordinaadid x ja y on
privaatse ligipääsuga, neid saab kasutada vaid sama klassi meetodites. Väärtuste küsimiseks on
eraldi käsklused GetX ja GetY. Esimese hooga võib tunduda selline väärtuse küsimise peitmine
imelik. Aga kui tulevikus näiteks soovitakse teha statistikat, et mitu korda on küsitud x-i ja
mitu korda y-i väärtust, siis alamprogrammiga andmete küsimise puhul saab selle loenduse
kergesti paika panna. Muutuja puhul aga ei ole nõnda tavaprogrammeerimise vahenditega
võimalik. Kuna siin on lubatud punkti asukoha määramine vaid eksemplari loomisel, siis kord
antud väärtusi enam muuta ei saa, sest Punkti juures pole lihtsalt vastavat käsklust. Klassiga
samanimelist väljastustüübita käsklust nimetatakse konstruktoriks. See käivitatakse vaid üks
kord - eksemplari loomisel - ning sinna sisse pannakse tavaliselt algväärtustamisega seotud
toimingud. Kui näiteks algväärtustamisel seada sisse piirang, et koordinaatide väärtused
peavad jääma nulli ja saja vahele, siis pole võimalik muus piirkonnas punkti luua. Sedasi
õnnestub objektina luua küllalt keerukaid süsteeme, mis oskavad "iseenese eest hoolitseda"
ning millel saab lihtsalt käsklusi ehk meetodeid välja kutsuda.
using System;
namespace Punktid3{
class Punkt{
private int x;
private int y;
public Punkt(int ux, int uy){
x=ux; y=uy;
}
public int GetX(){
return x;
}
public int GetY(){
return y;
}
public double KaugusNullist(){
return Math.Sqrt(x*x+y*y);
}
}
class Punktiproov{
public static void Main(string[] arg){
Punkt p1=new Punkt(3, 4);
Console.WriteLine(p1.GetX());
Console.WriteLine(p1.KaugusNullist());
}
}
}
/*
D:\kodu\0606\opikc#>Punktid3
3
5
*/
Ülesandeid
* Koosta klass riidelapi andmete hoidmiseks: pikkus, laius, toon
* Lisa käsklus lapi andmete väljatrükiks
* Lisa käsklus lapi pindala arvutamiseks
* Lisa meetod (alamprogramm) lapi poolitamiseks: pikem külg tehakse poole lühemaks.
* Poolitamise meetod lisaks algse lapi poolitamisele väljastab ka uue samasuguse algsest poole
väiksema eksemplari.
* Lisa teine poolitusmeetod, kus saab määrata, mitme protsendi peale lõigatakse pikem külg
Klassimuutuja
Klassi juurde korjatakse võimalusel kokku kõik vastavat tüüpi objektidega tehtavad toimingud
ja andmed. Enamasti kuuluvad andmed isendite ehk eksemplaride juurde, kuid mitte alati.
Näiteks tüüpiliselt - loodud punktide arvu loendur on küll punktidega sisuliselt seotud, kuid
pole sugugi ühe konkreetse punkti omadus. Punktide arv on olemas ka juhul, kui ühtegi
reaalset punkti eksemplari pole veel loodud. Sellisel juhul on punktide arv lihtsalt null. Samuti
on punktide arv olemas juhul, kui punkte on loodud hulgem. Lihtsalt sel juhul on loenduri
väärtus suurem. Sellise klassi ja mitte isendiga seotud muutuja ette pannakse sõna static.
static int loendur=0;
Teine asi on punkti järjekorranumber - see on iga punkti juures kindel arv, mis antakse punktile
tema loomise hetkel ning mida siinse programmi puhul enam hiljem muuta ei saa.
private int nr;
nr=++loendur;
tähendab, et kõigepealt suurendatakse loendurit (olemasolevate punktide arvu) ühe võrra ning
saadud arv pannakse uue loodud punkti järjekorranumbriks.
Osuti, omistamine
Klassi eksemplaride puhul toimub omistamine teisiti kui struktuuri eksemplaride puhul.
Struktuuri juures tehti lihtsalt väljade väärtustest koopia ja edasi elas kumbki eksemplar oma
elu teisest sõltumata. Kui nüüd aga teha omistamised
Punkt p1=new Punkt(3, 4);
Punkt p2=p1; //Kasutab sama mälupiirkonda
siis on mälus tegelikult koht vaid ühe punkti andmete jaoks, muutujate p1 ning p2 kaudu
pääseb lihtsalt ligi samadele andmetele.
Nii et kui ühe muutuja kaudu andmeid muuta:
p1.SetX(7);
siis muutuvad andmed ka teise märksõna alt paistvas vaates.
Ehk siis ükskõik, kas andmete poole pöörduda p1 või p2 kaudu – ikka saan tegelikult samad
väärtused.
Console.WriteLine(p2.GetNr()+" "+p2.GetX()+" "+p2.GetY());
trükib välja
1 7 4
ehkki esialgu olid koordinaatideks 3 ja 4 ning p2 kohe deklareerimisel polnud sugugi algne
koht esimese punkti andmetele ligi pääsemiseks.
Punkt järjekorranumbriga 2 on alles p3, sest tema loomisel käivitati uuesti Punkti konstruktor –
loodi uus eksemplar, mille käigus suurendati loendurit ning anti uued kohad andmete mälus
hoidmiseks.
Nüüd siis eelnevate seletuste kood tervikuna.
using System;
namespace Punktid4{
class Punkt{
static int loendur=0;
private int nr;
private int x;
private int y;
public Punkt(int ux, int uy){
x=ux; y=uy;
nr=++loendur;
}
public int GetX(){
return x;
}
public int GetY(){
return y;
}
public int GetNr(){
return nr;
}
public void SetX(int ux){
x=ux;
}
public void SetY(int uy){
y=uy;
}
public double KaugusNullist(){
return Math.Sqrt(x*x+y*y);
}
}
class Punktiproov{
public static void Main(string[] arg){
Punkt p1=new Punkt(3, 4);
Punkt p2=p1; //Kasutab sama mälupiirkonda
p1.SetX(7);
Console.WriteLine(p2.GetNr()+" "+p2.GetX()+" "+p2.GetY());
Punkt p3=new Punkt(77, 32); //Punkt järgmise järjekorranumbriga
Console.WriteLine(p3.GetNr());
}
}
}
/*
C:\Projects\oma\naited>Punktid4
1 7 4
2
*/
Punktimassiiv
Nii nagu üksiku muutuja juures, nii ka massiivi puhul tuleb (erinevalt structist) igale uuele
klassi eksemplarile new-käsuga mälu anda.
Punkt[] pd=new Punkt[10];
loob vaid kümme mälupesa, mis on võimelised näitama Punkt-tüüpi objektile. Samas
punktiobjekte pole selle käsu peale veel ühtegi. Ehk kui keegi sooviks näiteks pd[3] olematu
eksemplariga midagi teha, siis antaks veateade.
Alles siis, kui tsüklis luuakse iga ringi juures uus Punkti eksemplar ning pannakse massiivi
element sellele näitama, on võimalik iga massiivi elemendiga kui Punkti eksemplariga ringi
käia.
for(int i=0; iPunktid5
16
*/
Ülesandeid
* Koosta klass riidelapi andmete hoidmiseks: pikkus, laius, toon
* Koosta riidelappidest massiiv.
* Koosta uus lapimassiiv, kuhu pannakse osa eelmisest massiivist pärit lappe ja osa muid lappe
(kaltsukott).
* Arvuta mõlema massiivi puhul välja seal leiduvate lappide pindalade summa.
* Koosta riidelapi klassile staatiline käsklus näitamaks loodud riidelappide keskmist pindala.
Lappide puudumisel väärtuseks -1. Lisa vajalikud staatilised muutujad (lappide arv,
kogupindala)
* Koosta klass isikukoodi andmete hoidmiseks
* Lisa käsklus sünnikuupäeva küsimiseks
* Lisa käsklus sünnikuu küsimiseks sõnana
* Lisa käsklus sünniaasta väljastamiseks ka sajandit arvestades
* Kontrolli isikukoodi objekti tegemisel koodi pikkust
* Kontrolli isikukoodi kontrollisumma õigsust vastavalt järgnevale algoritmile:
Isikukoodi kontrolli algoritm
Isikukoodis peavad kõik sugu ning kuupäeva tähistavad väärtused olema
võimalikud ning viimane kontrollnumber arvutatakse järgneva algoritmi järgi:
liidetakse kokku esimese üheksa numbri korrutised igale arvule vastava
järjekorranumbriga, kümnenda numbri puhul ühega ning leitakse saadud summa
jääk jagamisel 11-ga. Kui jääk on võrdne kümnega, siis tehakse arvutus
uuesti ning võetakse teguriteks vastavalt 3, 4, 5, 6, 7, 8, 9, 1, 2, 3.
Näide: isikukoodi 37605030299 kontroll. Summa =
1*3 + 2*7 + 3*6 + 4*0 + 5*5 + 6*0 + 7*3 + 8*0 + 9*2 + 1*9 = 108
108 jääk jagamisel 11-ga on 9 => isikukoodi viimane number peab olema
üheksa.
Dokumenteerivad kommentaarid
Dokumentatsiooni loomiseks on üks võimalus panna kolme kaldkriipsuga märgistatud
kommentaarid koodi sisse, kommenteeritava ploki või muutuja ette. Sellised kommentaarid
oskab kompilaator sobiva võtme järgi välja, eraldi kommentaaride faili korjata. Kommentaariks
sobib xml-märgenditega piiratud tekst. Tavalisimaks elemendiks on , kuid eraldi
on olemas ka kokkuleppelised käsklused parameetrite, versioonide jm. jaoks.
Readonly
Võtmesõna readonly muutuja ees tähendab, et sinna võib väärtuse omistada vaid ühe korra.
Olgu siis muutuja kirjeldamisel või konstruktoris. Selliste muutujate puhul hoolitseb juba
kompilaator, et poleks võimalik kirjutada käsklusi, mis readonlyga kaitstud mäluväljade
väärtusi muudavad.
Näide
using System;
namespace Punktid6{
///
/// Tasandi punkti andmete hoidmine
///
class Punkt{
///
/// Muutuja ainult lugemiseks.
/// Andmed sisestatavad vaid konstruktoris.
///
private readonly int x;
private readonly int y;
///
/// Algandmed punkti loomisel kindlasti vajalikud
///
public Punkt(int ux, int uy){
x=ux; y=uy;
}
public int GetX(){
return x;
}
public int GetY(){
return y;
}
///
/// Kaugus arvutatakse Pythagorase teoreemi järgi.
///
public double KaugusNullist(){
return Math.Sqrt(x*x+y*y);
}
}
///
/// Eraldi klass punkti katsetamiseks.
///
class Punktiproov{
public static void Main(string[] arg){
Punkt p1=new Punkt(3, 4);
Console.WriteLine(p1.KaugusNullist());
}
}
}
Kompileerimine
Kompileerides saab siis /doc võtmega anda ette failinime, kuhu kirjutatakse kommentaarid.
/*
D:\kodu\0606\opikc#>csc Punktid6.cs /doc:Punktid6.xml
Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.42
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.
D:\kodu\0606\opikc#>Punktid6
5
*/
Kommentaarifail
Edasi on juba vastavalt kasutaja soovile - kas ta loeb tekkinud XML-faili lihtsa
tekstiredaktoriga, mõne eraldi XML-lugejaga (nt. vastavate võimetega veebibrauser) või siis
kirjutab juurde XSL-lehe andmete omale sobivale kujule muundamiseks.
Väike ülevaade tekkinud XML-failist. Esimene rida on ühine kõigile XML-vormingus
dokumentidele.
Järgnevaks juurelemendiks on doc, mille sisse kõik ülejäänud andmed mahuvad.
Assembly tähendab kokkukompileeritud üksust. Siin on ta laiendiks .exe, mõnel juhul võib
olla aga ka .dll
Punktid6
Ning edasi juba tulevadki klassi, muutuja, konstruktori ja meetodi töö kirjeldused.
Punktid6
Tasandi punkti andmete hoidmine
Muutuja ainult lugemiseks.
Andmed sisestatavad vaid konstruktoris.
Algandmed punkti loomisel kindlasti vajalikud
Kaugus arvutatakse Pythagorase teoreemi jC¤rgi.
Eraldi klass punkti katsetamiseks.
Ülesandeid
* Koosta klass riidelapi andmete hoidmiseks, lisa kommentaarid.
* Tutvu kompileerimisel loodud kommentaaride failiga.
Pärilus
Ajapikku on objektorienteeritud programmeerimiskeelte juurde lisatud mitmesuguseid
täiustusi ja võimalusi. Pärilus (inheritance) on mingil moel enamike objektorienteeritud keelte
küljes olemas. Nii ka end suhteliselt arenenuks keeleks pidava C# juures.
Objektidega toimetamisel ning pärilusel sealhulgas on vähemalt kaks eesmärki. Püütakse
lihtsustada koodi kohandamist. Samuti võimaldab pärilus vältida sama tööd tegeva koodi
kopeerimist, lubades samu käsklusi kasutada mitmel pool.
Päriluse abil püütakse programmeerimiskeele objektitüüpide omavahelisi suhteid lähendada
inimeste igapäevaselt tajutavale. Üheksakümnendate aastate algul ilmunud objektorienteeritud
programmeerimist tutvustavas õpikus oli ilus näide: "sebra on nagu hobune, kellel on triibud".
Sarnane tuttava kaudu uue tüübi kokkupanek ongi päriluse põhisisu. Midagi lisatakse, midagi
muudetakse, vahel harva ka kaotatakse. Ning märkamatult muutubki hobune sebraks või tühi
paneel tekstiväljaks.
Nõnda õnnestub olemasolevate (pool)valmis tükkide abil omale sobiv komplekt kokku panna.
Teiselt poolt võidakse objektide päriluspuu ehitada ka oludes, kus kõik programmi osad on
enese määrata. Sellisel juhul pole küll eesmärk omale üks ja parim tüüp kokku panna, vaid
otsitakse ühisosi, mille puhul on võimalik valminud tüübid gruppidesse jaotada ning nende
gruppidega vajadusel koos midagi ette võtta. Tüüpilise näitena pole teatripiletit müües vaja
teada inimese sugu ja ametit. Küll aga on balletirolli juures mõlemad andmed tähtsad.
Tavaelus tundub loomulikuna, et eriomadusi arvestatakse vaid kohtades, kus need on
vajalikud ning muul juhul aetakse vaid üldiste tunnustega läbi. Haruharva tuleb eraldi
rõhutada, et "meie teatrisse tohivad vaatajaks tulla inimesed sõltumata usutunnistusest ja
nahavärvist". Arvuti juures tuleb aga tasemed selgelt välja tuua. Igal keerukuse astmel tuleb
määrata, millised oskused ja omadused sinna juurde kuuluvad ning millise rolli jaoks millisel
tasemel oskuste komplekti vaja on. Keerukaks kiskunud seletuste kõrvale selgitused näidete
abil.
Päriluseta näide
Alustame inimese klassiga, kus igasugune pärilus puudub. Üks klass oma muutuja,
konstruktori ja meetodiga ning testprogrammis saab tema võimalusi katsetada.
using System;
namespace Parilus1{
class Inimene{
protected int vanus;
public Inimene(int uvanus){
vanus=uvanus;
}
public void YtleVanus(){
Console.WriteLine("Minu vanus on "+vanus+" aastat");
}
}
class InimTest{
public static void Main(string[] arg){
Inimene inim1=new Inimene(13);
inim1.YtleVanus();
}
}
}
/*
C:\Projects\oma\naited>Parilus1
Minu vanus on 13 aastat
*/
Alamklass
Edasi juba juures inimese alamklass (subclass) Modell, kel on olemas kõik inimese omadused
(ehk siis selle programmi puhul võime oma vanust öelda), kuid juures käsklus enese
esitlemiseks. Et esitlemine koosneb vanuse ja ümbermõõdu teatamisest, on see juba
esitlemiskäskluse sisse kirjutatud.
Klass Inimene on Modelli suhtes ülemklassiks (superclass, base class, parent class). C# puhul
on igal klassil alati täpselt üks ülemklass. Kui muud pole määratud, siis kasutatakse selleks
vaikimisi nimeruumi System klassi Object. Muul juhul aga on ülemklass kirjas klassikirjelduse
päriluse osas. Nii tekibki klasside puu, mis algab klassist Object.
Kui klassil konstruktor puudub, siis loob kompilaator vaikimisi tühja konstruktori, millel pole
parameetreid ja mis ka midagi ei tee. Inimese puhul siis näiteks
public Inimene(){}
Kui aga vähemalt üks programmeerija loodud konstruktor on olemas, siis seda nähtamatut
konstruktorit ei tehta. Päriluse puhul kutsutakse alamklassi eksemplari loomisel alati välja
ülemklassi konstruktor. Vaikimisi võtab kompilaator selleks ülemklassi parameetritega
konstruktori. Kui see aga puudub või soovitakse käivitada mõnda muud, siis tuleb sobiva
konstruktori väljakutse alamklassi juures ära märkida. Siin märgitakse näiteks Modelli loomise
juures, et Modelli isendi loomise juures tehakse kõigepealt valmis baasklassi (inimese) isend,
kellele siis Modelli enese konstruktoris vajalikud lisandused juurde pannakse. Ülemklassi
konstruktori määramine on kohe Modelli konstruktori juures. Pärast koolonit olev
base(vanus) ütleb, et kasutatagu inimese puhul seda konstruktorit, kus tuleb täisarvuline
vanus kohe ette öelda.
public Modell(int vanus, int uymberm66t):base(vanus){
ymberm66t=uymberm66t;
}
Ehkki praegu tegelikult muud võimalust polnudki, tuleb see ikkagi arvutile ette öelda.
using System;
namespace Parilus2{
class Inimene{
protected int vanus;
public Inimene(int uvanus){
vanus=uvanus;
}
public void YtleVanus(){
Console.WriteLine("Minu vanus on "+vanus+" aastat");
}
}
class Modell:Inimene {
protected int ymberm66t;
public Modell(int vanus, int uymberm66t):base(vanus){
ymberm66t=uymberm66t;
}
public void Esitle(){
YtleVanus();
Console.WriteLine("Mu ymberm66duks on "+ymberm66t+" sentimeetrit");
}
}
class InimTest{
public static void Main(string[] arg){
Modell m=new Modell(20, 90);
m.Esitle();
}
}
}
/*
C:\Projects\oma\naited>Parilus2
Minu vanus on 20 aastat
Mu ymberm66duks on 90 sentimeetrit
*/
Ülesandeid
* Lisa Inimesele pikkuse väli.
* Pikkus tuleb sisestada konstruktoris sarnaselt vanusele.
* Modelli käsklus Esitle teatab eraldi lausena ka pikkuse.
* Omista Modell Inimese tüüpi muutujale. Küsi sealtkaudu vanus.
* Koosta inimeste massiiv. Lisa sinna nii Modelle kui tavailisi inimesi. Küsi kõikide vanused.
* Lisa Inimesele int-tagastusväärtusega käsklus pikkuse väljastamiseks.
* Lisa testprogrammi käsklus static bool KasMahubAllveelaeva(Inimene isik), mis väljastab
tõese väärtuse juhul, kui pikkust on alla 165 sentimeetri. Katseta käsku nii Inimese kui Modelli
puhul, samuti Modellidest ja Inimestest koosneva massiivi juures.
* Väljasta false-vastus ka null-sisendi korral. Alveelaeva luba ka kuni 170 sentimeetri pikkused
modellid (painduvad hästi, kontrolliks isik is Modell).
* Loo mõlemale klassile taas ka ilma pikkuseta konstruktor. Sellisel juhul pannakse pikkuse
väärtuseks -1 ning esitluse juures teatatakse, et pikkuse andmed puuduvad.
Ülekate
Mõnikord ei piirduta omaduste lisamisega - tahetakse olemasolevaid võimalusi ka muuta.
Tavanäiteks on kujundid või graafikakomponendid, mis igaüks ennast vastavalt oma
omadustele joonistavad. Aga sarnaselt üle kaetavad võivad olla voogudesse andmete
kirjutamise käsklused või andmestruktuuridesse andmeid lisavad või sealt eemaldavad
käsklused nii, et neid käsklusi kasutav programm võib lihtsalt kasutatavat tükki usaldada, et
tal on vastava nimega käsklus olemas ning ta selle soovitud ajal sooritab.
Siin näites on Inimese alamklassiks tehtud Daam, kel kõik muud kirjeldatud omadused
hariliku Inimesega sarnased. Kuid vanuse ütlemine käib mõnevõrra teisiti. Et käske saaks rahus
üle katta, selleks on Inimese juurde käsu ehk meetodi YtleVanus ette lisatud sõna virtual,
Daami vastava meetodi ette aga override. Selliselt on kummatki tüüpi objektil vanuse
ütlemine selge, need lihtsalt käituvad erinevalt.
using System;
namespace Parilus3{
class Inimene{
protected int vanus;
public Inimene(int uvanus){
vanus=uvanus;
}
public virtual void YtleVanus(){
Console.WriteLine("Minu vanus on "+vanus+" aastat");
}
}
class Daam:Inimene {
public Daam(int vanus):base(vanus){}
public override void YtleVanus(){
Console.WriteLine("Minu vanus on "+(vanus-5)+" aastat");
}
}
class InimTest{
public static void Main(string[] arg){
Inimene inim1=new Inimene(40);
Daam inim2=new Daam(40);
inim1.YtleVanus();
inim2.YtleVanus();
}
}
}
/*
C:\Projects\oma\naited>Parilus3
Minu vanus on 40 aastat
Minu vanus on 35 aastat
*/
Ülesandeid
* Lisa Inimesele käsklus "KutsuEttekandja", katseta seda eksemplari juures.
* Katseta sama käsklust ka Daami eksemplari juures.
* Kata nimetatud käsklus Daami juures üle, pannes sisse sisse pidulikum ja peenem tekst.
Katseta.
* Loo Inimeste massiiv, kus on nii Inimesed kui Daamid. Palu igaühel teatada oma vanus ning
kutsuda ettekandja.
Liidesed
Nagu eespool kirjeldatud, on C# puhul üks ja kindel objektitüüpide pärinemise puu. Igal
klassil on oma ülemklass ning juureks on klass Object nimeruumist System. Samas aga
mõnegi tüübi puhul on sellest klassist objekte mugav kasutada tunduvalt rohkemates kohtades,
kui otsene päriluspuu ette näeb. Nii nagu Ambla kihelkonna Lehtse valla Läpi küla Troska talu
perepoeg Karl oli lisaks nendele ametlikele tiitlitele veel küla sepp, puutöömees ning korralik
vanapoiss, nii on hea mõnegi klassi puhul programmi ülesehituse lihtsuse huvides pakkuda
sinna rohkem rolle kui otsese päriluse järgi neid kokku tuleks. Näiteks Karlal oli leivateenimise
mõttes sepatööst kindlasti rohkem kasu kui teatest, et ta Ambla kirikuraamatus kirjas on.
Niisamuti on klassidele võimalik juurde panna liideseid, mis annavad õiguse vastava klassi
eksemplare kloonida, järjestada või muid kasulikke omadusi lisada. Liideste arv ühe klassi
juures ei ole piiratud. Liidesed mõeldi programmeerimiskeelte juurde välja, kuna selgus, et
nõnda kirjeldusi määrates ja kokku leppides õnnestub programmeerimisel vigu vähendada.
Järgnevas näites loodi liides IViisakas. I on liidesel ees sõnast Interface. Liidese juurde käib
enamasti sisuline seletus selle kohta, millised seda liidest realiseerivad objektid on. Ning lisaks
võivad olla mõned käsklused, millele vastavat liidest realiseerivad objektid on võimelised
reageerima. Liidese IViisakas puhul valiti selliseks käskluseks Tervita, mis saab omale
tekstilise parameetri. Ehk siis eeldatakse, et iga viisakas tegelane mõistab tervitusele vastata
juhul, kui talle öeldakse, kellega on tegemist. Nagu näha - Lapse ja Koera puhul need
tervitused on erinevad. Kuid nii nagu liides nõuab - nad on olemas. Sinnamaani suudab
kompilaator kontrollida. Ülejäänu on juba programmeerija hoolitseda, et kindlaksmääratud
nimega käskluse juurde ka vastavale klassile sobiv sisu saaks.
static void TuleSynnipaevale(IViisakas v){
v.Tervita("vanaema");
}
Loodud meetodi juures on näha, et parameetrina võetakse vastu vaid nende klasside
eksemplare, kelle puhul liides IViisakas on realiseeritud. Ning igaühel neist palutakse
tervitada vanaema. Kuidas keegi sellega hakkama saab, on juba klassi enese sees hoolitsetud.
using System;
namespace Liides1{
class Inimene{
protected int vanus;
public Inimene(int uvanus){
vanus=uvanus;
}
public virtual void YtleVanus(){
Console.WriteLine("Minu vanus on "+vanus+" aastat");
}
}
interface IViisakas{
void Tervita(String tuttav);
}
class Laps:Inimene, IViisakas {
public Laps(int vanus):base(vanus){}
public void Tervita(String tuttav){
Console.WriteLine("Tere, "+tuttav);
}
}
class Koer: IViisakas{
public void Tervita(String tuttav){
Console.WriteLine("Auh!");
}
}
class InimTest{
static void TuleSynnipaevale(IViisakas v){
v.Tervita("vanaema");
}
public static void Main(string[] arg){
Laps juku=new Laps(6);
juku.YtleVanus();
Koer muki=new Koer();
TuleSynnipaevale(juku);
TuleSynnipaevale(muki);
}
}
}
/*
C:\Projects\oma\naited>Liides1
Minu vanus on 6 aastat
Tere, vanaema
Auh!
*/
Kui mingil põhjusel jäänuks Lapsele või Koerale käsklus Tervita lisamata, siis annaks
kompilaator veateate. Sama tekiks ka juhul, kui käskluse tekstis trükiviga tehtaks. Selline
kompilaatoripoolne kontroll aitab vead üles leida juba enne tegelike andmetega katsetamise
alustamist.
Ülesandeid
* Katseta, mis juhtub, kui Lapse tervita kirjutada väikese tähega.
* Lisa liidesesse IViisakas käsklus KoputaUksele.
* Muuda liidest realiseerivaid klasse nii, et kood kompileeruks.
* Testi töö tulemust.
* Koosta liides ISummeerija käsklustega Alusta, Lisa ning KysiSumma. Esimene siis
tühjendab andmed, teine lisab ühe väärtuse ning kolmas väljastab olemasolevate summa.
* Koosta liidest realiseeriv klass, kus on muutuja summa hoidmiseks. Alustamise peale
pannakse see nulli, lisamise puhul suurendatakse väärtust ning summa küsimisel väljastatakse
meeles olev summa. Omista klassi eksemplar liidese tüüpi muutujale. Katseta.
* Koosta sama liidest realiseerima teine klass, kus lisatavate andmete jaoks on olemas massiiv.
Kogu aeg peetakse meeles lisatud väärtuste arv ning väärtused ise. Summa küsimisel
arvutatakse kokku elementide väärtuste summa ning väljastatakse see.
* Koosta eraldi peaprogramm, mis eeldab, et seal sees on kasutada ISummeerija liidest
realiseeriv objekt – tegelikult aga algul on seal tühiväärtus-null. Peaprogramm koostatakse
nõnda, et ta küsib kasutajalt arve ja palub ISummeerijal need kokku liita. Kontrolli, et kood
kompileeruks.
* Koosta libasummeerija, mille Alusta ja Lisa-käsklused ei tee midagi. KysiSumma aga
väljastab alati väärtuse -1. Katseta peaprogrammi loodud libaklassi objektiga.
* Loo eraldi klass SummeerijaVabrik summeerijaobjektide tootmiseks. Lisa sinna sisse
staatiline käsklus LooSummeerija(int EeldatavKogus). Kui kogus on alla kümne, väljastatakse
andmeid salvestav summeerija, muul juhul kohe andmeid kokku liitev summeerija. Katseta
süsteemi toimimist.
Abstraktne klass
Liideseid pidi pärinevad vaid meetodite nimed. Klasside kaudu pärinevad nimed koos sisuga.
Aga mõnikord soovitakse vahepealset varianti: et mõnedel meetoditel on olemas sisu, teistel
aga veel mitte. Sellisel juhul aitab välja abstraktne klass. Tüüpiline näide on toodud allpool.
Sirgete ja püstiste seintega kujundi puhul saab ruumala arvutada juhul, kui on teada põhja
pindala ja kõrgus - valemi järgi piisab vaid nende omavahelisest korrutamisest. Kui aga
kujundeid on hulgem, siis on niru seda valemit igale poole uuesti kirjutada. Rääkimata
juhtudest, kus valem tunduvalt keerukam on, või neid iga kujundi kohta mitu.
Siin on kujundi näidetena toodud Tikutops ja Vorstijupp. Esimesel neist on
standardmõõtude puhul suurus kohe teada, vastavad meetodid väljastavad kohe konkreetsed
arvud. Teisel juhul antakse mõõtmed objekti loomisel ette, meetodid peavad küsimise peale
need salvestatud väärtused välja andma. Ning kui vorst ilusti matemaatiliselt omale silindrina
ette kujutada, siis annab pii korda raadiuse ruut ilusti põhja ehk ristlõike pindala välja.
Klassist Kujund enesest ei saa eksemplare luua. Kui klass sisaldab abstraktseid ehk
defineerimata meetodeid, siis peab klass ka ise tervikuna olema abstraktne ning siis ei lubata
temast eksemplare teha. Muidu ju tekiks probleem, kui soovitaks käivitada kujundi olematu
koodiga käsklust KysiKorgus().
Küll aga tohib muutujale tüübist Kujund tegelikke objekte omistada - olgu nad siis
Tikutopsid, Vorstijupid või pärit mõnest muust Kujundi alamklassist. Sarnaselt nagu võis
Daami omistada Inimese tüüpi muutujale või Lapse muutujale tüübist IViisakas. Kui üle
kaetud klassis on eelnevalt abstraktsetele meetoditele sisu antud, siis võib sellest klassist
julgesti isendeid luua ning neid ka kõikidest ülemklassidest pärit muutujatele omistada.
using System;
namespace AbstraktseKlassiUuring{
abstract class Kujund{
public abstract double KysiPohjaPindala();
public abstract double KysiKorgus();
public double KysiRuumala(){
return KysiPohjaPindala()*KysiKorgus();
}
}
class Tikutops:Kujund{
public override double KysiPohjaPindala(){return 8;}
public override double KysiKorgus(){return 1.5;}
}
class Vorstijupp: Kujund{
int pikkus, raadius;
public Vorstijupp(int upikkus, int uraadius){
pikkus=upikkus;
raadius=uraadius;
}
public override double KysiPohjaPindala(){
return Math.PI*raadius*raadius;
}
public override double KysiKorgus(){
return pikkus;
}
}
class Test{
public static void Main(string[] arg){
Tikutops t=new Tikutops();
Vorstijupp v=new Vorstijupp(10, 3);
Console.WriteLine("Ruumalad {0} ja {1}",
t.KysiRuumala(), v.KysiRuumala());
}
}
}
/*
D:\kodu\0606\opikc#>AbstraktseKlassiUuring
Ruumalad 12 ja 282,743338823081
*/
Ülesandeid
* Lisa klassile Kujund abstraktne meetod PohjaYmbermoot ning meetod KyljePindala.
Katseta - lisades vajalikud meetodid ka alamklassidesse.
* Loo Kujundi alamklass Risttahukas lisades talle vajalikud väljad kolme mõõtme hoidmiseks
ja kattes üles Kujundi abstraktsed meetodid. Katseta mitmesuguste Risttahuka
eksemplaridega.
* Koosta mitmesuguste Kujundite massiiv. Loo alamprogramm leidmaks massiivis olevate
kujundite ruumalade summa. Loo eraldi alamprogramm leidmaks massiivis olevate kujundite
pindalade summa.
Meetodite asendus
Harilikult kirjutatakse meetodite üle katmise juures ülemklassi meetodi ette virtual ning
alamklassi juurde override. Sellisel juhul alamklassi (siinses näites Daami) objekti puhul
kasutatakse alati seda meetodit, mis tema juurde käib - sõltumata, millisest tüübist on muutuja,
mille kaudu eksemplari poole pöördutakse. C++ võimalusi säilitades aga on jäetud ka teine
võimalus. Meetodi võib asendada nõnda, et tema kirjeldamise ette kirjutatakse sõna new. Sel
juhul peidetakse vana meetod samuti ära. Vana meetodi saab aga kätte juhul, kui objekti poole
pöörduda ülemklassi muutujast, kus vastav meetod vanal kujul kasutusel oli. Kui
virtual/override puhul pidid parameetrid ja väljastustüüp samaks jääma, siis new loob
täiesti uue ja eelmisest sõltumatu meetodi.
Järgnevas näites on ehitatud kunstlik pärilusahel. Ülemklassiks Inimene, kes ütleb oma
vanuse nõnda nagu see on. Inimesest pärinenud Daam võtab ilma pikemalt mõtlemata 5 aastat
maha. Daami alamklassiks olev Beib keeldub üldse vanuse teatamisest ning eriti kaugele
arenenud KavalBeib palub kasutajal ise tema vanust pakkuda. Sõna sealed klassi juures
näitab, et sellest klassist ei lubata enam edasi pärida. Selline määrang aitab kompilaatoril koodi
optimeerida.
Alljärgnevalt katsetatakse, millist tüüpi muutuja kaudu millise tegeliku objekti poole
pöördumisel milline tulemus saadakse. Et omistamine on võimalik ainult ülemklassi suunas, siis
igaühe neist saab omistada Inimese tüüpi muutujale. Mida tase edasi, seda vähem on
omistusvõimalusi.
Katsetamise käigus antakse Beiblastele vanuseks 17 aastat, teistele 40. Ning
jälgitakse, milline meetod millise muutuja kaudu väljakutsel käima läheb. Kõige pikem ja
keerukam lugu on Kavala Beibega. Et ta on pärimispuus kõige kaugemal, siis teda on võimalik
omistada kõikide selles puus olevate tüüpidega muutujatele. Enese tüübi puhul küsib ta
vanuseks 19, nagu käskluses öeldud. Ka lihtsalt Beib-tüüpi muutuja kaudu küsib ta enesele 19,
sest klassi Beib meetod YtleVanus on virtual ning tegelikult käima läheb objekti enese ehk
klassis KavalBeib loodud meetod.
Edasi muutub lugu keerulisemaks. Daami-muutujast välja kutsutav Kavala Beibe vanus
tuleb 12, sest ta lahutab aastad maha nagu Daamile kohane. Ning ka hariliku inimese kaudu
tuleb 12, sest virtual-piiritleja kaudu võetakse käsklus võimalikult objekti enese lähedalt.
using System;
namespace Asendus{
class Inimene{
protected int vanus;
public Inimene(int uvanus){
vanus=uvanus;
}
public virtual void YtleVanus(){
Console.WriteLine("Minu vanus on "+vanus+" aastat");
}
}
class Daam:Inimene {
public Daam(int vanus):base(vanus){}
public override void YtleVanus(){
Console.WriteLine("Minu vanus on "+(vanus-5)+" aastat");
}
}
class Beib:Daam{
public Beib(int vanus):base(vanus){}
new public virtual void YtleVanus(){
Console.WriteLine("Minu vanus pole sinu asi, vot!");
}
}
sealed class KavalBeib:Beib{ //siit ei saa enam edasi areneda
public KavalBeib(int vanus):base(vanus){}
public override void YtleVanus(){
Console.WriteLine("Arva, kas olen {0}?", vanus+2);
}
}
class InimTest{
public static void Main(string[] arg){
KavalBeib kb=new KavalBeib(17);
Beib b=new Beib(17), bkb=kb;
Daam d=new Daam(40), db=b, dkb=kb;
Inimene i=new Inimene(40), id=d, ib=b,ikb=kb;
kb.YtleVanus();
b.YtleVanus();
bkb.YtleVanus();
d.YtleVanus();
db.YtleVanus();
dkb.YtleVanus();
i.YtleVanus();
id.YtleVanus();
ib.YtleVanus();
ikb.YtleVanus();
}
}
}
/*
D:\kodu\0606\opikc#>Asendus
Arva, kas olen 19?
Minu vanus pole sinu asi, vot!
Arva, kas olen 19?
Minu vanus on 35 aastat
Minu vanus on 12 aastat
Minu vanus on 12 aastat
Minu vanus on 40 aastat
Minu vanus on 35 aastat
Minu vanus on 12 aastat
Minu vanus on 12 aastat
*/
Ülesandeid
* Loo klass Punkt väljadega x ja y ning meetoditega KaugusNullist ja TeataAndmed. Esimene
väljastab reaalarvuna kauguse koordinaatide alguspunktist. Teine tagastab tekstina
koordinaatide väärtused.
* Loo Punktile alamklass RuumiPunkt. Lisa väli z, kata üle KaugusNullist ning asenda
TeataAndmed. Esimene väljastab kauguse nullist kolme koordinaadi korral, teine aga kirjutab
RuumiPunkti andmed ekraanile, meetodid tagastustüübiks on void.
* Katseta loodud objekte ja nende käsklusi igal võimalikul moel. Punkt Punkti muutujast,
RuumiPunkt RuumiPunkti muutujast ning RuumiPunkt Punkti muutujast.
Omadused
Objektide juures tehakse enamasti selget vahet: väljad ehk muutujad on andmete hoidmiseks,
käsud ehk funktsioonid ehk meetodid toimingute sooritamiseks, muuhulgas ka andmete poole
pöördumiseks. Ning korralikult kapseldatud objektorienteeritud programmis pääseb väljadele
otse ligi vaid objekti seest, kõik muud välised toimetused käivad meetodite kaudu.
Kahjuks või õnneks on enamik programmeerijaid kirjutanud ka objektikaugemat koodi. Kes
sellepärast, et tema kooliajal polnud veel objektorienteeritus kuigi laialt levinud või mõni teine
põhjusel, et piisavalt väikeste programmide puhul võib olla objektindusest rohkem tüli kui tulu.
Nõnda pakuvad programmeerimiskeeled mitmesuguseid "vahevorme", kus püütakse kasu
lõigata objektide süstemaatilisusest ning samal ajal jätta alles protseduuridel põhineva
programmeerimise lihtsuse.
Eelpool oli selliseks heaks mooduseks kirje (struct). Kirje puhul eeldatakse, et ta on loodud
põhiliselt andmete hoidmiseks, andmete õigsuse ja kokkusobivuse eest hoolitseb väline
programm, et tegemist on lihtsalt muutujate komplektiga nagu näiteks punkti koordinaadid.
Kirje vaid hoolitseb, et x ja y alati kokku kuuluksid.
Siiski on erinevalt mõnest muust keelest C# puhul lubatud kirje juurde ka käsklusi lisada.
Enamasti kasutatakse neid arvutatavate omaduste - nagu näiteks sünniaja järgi vanuse -
leidmiseks. Kirje kasutamise teeb objektist mugavamaks käskude mõningane lühidus. Seal
võib rahulikult kirjutada
p1.X=17;
int a=p1.X;
Objektide puhul peetakse sellist otse muutujate poole pöördumist ebaviisakaks. Paremaks
lahenduseks soovitatakse
p1.paneX(17);
int a=p1.KysiX();
On küll vaid paar tähte juures, aga piisavalt selle jaoks, et laisad programmeerijad vahel pigem
muudavad objekti muutujad otse ja avalikult kättesaadavaks, kui et andmeid viisakalt
meetodite kaudu vahetavad. Et lühidat kirjastiili säilitada ning samas jätta alles meetoditele
iseloomulik kontrollitavus ja muudetavus, selleks ongi loodud võimalus objektile luua
omadusi, mis näevad välja nagu muutujad, kuid käituvad nagu meetodid.
Järgnevas näites on klassi Ilmaandmed eksemplaridele lisatud omadus Temperatuur, millele
saab väljapoolt väärtust omistada ning küsida nagu tavaliselt muutujalt ehk väljalt. Küsimise
peale pannakse lihtsalt tööle get-koodiosa ning omistamise peale set-koodiosa. Sõna "value"
tähistabki omistamisel antud väärtust. Koodi ülesandeks on vastava märksõna all saabunud
andmed vajalikku kohta paigutada. Tegelikke andmeid hoitakse privaatses ehk väljapoolt
nähtamatus muutujas nimega temper. Vajadusel saab nõnda omaduse või meetodi kaudu
andmete poole pöördudes peale panna näiteks kontrolli võimalike väärtuste üle omistamisel.
Või siis saab programmeerija otsustada hoopis, et temperatuure endid ei hoita mingil hetkel
enam muutujas, vaid hoopis andmebaasis. Et kogu andmetega toimetamine on jäetud get- ja
set-meetodite hooleks, siis on selline asendus täiesti võimalik.
using System;
namespace Omadused1{
class Ilmaandmed{
private int temper;
public int Temperatuur{
get{return temper;}
set{temper=value;} //value on sisendväärtuse nimi
}
}
class Test{
public static void Main(string[] arg){
Ilmaandmed jaam1=new Ilmaandmed();
jaam1.Temperatuur=15;
Console.WriteLine(jaam1.Temperatuur);
}
}
}
/*
D:\kodu\0606\dotnet>Omadused1
15
*/
Pöördumisstatistika
Siin ongi toodud võimalikult lihtne näide, kuidas peale omistamise/küsimise veel meelde jätta
ja teada saada nende operatsioonide arv. Lihtsalt loendamise jaoks vastavad muutujad ja
käsklused juures.
using System;
namespace Omadused2{
class Ilmaandmed{
private int temper;
private int muudetud=0;
private int loetud=0;
public int Temperatuur{
get{
loetud++;
return temper;
}
set{
muudetud++;
temper=value;
}
}
public override string ToString(){
return "Muudetud: "+muudetud+", loetud:"+ loetud+
", temperatuur:"+temper;
}
}
class Test{
public static void Main(string[] arg){
Ilmaandmed jaam1=new Ilmaandmed();
jaam1.Temperatuur=15;
Console.WriteLine(jaam1.Temperatuur);
Console.WriteLine(jaam1.Temperatuur);
Console.WriteLine(jaam1);
}
}
}
/*
C:\Projects\oma\naited>Omadused2
15
15
Muudetud: 1, loetud:2, temperatuur:15
*/
Ülesandeid
* Kui temperatuuriks märgitakse üle 35, trüki hoiatusteade kahtlase väärtuse kohta
* Loo klass Kasutaja. Kasutajanimi määratakse konstruktoris, ning seda on hiljem võimalik
ainult omadusena küsida, mitte muuta (set-osa puudub). Parooli saab ainult määrata, aga
küsida pole võimalik (get-osa puudub). Lisa käsklus parooli kontrolliks. Lisa omadusena
kasutaja telefoninumber, mida on võimalik küsida ja muuta. Kontrolli, et töötaksid vaid
määratud tehted (st. et kord pandud parooli ei saaks küsida).
Indekseering
Massiivide puhul oleme harjunud, et kandiliste sulgude ja järjekorranumbri järgi küsime või
seame omale väärtuse. C# süntaks lubab aga ka ise kirjeldatud objektidel omapoolsed käsud
sellise pöördumise peale tööle panna. Iseenesest oleks võimalik kõik sellised toimetused mõne
hariliku funktsiooni ehk meetodiga ära ajada, aga kandiliste sulgudega kirja panduna tehe näeb
lühem välja. Ning mõnigi kord, kui andmed paistavad sarnased nagu massiivides hoitakse,
võimaldab selline kantsulgudega tehe koodi ka harjunumalt kirja panna.
Esimeses, võimalikult lihtsas näites väljastatakse indekseerimistehte tulemusena etteantud arvu
ruut. Kirjaviis on mõnevõrra sarnane omaduse kirjeldamisele: get-osas tuleb soovitud väärtus
returni abil tagasi anda.
using System;
namespace Indekseering1{
class Ruuduarvutus{
public int this[int nr]{
get{return nr*nr;}
}
}
class Test{
public static void Main(string[] arg){
Ruuduarvutus r=new Ruuduarvutus();
Console.WriteLine(r[3]);
}
}
}
/*
C:\Projects\oma\naited>Indekseering1
9
*/
Vahendus
Mõnelgi korral võib omaloodud indekseerimisel kasutada juba olemasolevat massiivi või muud
andmekogumit. Ning indekseerimise kaudu saab lisada selle elementide kasutamisele mõne
piirangu või ümberarvutusfunktsiooni. Siinses näites ehitati indekseerimise abil kest ümber
paisktabelile - paaride kogumile, kus igas paaris on võti ja väärtus. Võtmeks on kuupäev,
väärtuseks asukoht, kus vastaval päeval ansambel esineb. Andmete küsimisel vastatakse veel
vaba päeva kohta küsimisel "Vaba", kinnipandud päeva puhul teatatakse, kus vastaval päeval
esinemine on. Nimeruumis System.Collections asuval Hashtable klassist objektil on
andmete salvestamise ja küsimise käsklused juba sisse ehitatud. Lihtsalt Hashtable annab
vastava võtme puudumisel vastuseks tühiväärtuse null, meie aga vastame selle peale
inimkeelse "Vaba" ning andmete salvestamise juures juhul kui vastav kuupäev kinni on
heidetakse erind veateatega, miks vastav päev ei sobi. Kui aga soovitud kuupäev on veel vaba,
siis pannakse sinna juurde ilusti sobiv väärtus kirja.
using System;
using System.Collections;
namespace Indekseering2{
class Ringreis{
Hashtable esinemised=new Hashtable();
public string this[int kuupaev]{
get{
if(esinemised[kuupaev]==null){return "Vaba";}
return (string)esinemised[kuupaev];
}
set{
if(esinemised[kuupaev]!=null){
throw new Exception("Juba kinni, esinemine linnas "+
esinemised[kuupaev]);
}
esinemised[kuupaev]=value;
}
}
}
class Test{
public static void Main(string[] arg){
Ringreis r=new Ringreis();
r[3]="Narva";
r[4]="Tartu";
Console.WriteLine(r[3]);
Console.WriteLine(r[5]);
r[3]="Viljandi";
}
}
}
/*
C:\Projects\oma\naited>Indekseering2
Narva
Vaba
Unhandled Exception: System.Exception: Juba kinni, esinemine linnas Narva
at Ringreis.set_Item(Int32 kuupaev, String value)
at Test.Main(String[] arg)
*/
Ülesandeid
* Muuda arvu ruutu väljastavat indekseerimise näidet nii, et see väljastaks etteantud arvu
kuubi.
* Loo objekt, mis võtaks konstruktoris vastu sõna. Väljasta nii mitmes täht, kui indeksiga
näidatakse. Kui arv ületab sõna pikkuse, siis antakse teada, et sellise järjekorranumbriga tähte
ei leidu. Kui indeksiks pandud arv on negatiivne, siis väljastatakse niimitmes täht sõna lõpust
arvates.
* Loo indekseeringu kaudu kasutatav seitsmeelemendiline massiiv vastaval nädalapäeval
tehtud töötundide arvu summeerimiseks. Igal omistamisel liidetakse töötunnid vastava päeva
arvule otsa. Igal küsimisel antakse välja sellele nädalapäevale liidetud töötundide summa.
Negatiivse arvu sisestamisel tuleb veateade.
Struktuurne andmestik
Mitmedki toimetused saab tehtud ühe klassiga. Vajalikud andmed algul konstruktoris sisse
ning käskudega kannatab neid küsida, lisada ja muuta nagu parajasti sobiv ja võimalik on. Kui
on tegemist korraga mitme sarnase ülesehituse, kuid erinevate väärtustega andmetega (näiteks
mitme isikukoodiga), siis võib loodud klassist luua iga väärtuse jaoks omaette objekti ja
sealtkaudu siis mõningast analüüsi vajavate andmetega ümber käia. Kui aga andmeid on
hulgem, mitut tüüpi ja omavahel seotud, siis tasub mõelda nende omavahelise struktuuri peale.
Programmeerimist saab üldjuhul õppida „pastakast välja imetud” lihtsate näidete peal. Kui aga
mõnda teemasse sisse minna, siis tuleb erialateadmised ja programmeerimisoskused ühendada.
Mõlemad võivad eraldi võttes olla küllaltki lihtsad, kuid nende kokkupanek võib veidi
peamurdmist nõuda. Samas aga teadmiste ja tehnika ühendamine võib anda eraldi
toimetamisest märksa kasulikumaid tulemusi.
Järgnevas näites mängitakse läbi elektriskeemides loodetavasti põhikooliajast tuttavate
takistite ja nende ühendamisega ette tulevad arvutused. Elektroonikule on seostub takistiga
tõenäoliselt sageli punast värvi väike silinder, mille mõlemast otsast traat välja tuleb. Iseenesest
aga käituvad takistina ka tavalised lambipirnid ja mõned muudki elektroonikaseadmed. Lihtsal
takistil on keskkonnaoludest suhteliselt sõltumatu väärtusega takistus. Samuti on tal lubatud
suurim eralduv võimsus, mille ületamisel võib takistist välja tulla „hall mull” ehk tossupilv ning
komponent pole pärast seda enam kasutatav. Takistuseks nimetatakse juhtmeotstel ehk
klemmidel oleva pinge (surve, voltides) ning takistit läbiva voolu (elektronide voog, amprites)
suhet. Mida suurem takistus, seda vähem läheb sama pinge juures takistist voolu läbi. Takistil
soojusena eralduv võimsus (wattides) leitakse takisti klemmidele pandud pinge ning takistit
läbiva voolu korrutisena. Põhivalemid siis:
U/I=R
U*I=N
Kui veidi avaldada, siis leiab sealt, et . Ehk siis teadaoleva lubatud
maksimumvõimsuse ja takistuse põhjal on võimalik leida takistit läbi suurim lubatud vool.
Takisteid saab omavahel kombineerida. Tüüpilised ühendused on järjestikku (jadamisi) ja
rööbiti (paralleelselt). Jadamisi ühendades on arvutuskäik lihtne – takistuste komplekti
kogutakistus on võrdne ühendatud takistite takistuste summaga. R=R1+R2 või ka rohkem
komponente üksteisele järele liidetult. Rööbiti on arvutuskäik veidi keerulisem, kuid ka mitte
lootusetu. Kahe rööbiti takisti kogutakistus on väiksem kui kummalgi eraldi, kogutakistuse
pöördväärtus on võrdne üksikute takistite takistuste pöördväärtuste summaga -
esialgses programmis aga rööbiti ühendusega lihtsuse mõttes ei tegele.
Alustame kõige lihtsamast – loome klassi takisti andmete hoidmiseks ja sealt vajalike väärtuste
küsimiseks või arvutamiseks vastavalt lisaandmetele. Takistil omal muutujateks takistus R ja
maksimumvõimsus MaxN. Need antakse sisse ka konstruktoris. Vastavate andmete
kättesaamiseks eraldi käsklused LeiaTakistus ja LeiaMaksimumV6imsus – kuna tegemist
piiratud ligipääsuga (protected) muutujatega, siis muidu ei pruugi loodud objektist enam
väärtusi näha. Otse muutujate poole pöördumine pole viisakas – ei võimalda programmeerijal
hiljem enam loodud objektiga toimuvat kontrollida. Siin aga kui andmed antakse sisse
konstruktoris ning hiljem on võimalik neid ainult küsida, siis pole muret, et takisti andmed
seletamatul põhjusel muutuma hakkaksid. Juurde ka mõned käsklused voolu ja võimsuse
arvutamiseks ning kontrollimiseks, et kas soovitud pinge või vool ka konkreetsele takistile
lubatud on.
using System;
using System.Text;
namespace Takistid {
class Takisti {
///
/// Takistus
///
protected double R;
///
/// Maksimumvõimsus
///
protected double MaxN;
public Takisti(double Takistus, double Maksimumv6imsus) {
this.R = Takistus;
this.MaxN = Maksimumv6imsus;
}
public double LeiaVool(double Pinge) {
return Pinge / R;
}
public double LeiaV6imsus(double Pinge, bool SobivusKontroll) {
double V6imsus=Pinge * LeiaVool(Pinge);
if (SobivusKontroll) {
if (V6imsus > MaxN) {
throw new Exception("Pingel " + Pinge + " ületab võimsus " +
V6imsus + " lubatud maksimumvõimsust " + MaxN);
}
}
return V6imsus;
}
public double LeiaV6imsus(double Pinge) {
return LeiaV6imsus(Pinge, true);
}
public bool KasLubatudVõimsusVastavaltPingele(double Pinge) {
return LeiaV6imsus(Pinge, false) <= MaxN;
}
public double LeiaMaksimumVool() {
return Math.Sqrt(MaxN / R);
}
public double LeiaTakistus() {
return R;
}
public double LeiaMaksimumV6imsus() {
return MaxN;
}
}
}
Kui takisti klass valmis, siis on hea seda katsetada. Loome konkreetsete omadustega takisti –
näiteks 5? takisti maksimumvõimsusega 2W – ehk siis ettekujutatuna ühe pisikese lapse
näpuotsasuuruse jupikese, millest kaks juhet välja tulevad. Kontrollime, kas sellise takisti
kannataks ühendada 1,5-voldise patarei taha. Programm arvutab ja teatab, et kannatab küll,
väljundvõimsuseks 0,45 Watti. Ise järgi arvutades võime tulemust kontrollida. 1,5 volti
jagatud 5 oomiga annab 0,3 amprit. 0,3 amprit korrutatuna 1,5 voldiga teebki 0,45 watti. Mis
siis teeb küll takisti õrnalt soojaks, aga ei lõhu seda veel ära. Kui küsitaks peale tunduvalt
suurem pinge, siis meie programm peaks teatama, et sellise pingega tekkiv võimsus pole
lubatud.
using System;
using System.Text;
namespace Takistid {
class TakistiProov {
public static void Main(string[] arg) {
Takisti t1 = new Takisti(5, 2); //5 oomi, 2 watti
double PatareiPinge = 1.5; //volti
if (t1.KasLubatudVõimsusVastavaltPingele(PatareiPinge)) {
Console.WriteLine("Patareilt " + PatareiPinge + "V saadakse " +
" takistiga " + t1.LeiaTakistus() + " oomi vool " +
t1.LeiaVool(PatareiPinge) + "A ja " +
" võimsus " + t1.LeiaV6imsus(PatareiPinge) + "W");
} else {
Console.WriteLine("Pingel "+PatareiPinge+"V tekkiv võimsus "+
t1.LeiaV6imsus(PatareiPinge, false)+"W ületab lubatud "+
"võimsust "+t1.LeiaMaksimumV6imsus()+"W");
}
}
}
}
/*
Patareilt 1,5V saadakse takistiga 5 oomi vool 0,3A ja voimsus 0,45W
*/
Ühe või kahe takistiga saab rahumeeli toimetada. Nendega ümber käimiseks pole isegi
programmi vaja. Kui aga andmeid rohkem, siis tasub mõelda nende haldamise peale.
Elektroonikutel on suuremate takistikogustega ümber käimiseks kasutada takistussalved.
Sarnase vahendi kannatab ka programmi poole pealt valmis teha. Nii nagu programmiobjektid
ühel või teisel moel jäljendavad reaalse maailma objektid omadusi, nii ka siinpool. Koostame
takistite jadamisi ühendamiseks eraldi takistussalve. Nii nagu pärissalves on kindel arv kohti
takistite jaoks, nii siin on massiivil ka kindel hulk mälupesi takistiobjektide jaoks. Pesade arv
määratakse kindlaks salve konstruktoris. Salvest voolu läbilaskmisel on piirajaks kõige
nõrgemat voolu kannatava takisti maksimumvool. Kui aga salv on tühi, ka siis ei või sellest
lõpmatult suurt voolu läbi lasta. Siin näites on määratud tühja salve maksimumvooluks 16
amprit – selline korraliku koduse pikendusjuhtme läbilaskevõime. Salvel on käsklus takisti
lisamiseks – esialgu aga veel mitte eemaldamiseks või väljavahetamiseks – seda lihtsalt
lühiduse ja lihtsuse mõttes. Lisamisel jäetakse meelde lisatud takistite arv – siis teab, millisesse
pessa järgmine lisatav takisti panna. Samuti on võimalik hoiatusteade anda juhul, kui salve
enam takisteid ei mahu. Kogutakistuse leidmiseks liidetakse salves olevate takistite takistuste
summad kokku. Maksimumvoolu leidmiseks leitakse vool, mida kannatab ka kõige nõrgem
takisti ahelas. Lubatud pinge kontrollimiseks leitakse salve kogutakistuse järgi arvutatud vool
vastavalt pingele ning kontrollitakse, kas see on piisavalt väike, et kõik salves olevad takistid
selle välja kannataksid.
using System;
using System.Text;
namespace Takistid {
class JadamisiTakistusSalv {
Takisti[] Andmed;
int TakistiteArv = 0;
const int TyhjaSalveMaksimumvool = 16;
public JadamisiTakistusSalv(int TakistiteMaksimumArv) {
Andmed = new Takisti[TakistiteMaksimumArv];
}
public void LisaTakisti(Takisti t) {
if (TakistiteArv < Andmed.Length) {
Andmed[TakistiteArv] = t;
TakistiteArv++;
} else {
throw new Exception("Takistussalv täis!");
}
}
public double LeiaKoguTakistus() {
double summa = 0;
for (int i = 0; i < TakistiteArv; i++) {
summa = summa + Andmed[i].LeiaTakistus();
}
return summa;
}
public double LeiaMaksimumVool() {
double MaxVool = TyhjaSalveMaksimumvool;
for (int i = 0; i < TakistiteArv; i++) {
if (Andmed[i].LeiaMaksimumVool() < MaxVool) {
MaxVool = Andmed[i].LeiaMaksimumVool();
}
}
return MaxVool;
}
public bool KasLubatudV6imsusVastavaltPingele(double Pinge) {
double TekkivVool = Pinge / LeiaKoguTakistus();
return TekkivVool <= LeiaMaksimumVool();
}
}
}
Iga loodud klassi on ka hea katsetada. Siis julgem tunne, et tulevikus klassi tööd usaldada
võib. Vastutusrikkamatel kohtadel tuleb läbi proovida igasugu kombinatsioonid ja võimalikud
erijuhud. Ning seal võib testprogrammide kirjutamine olla paar-kolm korda keerukam kui
rakenduse enese loomine. Siin aga lihtsalt proovime, kas salve loomine õnnestub. Paar takistit
sisse ja väike arvutus kogutakistuse, maksimumvoolu ja lubatud pinge kohta. Nagu näha, salv
loodi viiele takistile, sinna sisse paneme praegu ainult kaks. Üks kümneoomine ühevatine ning
teine kümneoomine kahevatine. Siis aimatavalt on kogutakistus kakskümmend oomi ehk kahe
takisti takistuste summa.
using System;
namespace Takistid {
class TakistusSalveProov {
public static void Main(string[] arg) {
JadamisiTakistusSalv Salv = new JadamisiTakistusSalv(5);
Salv.LisaTakisti(new Takisti(10, 1));
Salv.LisaTakisti(new Takisti(10, 2));
Console.WriteLine("Kogutakistus: "+Salv.LeiaKoguTakistus());
Console.WriteLine("Maksimumvool: " + Salv.LeiaMaksimumVool());
Console.WriteLine("Kas 12 Volti salve klemmidel lubatud: " +
Salv.KasLubatudV6imsusVastavaltPingele(12));
}
}
}
/*
Kogutakistus: 20
Maksimumvool: 0,316227766016838
Kas 12 Volti salve klemmidel lubatud: False
*/
Ülesandeid
* Tutvu näidetega, vaheta väärtusi, kontrolli vastuste usaldusväärsust.
* Lisa takistiklassile käsklus, kus leitakse etteantud voolu
tekitamiseks vajalik pinge. Katseta
* Lisa sama käsklus takistussalvele
* Kontrolli takisti lisamisel salve, et sama takisti ei oleks seal juba olemas.
Ühine ülemklass
Kui eelmist näidet tähelepanelikult jälgida, siis paistab, et nii takistussalve kui takisti juures on
sarnaseid käsklusi. Voolu järgi pinge või pinge järgi voolu leidmise valem on ikka sarnane –
tuleb ainult omale selgeks teha, mida see takistus parajasti tähendab. Samuti ei tohi ei üksikule
takistile ega salvele panna peale suuremat pinget, kui suurima lubatud voolu jaoks kõlbulik
pinge on. Ning mis veel peenem – kui kord on kokku pandud takistussalv sobiva suurusega
takistusega, siis võib selle salve juhtmeotsad hea tahtmise korral ühendada teise salve ühe
takisti kohale. Nõnda kombineerides saab salvedest ja takistitest kokku ühendada päris
paindliku süsteemi. Ja et see päriselus võimalik on, siis on programmeerimiskeelte loojad
teinud kõik, et ka programmiklassidega maailma järele tehes sellise paindlikkuse kokku saaks.
Nagu alljärgnevalt näha, see ka õnnestub.
Takistile ja takisstussalvele (ja võibolla veel mõnele muule elektronide voolamist pidurdavale
seadmele, näiteks lambipirnile) loodi ühine ülemklass TakistusKomponent. Käsklused
LeiaTakistus ja LeiaMaksimumVool on määratud abstraktseteks. Sest igal seadmel on selle
leidmise moodus isesugune, vastavad omadused on aga igal elektriseadmel ehk
takistuskomponendil olemas. Ülejäänud abstraktse klassi käsklustes võime takistuse ja
maksimumvoolu lugeda juba teatuks ning konstrueerida muid käsklusi neid käske kasutades.
Kui takistus teada, siis voolu saab pinge jagades takistusega. Kui maksimumvool teada ja
pingele vastav vool arvutatav, siis saab kergesti järele kontrollida, kas komponendi klemmidele
tohib olemasolevat pinget rakendada või mitte.
using System;
namespace TakistusKomponendid {
abstract class TakistusKomponent {
public abstract double LeiaTakistus();
public abstract double LeiaMaksimumVool();
public double LeiaVoolVastavaltPingele(double Pinge) {
return Pinge / LeiaTakistus();
}
public bool KasLubatudPinge(double Pinge) {
return LeiaVoolVastavaltPingele(Pinge) < LeiaMaksimumVool();
}
}
}
Takisti ise näeb pärast seda juba suhteliselt lihtne välja. Poest ostetud vidinale omased
sisetakistus ja maksimumvõimsus tuleb ikka meelde jätta. TakistusKomponent kohustab üle
katma käsklused LeiaTakistus ja LeiaMaksimumVool. Esimese saab kätte otse muutujast.
Teise leidmise jaoks tuleb üleval tuletatud valemit rakendada.
using System;
namespace TakistusKomponendid {
class Takisti:TakistusKomponent {
protected double R, MaxN;
public Takisti(double Takistus, double MaksimumV6imsus) {
this.R = Takistus;
this.MaxN = MaksimumV6imsus;
}
public override double LeiaTakistus() {
return R;
}
public override double LeiaMaksimumVool() {
return Math.Sqrt(MaxN / R);
}
}
}
Takistussalves tuleb ikka luua koht sinna sisse pandavate komponentide andmete hoidmiseks.
Erinevalt eelnevast näitest aga nüüd on andmepesa tüübiks TakistusKomponent. See
tähendab, et programmis lubatakse salve sisse panna ka teisi salvesid. Miski ei takista praegu
salvel ka iseenese väljuvaid juhtmeid ühe oma takistikomponendi klemmidele ühendada –
selline suhteline mõttetus jääks aga praegu lihtsalt programmeerija südametunnistusele.
Kusjuures mõne programmi puhul pole iseenese andmete hoidmine sugugi mõttetu nähtus.
Näiteks, kui grupijuht peab hoolitsema inimeste toiduportsude eest, siis peab ta kindlasti
hoolitsema, et ta ka enese tarbeks supikausi muretseks. Kogutakistuse leidmisel leitakse
üksikute pesade takistuste summad. Kui seal juhtuvad olema tavalised takistid, siis saadakse
väärtused ühe käsuga muutujast kätte. Kui aga salve pesas juhtub olema teine salv, siis käsklus
LeiaTakistus käivitab selle salve kogutakistuse leidmise käskluse. Samuti maksimumvoolu
puhul leitakse eraldi iga komponendi maksimaalne lubatud vool. Ning kui mõneks
komponendiks on salv, siis uuritakse läbi eraldi kõik selle salve pesad ja antakse tagasi sealse
kõige õrnemat voolu kannatava komponendi vooluandmed.
using System;
namespace TakistusKomponendid {
class JadamisiTakistiteSalv:TakistusKomponent {
TakistusKomponent[] Andmed;
int KomponentideArv = 0;
const double TyhjaSalveMaksimumVool=16;
public JadamisiTakistiteSalv(int MaxKogus) {
Andmed = new TakistusKomponent[MaxKogus];
}
public void LisaTakistusKomponent(TakistusKomponent t) {
Andmed[KomponentideArv] = t;
KomponentideArv++;
}
public override double LeiaTakistus() {
double abi = 0;
for (int i = 0; i < KomponentideArv; i++) {
abi = abi + Andmed[i].LeiaTakistus();
}
return abi;
}
public override double LeiaMaksimumVool() {
double MaxVool = TyhjaSalveMaksimumVool;
for (int i = 0; i < KomponentideArv; i++) {
if (Andmed[i].LeiaMaksimumVool() < MaxVool) {
MaxVool = Andmed[i].LeiaMaksimumVool();
}
}
return MaxVool;
}
}
}
Edasine on taas katsetus. Luuakse kaks salve. Ühele sisse kaks kümneoomist takistit, kahe- ja
ühevatine. Teise salve üks 15-oomine kümnevatine takisti. Ning kolmanda salve sisse sootuks
kaks esimest salve. Edasi võib juba rahus päringuid tegema hakata ja vaadata, milliseid
andmeid kust tagastatakse.
using System;
namespace TakistusKomponendid {
class TakistusKomponentideProov {
public static void Main(string[] arg) {
JadamisiTakistiteSalv ts1 = new JadamisiTakistiteSalv(5);
ts1.LisaTakistusKomponent(new Takisti(10, 2));
ts1.LisaTakistusKomponent(new Takisti(10, 1));
JadamisiTakistiteSalv ts2 = new JadamisiTakistiteSalv(5);
ts2.LisaTakistusKomponent(new Takisti(15, 10));
JadamisiTakistiteSalv ts3 = new JadamisiTakistiteSalv(5);
ts3.LisaTakistusKomponent(ts1);
ts3.LisaTakistusKomponent(ts2);
Console.WriteLine("Esimese salve maksimumvool: " +
ts1.LeiaMaksimumVool());
Console.WriteLine("Teise salve maksimumvool: " +
ts2.LeiaMaksimumVool());
Console.WriteLine("Kolmanda salve maksimumvool: " +
ts3.LeiaMaksimumVool());
}
}
}
/*
Esimese salve maksimumvool: 0,316227766016838
Teise salve maksimumvool: 0,816496580927726
Kolmanda salve maksimumvool: 0,316227766016838
*/
Ülesandeid
* Koosta salv nelja viievatise kümneoomise takistiga. Leia kogutakistus, samuti suurim
lubatud vool.
* Lisa abstraktsele takistuskomponendile käsklus LeiaPingeVastavaltVoolule. Selle ning
LeiaMaksimumVool-u abil koosta käsklus LeiaMaksimumPinge. Katseta seda käsklust nii
üksiku takisti kui takistussalve juures. Võrdele leitud pinget eelmises ülesandes leitud voolule
vastava pingega.
* Koosta TakistusKomponendi alamklass rööpühenduses oleva kahe takistuskomponendi
kogutakistuse leidmiseks. . Maksimumvoolu leidmisel tuleb arvestada, et
mõlemale takistile mõjub sama pinge, takisteid läbiv vool aga jaotub pöördvõrdeliselt vastavalt
takistusele – mida suurem takistus, seda väiksem vool. Võimalik on ka hakata pingeid
katsetama väikese sammuga ja sealtkaudu leida sobiv maksimumvool. Katseta selle
rööpühenduse klassi põhjal loodud objekti töö õigsust mitmesuguste takistite ja
takistussalvede puhul – kaks ühesugust takistit rööpühenduses, kaks erinevat takistit
rööpühenduses. Takisti ja jadamisi salv rööpühenduses. Kaks rööpühendust veel omakorda
rööpühenduses.
Operaatorite üledefineerimine
Kui kõik saadud kiidusõnad kokku liita, siis võib end küll inglina tunda ...
Ehk siis liitmine ei pruugi sugugi tähendada arvude aritmeetilist summeerimist, vaid võib
vastavalt taustale ja teemale hoopis isesuguse tähenduse saada. Samuti võib omaloodud
klassist objektide puhul määrata, mida üks või teine tehtemärk tegelikult nende objektidega
teeb. Mõnes keeles (nt. Java) on tehtemärkide programmeerijapoolne määramine veaohu tõttu
ära keelatud. Aga siia keelde on võimalus alles jäetud ning aitab loodud objekte tavaelus
toimuvaga sarnasemalt käituma panna.
Eelpoolkirjeldatud indekseerimistehte juures määras programmeerija samuti, kuidas
omaloodud objekti puhul kantsulgude operaatori juures toimida. Järgnevalt paistab aga, et
omapoolse tõlgenduse võib lisada mitmetele kasutatavatele tehtemärkidele. Nii nagu
kantsulgude puhul, nii ka igasugu muude tehtemärkide asenduste puhul saab sama töö ära teha
tegelikult vaid omaloodud funktsioone kasutades. Ja seetõttu pole koodi kirjutamise juures
siinne teema sugugi hädavajalik. Aga võõra koodi mõistmiseks või oma objektidega lihtsaks ja
elegantseks toimetamiseks sobivad omapoolselt käituma määratud tehtemärgid hästi.
Näiteks on võetud inimestele hästi tuttav kellaaeg. Tunde ja minuteid saab sarnaselt liita nagu
kõiksugu muid suurusi. Kui aga näiteks 14:45le liita kaks tundi ja 30 minutit, siis tulemuseks
pole mitte 16:75, vaid 17:15 ja selline ümberarvutus tuleb ka programmile selgeks teha, et välja
arvutatud tulemustega ka inimeste keskkonnas midagi peale hakata oleks.
Plussmärgi üledefineerimiseks luuakse funktsioon nimega operator+, parameetriteks antakse
kaks kellaaega - ehk siis vasakul ning paremal pool tehtemärki olev. Ja funktsiooni
tagastustüübiks on samuti Kellaaeg. See tähendab, et kui kokku liidetakse kaks Kellaaega,
siis tulemuseks on ka Kellaaeg.
Funktsiooni sisu jääb praegu lühikeseks. Käsu new abil luuakse uue Kellaaja eksemplar ning
mõlema välja jaoks antakse lihtsalt ette plussmärgi mõlemal pool olnud vastava välja summad.
Et funktsiooni esimese parameetrina olev k1 on tehte väljakutsuja, siis siin on võetud tund ja
minut ta väljade seest. Objekti k2 puhul on andmete poole pöördumiseks viisakalt meetodit
kasutatud. Aga eks oleks võimalik ka mõlemal juhul meetodiga seda küsimist toimetada.
public static Kellaaeg operator+(Kellaaeg k1, Kellaaeg k2){
return new Kellaaeg(k1.tund+k2.Tund(), k1.minut+k2.Minut());
}
Et uue eksemplari loomisel andmed ilusti salvestatud saaksid, selle eest hoolitseb konstruktor.
Samuti kutsub viimane välja käskluse aegKorda, mille ülesandeks on liigsed minutid
tundideks muundada.
public Kellaaeg(int utund, int uminut){
tund=utund;
minut=uminut;
aegKorda();
}
Senikaua, kui minuteid juhtub etteantud olukorras olema üle kuuekümne, minnakse järgmise
tunni juurde ning võetakse minutite alt neid tunni jagu vähemaks.
void aegKorda(){
while(minut>60){
tund++;
minut-=60;
}
}
Kõige tavalisemal juhul, ehk siis, kui minuteid ongi alla kuuekümne, on tsükli tingimus kohe
algul väär, tsüklit ei täideta ühtegi korda ning funktsioon lõpetab töö ilma midagi tegemata.
Aga vähemasti võime kindlad olla, et ajaga on kõik korras.
using System;
class Kellaaeg{
int tund, minut;
public Kellaaeg(int utund, int uminut){
tund=utund;
minut=uminut;
aegKorda();
}
void aegKorda(){
while(minut>60){
tund++;
minut-=60;
}
}
public int Tund(){return tund;}
public int Minut(){return minut;}
public void tryki(){
Console.WriteLine("{0}:{1}", tund, minut);
}
public static Kellaaeg operator+(Kellaaeg k1, Kellaaeg k2){
return new Kellaaeg(k1.tund+k2.Tund(), k1.minut+k2.Minut());
}
}
class Test{
public static void Main(string[] arg){
Kellaaeg k1=new Kellaaeg(12, 10);
Kellaaeg k2=new Kellaaeg( 1, 4);
Kellaaeg k3=k1+k2;
k3.tryki();
}
}
/*
C:\Projects\oma\naited>Operaatorid1
13:14
*/
Tüübimuundusoperaatorid
Tahtes reaalarvu täisarvuks muundada nõnda, et komakohad kaduma lähevad, piisab arvu ette
sulgudes sõna (int) kirjutamisest. Näiteks
int a=(int)6.34;
Kui uut tüüpi ette ei kirjutaks, siis annaks kompilaator veateate, sest reaalarvu ei pruugi olla
võimalik kadudeta muundada täisarvuks. Sama lugu kehtib ka erineva suurusvaruga täisarvude
või erineva täpsusega reaalarvude puhul: kui muundusel võib andmeid kaduma minna, siis
tuleb muunduskäsk selgelt välja kirjutada. Vastupidi võib lasta ka arvutil tüübimuunduse
automaatselt ära teha. Näiteks
double b=3;
Ehkki kirjutatud kolm on arvuti jaoks algselt tüübist int, lubatakse see rahus reaalarvule
omistada.
Tüübimuundusi võib aga ka omaloodud tüüpide juures ette võtta.
Järgnevaga teatatakse, mis tuleb ette võtta juhul, kui kellaaeg omistatakse täisarvule. Sõna
implicit ütleb, et omistada võib eraldi tüübiteisenduskäsku (int) näitamata.
public static implicit operator int(Kellaaeg k){
return k.Tund()*60+k.Minut();
}
Ehk kui algul kirjutatakse
Kellaaeg k1=new Kellaaeg(12, 10);
ja pärast
int minutidPaevaAlgusest=k1;
siis kellaaja muutmine arvuks käib ilma, et programmeerija peaks sellele eraldi tähelepanu
juhtima.
Kui operaatorite kirjeldajal aga tekib kahtlus, et teisenduste käigus võivad andmed
ebatäpsemateks muutuda, või lihtsalt soovitakse, et kogemata ei tehtaks kirjeldatavat
teisendust, siis tuleb lisada piiritlejaks sõna explicit.
public static explicit operator double(Kellaaeg k){
//kohustuslik muunduse näitamine
return k.Tund()+k.Minut()/60.0;
}
Sellisel juhul tuleb omistamisel sobivasse kohta kirjutada (double), et teisendusest asja saaks.
Muidu annab kompilaator lihtsalt veateate.
double tunnidPaevaAlgusest=(double)k1;
Operaatorid võivad töötada ka teises suunas - ehk siis olemasolevast tüübist uue loodava tüübi
poole. Siin näites tehakse minutite hulgast taas Kellaaeg. Niipalju, kui jagub täistunde,
pannakse tundide alla. Mis aga tundideks jagamisest jäägina üle jääb, see muutub minutiteks.
public static explicit operator Kellaaeg(int minutid){
return new Kellaaeg(minutid/60, minutid%60);
}
Ning omistamisel saabki minutid taas Kellaajaks.
Kellaaeg k4=(Kellaaeg)minutidPaevaAlgusest;
Kui koodi töö tulemust vaadata, siis algul oli kellaaeg k1 12:10. Vahepeal muundati see
muutujasse minutidPaevaAlgusest ja saadi täisarvuna 730. Lõpuks minutid taas tundide ja
minutitena kellaaja sisse jaotatuna andsid jälle 12 tundi ja 10 minutit.
using System;
class Kellaaeg{
int tund, minut;
public Kellaaeg(int utund, int uminut){
tund=utund;
minut=uminut;
aegKorda();
}
void aegKorda(){
while(minut>60){
tund++;
minut-=60;
}
}
public int Tund(){return tund;}
public int Minut(){return minut;}
public void tryki(){
Console.WriteLine("{0}:{1}", tund, minut);
}
public static Kellaaeg operator+(Kellaaeg k1, Kellaaeg k2){
return new Kellaaeg(k1.Tund()+k2.Tund(), k1.Minut()+k2.Minut());
}
public static implicit operator int(Kellaaeg k){
return k.Tund()*60+k.Minut();
}
public static explicit operator double(Kellaaeg k){
//kohustuslik muunduse näitamine
return k.Tund()+k.Minut()/60.0;
}
public static explicit operator Kellaaeg(int minutid){
return new Kellaaeg(minutid/60, minutid%60);
}
}
class Test{
public static void Main(string[] arg){
Kellaaeg k1=new Kellaaeg(12, 10);
Kellaaeg k2=new Kellaaeg( 1, 4);
Kellaaeg k3=k1+k2;
k3.tryki();
int minutidPaevaAlgusest=k1;
double tunnidPaevaAlgusest=(double)k1;
Console.WriteLine(minutidPaevaAlgusest);
Console.WriteLine(tunnidPaevaAlgusest);
Kellaaeg k4=(Kellaaeg)minutidPaevaAlgusest;
k4.tryki();
}
}
/*
C:\Projects\oma\naited>Operaatorid2
13:8
730
12,1666666666667
12:10
*/
Võrdlusoperaatorid
Ikka tahetakse võrrelda, kas midagi toimus enne või pärast; uus on vanast suurem või väiksem;
keegi kellestki targem või rumalam. Mõningate objektide puhul ei pruugi selline võrdlus anda
selgepiirilist jah/ei vastust ning sellisel juhul on mõistlik arvuti jaoks võrdlusoperaatori
defineerimine ära jätta ning piirduda pigem mõnevõrra hägusema ja paindlikuma skaalaga. Kui
aga enamikel juhtudel on objektid omavahel kindlalt võrreldavad, siis on vastav operaator
omal kohal.
Võrdluse jaoks on tehteid palju: >, <, <=, <=, ==, !=. Enamasti pole aga otstarbekas kõiki neid
kohe ja eraldi defineerima hakata. Pigem teha täisarvu väljastav levinud funktsioon Compare.
Selles võrreldakse aktiivset objekti funktsiooni parameetrina antud objektiga. Kui
programmeerija arvates on aktiivne objekt parameetrina antud objektist järjestusreas eespool,
siis peaks funktsioon väljastama negatiivse arvu. Kui tagapool, siis positiivse. Ning kui
objektide võrreldavad tunnused peaksid programmeerija arvates olema võrdsed, siis tuleb
väljastada 0. Edasised võrdlustehted saab juba sedasama funktsiooni kasutades korda ajada.
Siin näites tehakse mõlema kellaaja sisu kõigepealt minutiteks alates päeva algusest, et oleks
kergem võrrelda ning siis juba saab ühe lahutustehtega sobiva vastuse kätte.
public int Compare(Kellaaeg k){
int omaminutid=(int)this;
int teiseminutid=(int)k;
return omaminutid-teiseminutid;
}
Samuti on viisakas üle katta klassist Object kaasa tulev käsklus Equals, mille ülesandeks on
teatada, kas aktiivne objekt on etteantud objektiga sisu poolest võrdne. Kui oma Compare juba
defineeritud, siis läheb edasine juba küllalt sarnaselt iga objekti puhul. Tingimuse esimese
poolega tasub kontrollida, et kas võrreldav objekt üldse on meie omaga sama tüüpi. Ehk siis
küsitakse, kas
ob is Kellaaeg
Vastus on tõene vaid juhul, kui etteantud objekt tõesti oli Kellaaeg. Ainult sel juhul minnakse
&& abil kirjutatud võrdluse kontrollimisega edasi. Muul juhul on tingimus kohe vale,
tingimusblokist hüpatakse üle ning väljastatakse funktsiooni lõpus tagastatav vastus
teatamaks, et kindlasti ei ole meie Kellaaeg sama väärtusega kui võrdlemiseks etteantud
objekt, sest see teine objekt lihtsalt ei ole Kellaaeg.
Et saadud objekti saaks Kellaajana Compare-meetodisse panna, peab eraldi teatama, et me
teda ikka Kellaajana kasutame. Ehkki is-kontrolli abil tegime juba kindlaks, et tegemist on
Kellaajaga, tuleb funktsioonile ette andmiseks see uuesti muundada. Üheks võimaluseks
oleks
(Kellaaeg)ob
siin aga näitame teist sarnast võimalust
ob as Kellaaeg
mis käitub üldjoontes samamoodi. Ainsaks erinevuseks on, et kui peaks siiski tüübiprobleem
tekkima, siis esimesel juhul heidetakse erind, teisel juhul väljastatakse aga lihtsalt tühiväärtus.
Kuna siin on juba kontroll tehtud, siis veateadet nagunii ei saa tekkida ning kood töötaks
mõlemal juhul sarnaselt. Käsule Compare antakse tulemus kontrollida. Kui väärtused loeti
võrdseteks ning väljastatakse 0, siis sel juhul kannatab Equals välja anda true, muul juhul
false.
public override bool Equals(Object ob){
if (ob is Kellaaeg && (this.Compare(ob as Kellaaeg)==0)){
return true;
}
return false;
}
Oma võrdlusoperaatorite puhul on viisakas üle katta veel ka käsklus GetHashCode. Selle
salapärase käsu ülesandeks on anda arvutile märku, kas objektid võiksid olla võrdse
väärtusega. Selleks tuleb objekti andmete põhjal kokku panna üks täisarv. Kui kahe objekti
andmed kattuvad, peab see arv olema ühesugune, muul juhul võimaluse korral erinev.
Kasutatakse näiteks paisktabelites, andmete otsimise jms. korral. Et meil on juba olemas
andmete minutiteks tegemise operaator, siis see sobib räsikoodiks imehästi. Piisab vaid
andmed täisarvuks teha, kui juba ongi eri kellaaegade puhul erinev arv käes, sest sama arv
minuteid päeva algusest saab olla vaid ühesuguse kellaaja korral.
public override int GetHashCode(){
return (int)this;
}
Kui näiteks hoitaks ajahetke objektis nii kellaaegu kui kuupäevi, siis võiks olukord veidi
raskemaks minna, sest neljabaidine int ei pruugi suuta näidata kõiki erinevaid väärtusi, mis
aastatuhandete jooksul ette tulevad. Sel juhul tuleks juba keerukamaid arvutusi rakendada, et
kõik objekti väljad oleksid räsikoodi arvutamisel rakendatud ja muudaksid tulemust. Samas
aga oleks eri väärtusega objektidel kahe sarnase räsikoodi kokku juhtumine võimalikult
haruldane. Ehk siis ligikaudu üks paari miljardi kohta - nii nagu neid int-muutuja erinevaid
väärtusi on.
Kui funktsioonidega eeltöö tehtud, siis edasine operaatorite defineerimine on juba käkitegu:
tuleb lihtsalt õige funktsioon välja kutsuda.
public static bool operator!=(Kellaaeg k1, Kellaaeg k2){
return !k1.Equals(k2);
}
Ning teiste võrdlustega sarnaselt. Nüüd aga kood tervikuna.
using System;
class Kellaaeg{
int tund, minut;
public Kellaaeg(int utund, int uminut){
tund=utund;
minut=uminut;
aegKorda();
}
void aegKorda(){
while(minut>60){
tund++;
minut-=60;
}
}
public int Tund(){return tund;}
public int Minut(){return minut;}
public void tryki(){
Console.WriteLine("{0}:{1}", tund, minut);
}
public static Kellaaeg operator+(Kellaaeg k1, Kellaaeg k2){
return new Kellaaeg(k1.Tund()+k2.Tund(), k1.Minut()+k2.Minut());
}
public static implicit operator int(Kellaaeg k){
return k.Tund()*60+k.Minut();
}
public static explicit operator double(Kellaaeg k){
//kohustuslik muunduse näitamine
return k.Tund()+k.Minut()/60.0;
}
public static explicit operator Kellaaeg(int minutid){
return new Kellaaeg(minutid/60, minutid%60);
}
public int Compare(Kellaaeg k){
//Võrdlusoperaatorite puhul soovitav defineerida
int omaminutid=(int)this;
int teiseminutid=(int)k;
return omaminutid-teiseminutid;
}
public override bool Equals(Object ob){
if (ob is Kellaaeg && (this.Compare(ob as Kellaaeg)==0)){
return true;
}
return false;
}
public override int GetHashCode(){
return (int)this;
}
public static bool operator==(Kellaaeg k1, Kellaaeg k2){
return k1.Equals(k2);
}
public static bool operator!=(Kellaaeg k1, Kellaaeg k2){
return !k1.Equals(k2);
}
public static bool operator<(Kellaaeg k1, Kellaaeg k2){
return k1.Compare(k2)<0;
}
public static bool operator>(Kellaaeg k1, Kellaaeg k2){
return k1.Compare(k2)>0;
}
}
class Test{
public static void Main(string[] arg){
Kellaaeg k1=new Kellaaeg(12, 10);
Kellaaeg k2=new Kellaaeg( 1, 4);
Kellaaeg k3=new Kellaaeg( 1, 4);
if(k2==k3){
Console.WriteLine("Samad");
}
if(k2Operaatorid3
Samad
Enne
*/
Ülesandeid
* Alusta esimesest lühikesest näitest ning lisa kellaajale ka sekundid.
* Arvesta sekundeid ka kellaaegade liitmise juures.
* Juhul, kui Kellaaeg teisendatakse tüübiks int, anna väärtus endise minutite asemel
sekundites.
* Loo klass Nihe, mille väljadeks on pikkus x- ja y-suunal.
* Defineeri operaator nihete liitmiseks.
* Võimalda nihkeid ka lahutada.
* Võrdlusoperaator loeb nihked võrdseks vaid juhul, kui mõlema koordinaattelje suunalised
pikkused on võrdsed.
* Suurema ja väiksema võrdlemisel võrreldakse vaid nihete pikkusi.
* Hoolitse ka omaloodud räsikoodi eest.
Abivahendid
Erindid
Kui arvutil palutakse teha tema jaoks võimatu käsk, siis enamasti lõpeb programmi töö
veateatega. Nii nagu järgnevas näites, kus tekst "Tere" püütakse muundada täisarvuks.
using System;
class Erind1{
public static void Main(string[] arg){
string tekst1="Tere";
int arv1=int.Parse(tekst1);
Console.WriteLine(arv1);
}
}
Veateatest võib välja lugeda, et klassi Erind1 käsklusest Main kutsuti välja
System.Number.ParseInt32, mis omakorda kutsus välja käsu StringToNumber. Viimane
aga jäi teksti arvuks muutmisega hätta. Anti välja veateade tüübist System.FormatException
koos selgitusega, et etteantud sõna pole sobival kujul.
/*
C:\Projects\oma\naited>Erind1
Unhandled Exception: System.FormatException: Input string was not in a
correct format.
at System.Number.StringToNumber(String str, NumberStyles options,
NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
at System.Number.ParseInt32(String s, NumberStyles style,
NumberFormatInfo info)
at Erind1.Main(String[] arg)
*/
Vähemasti saime teada, mis juhtus, aga kasutaja ei pruugi sellise teatega kuigivõrd rahul olla.
Eriti kui tuleb ette süsteemne veateateaken, mis püüab andmeid kuhugile saata või programmi
siluma hakata ning keeldub eest ära minemast.
Püüdmine
Arvuti veateate saab asendada enese omaga. Või siis hoopis paluda veateate andmise asemel
arv uuesti sisestada või sisestusest loobuda. Siin antakse lihtsalt omapoolne väike seletus
toimunu kohta.
Veateate püüdmiseks on C# keeles olemas try{}catch plokk. Kõik looksulgude vahel
juhtunud probleemid saadetakse lahendamisele catch-ossa, kus on juba programmeerija
otsustada, mida tekkinud olukorras peale hakata.
using System;
class Erind2{
public static void Main(string[] arg){
try{
string tekst1="Tere";
int arv1=int.Parse(tekst1);
Console.WriteLine(arv1);
}catch(FormatException probleem){
Console.WriteLine("Viga teisendusel: "+probleem.Message);
}
}
}
/*
C:\Projects\oma\naited>Erind2
Viga teisendusel: Input string was not in a correct format.
*/
Reageering tüübi põhjal
Sama koodilõigu juures võib ette tulla mitmesuguseid probleeme. Kord ei leita sobivat
andmefaili, teinekord ei saa teksti arvuks muundada ning mõnikord võib hoopis ette tulla
jagamine nulliga. Vanemate programmeerimiskeelte juures oli tavaks iga käsu juures
kontrollida, kas see õnnestus, ning siis püüda koheselt reageerida. Kui kohene parandamine on
võimalik, on selline lähenemine hea. Kui aga peab parandamiseks palju asju ära muutma, siis
kulub palju tööd. Selle lihtsustamiseks erindid ja veahaldus välja mõeldigi.
Ploki lõpus oleva catchi sulgudesse kirjutatakse selline erinditüüp, millele ollakse valmis
reageerima. Nagu eespool oli - FormatException tekkis sisendandmete vormingu vea tõttu
ning sellele probleemile ka reageeriti. Võib tekkida aga olukord, kus sisendiks on küll kõik
numbrid, aga kokku tuleb int-vormingu jaoks liiga suur arv. Sellisel juhul heidetakse hoopis
OwerflowException. Eraldi catchidega püüdes saab nendele vigadele sobivalt reageerida.
Vigade klassid moodustavad isekeskis hierarhia. Selle puu juureks on klass nimega
Exception. Tema alamklassideks on hulk .NET runtime käivitamisega seotud probleemiklasse,
aga muuhulgas ka SystemException, mille alt siis omakorda kättesaadavad enamik meil
programmides ettetulevaid System-nimeruumi objektidega seotud erindeid.
SystemExceptioni enese alt leiab omakorda näiteks ArithmeticExceptioni, mille juurest
omakorda OverflowExceptioni ja DivideByZeroExceptioni. Kui me tahame liialt suurt arvu
(ületäitumine) ning nulliga jagamist kontrollida sama catchi sees, siis võib piirduda
ArithmeticExceptioni püüdmisega. Kui aga kummagi olukorra jaoks on soov käivitada eri
kood, siis tasub need eraldi kinni püüda.
Viga jääb kinni ainult ühes catch-plokis. Seepärast pannakse detailsemad erindid püüdmisel
ettepoole ning üldisemad tahapoole. Muidu juhtuks, et teade jääb üldisematele tingimustele
vastavasse plokki kinni ja väljavalitud lõiku kunagi ei pruugitagi. Tahtes kõik teated kindlasti
kätte saada, võib lõppu panna catch(Exception). Sellele tüübile vastavad kõik veateated -
ka need, mis on kõigist muudest püünistest juba mööda tulnud.
Juhul, kui soovitakse saabunud veateate andmetega midagi lähemat ette võtta, saab selle
püüda omaette muutujasse ning sealtkaudu erindiobjektiga suhelda. Nagu näiteks
catch(FormatException probleem){
Console.WriteLine("Viga sisendandmetega: "+probleem.Message);
}
Kui aga piisab vaid teadmisest, et juhtus vastavat tüüpi olukord ning sellest teadmisest on
meile reageerimiseks küllalt, siis võib oma muutuja loomata jätta nagu näiteks
catch(OverflowException){
Console.WriteLine("Liiga suur arv.");
}
finally-plokki püüniste lõpus kasutatakse käskluste jaoks, mis tulevad igal juhul ära teha.
Näiteks faili sulgemine andmete lugemisel: isegi siis, kui lugemine ebaõnnestus, tuleb fail
teistele kasutajatele kättesaadavaks teha. Aga mainimist väärib, et finally-plokki jõutakse
siiski ainult juhul, kui viga polnud või sai sellele reageeritud. Nii et kindlaks lõpuni jõudmiseks
on mõistlik panna viimaseks veapüüniseks ikkagi catch(Exception). Kuigi - vahel
soovitatakse, et pigem jäta erind püüdmata, kui et püüad midagi, millega sa mõistlikku peale
hakata ei oska. Et kui tuleb ametlik veateade, on see vahel parem, kui omalt poolt vea
peitmine, mis võib hiljem vigaste andmete näol kusagil kätte maksta.
Nüüd aga näide tervikuna. Käsurea parameetrina oodatakse numbreid, mille programm arvuks
teisendab ning välja trükib. Juhtub aga midagi sobimatut, siis teatatakse vastav veateade.
using System;
class Erind3{
public static void Main(string[] arg){
try{
if(arg.Length!=1){
Console.WriteLine("Kasuta kujul: Erind3 sisendarv");
return;
}
string tekst1=arg[0];
int arv1=int.Parse(tekst1);
Console.WriteLine("Sisestati edukalt "+arv1);
} catch(FormatException probleem){
Console.WriteLine("Viga sisendandmetega: "+probleem.Message);
} catch(OverflowException){
Console.WriteLine("Liiga suur arv.");
} catch(Exception){
Console.WriteLine("Tundmatu probleem");
} finally{
Console.WriteLine("Plokk otsas");
}
}
}
/*
C:\Projects\oma\naited>Erind3
Kasuta kujul: Erind3 sisendarv
Plokk otsas
C:\Projects\oma\naited>Erind3 tere
Viga sisendandmetega: Input string was not in a correct format.
Plokk otsas
C:\Projects\oma\naited>Erind3 1234567890123456
Liiga suur arv.
Plokk otsas
C:\Projects\oma\naited>Erind3 78
Sisestati edukalt 78
Plokk otsas
*/
Püüdmine alamprogrammist
Veapüüniste tähtsaim eelis varasema veakoodinduse ees ongi kogu rakenduse
alamprogrammide rägastikus tekkinud probleemide transport konkreetsetesse kohtadesse
kokku, kus nendega üheskoos on vahel mõnevõrra kergem hakkama saada.
Järgnevas näites tekibki tõenäoline probleem alamprogrammis nimega LoeArv juhul, kui
sisendiks pole arv. Veateade aga trükitakse alles Main-meetodi juures. Nõnda võib näiteks
paluda kasutajal arvutamise jaoks anda mitu arvu. Kui aga kasvõi ühel korral sisestusel eksiti,
on tulemus ikka sama - tulemust pole võimalik kokku saada. Ning sellest antakse veapüünises
ka teada.
using System;
class Erind4{
public static int LoeArv(){
Console.WriteLine("Palun arv:");
string s=Console.ReadLine();
int a=int.Parse(s);
return a;
}
public static void Main(string[] arg){
try{
int arv1=LoeArv();
Console.WriteLine("Kirjutati: "+arv1);
}catch(FormatException probleem){
Console.WriteLine("Viga teisendusel: "+probleem.Message);
}
}
}
/*
D:\kodu\0606\opikc#>Erind4
Palun arv:
5
Kirjutati: 5
*/
Erindi heitmine
Sugugi ei pea leppima vaid arvuti enese antud veateadetega. Kui ikka oma programmis
paistab, et midagi läheb väga käest ära, siis on vahel kasulik ise märku anda, et sarnaselt edasi
toimida pole enam mõtet. Näiteks, kui arvutuse algandmed on ilmselgelt valed (kolmnurga üks
külg pikem kui teised kaks kokku), siis võib julgesti enne arvutamist teada anda, milles asi
ning heita selleteemalise erindi. Edasi on juba vastavat koodilõiku väljakutsuva
programmeerija ülesandeks silumise käigus kindlaks teha, millest probleem tekkis ning
vastavalt edasi toimida.
Siin näites lihtsalt keelati sajast suuremate arvude sisestus. Kui arv juhtub liiga suur olema,
heidetakse erind. Lihtsuse mõttes pole oma tüüpi loodud, kasutatakse SystemExceptionit.
Kuigi - vähegi pikema programmi selguse huvides oleks oma tüübi loomine kasulik. Et
peaprogrammis pole SystemExceptioni jaoks veapüünist, siis tuleb ette süsteemne veateade,
mille järele programmeerija peab juba ise edasi mõtlema, mida edasi teha.
using System;
class Erind5{
public static int LoeArv(){
Console.WriteLine("Palun arv:");
string s=Console.ReadLine();
int a=int.Parse(s);
if(a>100){
throw new SystemException("Liiga suur arv");
}
return a;
}
public static void Main(string[] arg){
try{
int arv1=LoeArv();
Console.WriteLine("Kirjutati: "+arv1);
}catch(FormatException probleem){
Console.WriteLine("Viga teisendusel: "+probleem.Message);
}
}
}
/*
D:\kodu\0606\opikc#>Erind5
Palun arv:
789
Unhandled Exception: System.SystemException: Liiga suur arv
at Erind5.LoeArv()
at Erind5.Main(String[] arg)
*/
Ülesandeid
* Katseta näite "Erind2" juures, kuidas käitub programm juhul, kui ette anda veatu arv.
* Muuda täisarvu käsklused reaalarvu omadeks ja leia, mis kasutamisel muutus.
* Loo tsükkel, mille abil küsitakse arvu senikaua, kuni saadakse sobiv sisend.
* Muuda näidet "Erind5" nõnda, et see annaks peaprogrammis viisaka seletuse ka
omaheidetud erindi korral.
* Loo erindeid kasutades programm, mis suurendaks faili arv.txt sisu ühe võrra. Kui fail
puudub, või failis pole arv, siis antakse selgitusega veateade.
* Kui failis olev arv ületab 365, siis anna välja omapoolne erind ning püüa sellele reageerida.
Enum
Ikka leidub kohti, kus on võimalik teha piiratud arv valikuid. Asukoht Eestis on ühes
maakondadest. Ühissõiduk on üldjuhul rong, tramm, troll või buss jne. Kui programmikoodis
tuleb leida käitumisjuhis ühele etteantud valikutest, siis on enum hea abivahend. Maakonna
nime saab kirjutada mitmeti. Olgu siis "Harjumaa", või "Harju maakond", rääkimata suurte ja
väikeste tähtede ning tühikute erisusest. Andmebaaside puhul kasutatakse üldjunul võimalust,
et ei kirjutata inimese andmete juurde maakonna nime, vaid pannakse selle maakonna kood.
Ning selle järgi on vajadusel võimalik teisest andmetabelist järele vaadata, millise maakonnaga
siis päriselt tegu. Midagi sarnast toimub ka enumi puhul. Ehk siis vastavas loetelus
kirjeldatakse ära kõik võimalikud väärtused. Ning hiljem programmi sees pole võimalik enam
vastavat nimetust valesti kirjutada ilma, et kood kompileerimata jääks. Sedasi on võimalik
vältida vigu, mis muidu üllatavatel hetkedel võiksid avalduda. Tüüpiliseks kasutuskohaks on
näiteks alamprogrammi parameetrid, kus enumi abil määratakse, kuidas just sel korral
vastavate andmetega tuleb käituda. Järgnevas näites siis tuuaks tugevuse kohta kolm
konstanti: tumm, yhekordne ja mitmekordne. Ning praegusel juhul alamprogrammis esimese
variandi puhul jäetakse etteantud tekst sootuks trükkimata. Teisel juhul trükitakse ühe korra
ning viimasel juhul mitu korda. Kui aga yhegordne kirjutatuks nõrga g-ga, siis jäänuks kood
kompileerimata. Pealtnäha iseenesestmõistetav. Aga kui enumi asemel olnuks kasutatud
stringi, siis just sellised vead on kerged tulema.
using System;
namespace Enumeratsioon1{
enum tugevus{tumm, yhekordne, mitmekordne};
class Trykkimine{
static void Tryki(string tekst, tugevus t){
if(t==tugevus.yhekordne){
Console.WriteLine(tekst);
}
if(t==tugevus.mitmekordne){
Console.WriteLine(tekst);
Console.WriteLine(tekst);
}
}
public static void Main(string[] arg){
Tryki("Tere", tugevus.yhekordne);
}
}
}
/*
E:\jaagup\07\12>Enumeratsioon1
Tere
*/
Ülesandeid
* Loo klass vooluallika andmete hoidmiseks (pingevahemik, enum näitamaks kas tegemist
alalis- või vahelduvvooluga)
* Koosta sellistest vooluallikatest mitmesuguste väärtustega massiiv.
* Koosta alamprogramm, mis saab parameetriks soovitud pinge, voolutüübi ja vooluallikate
massiivi ning trükib välja soovitule vastavate vooluallikate andmed.
Andmekollektsioonid
Andmetega ümberkäimisele kulub märgatav osa arvutite ja programmeerija ajast. 2000 aasta
paiku arvati selleks osaks olema ligikaudu kolmandik. Nüüd ehk veidi vähem, kuid tähtsus on
ikka alles jäänud. Et põhioperatsioonidele ei kuluks liialt palju tähelepanu, selleks on
programmeerimiskeeltes välja mõeldud valmis vahendid andmeoperatsioonideks. Nii ka C#
puhul.
ArrayList
Hea lihtne koht andmete hoidmiseks ja kätte saamiseks. Võrreldes tavalise massiiviga pole
vaja elementide arvu kohe ette määrata. ArrayListi objekt hoolitseb ise selle eest, et oleks
parajalt ruumi sissepandud andmete hoidmiseks. Iga Add-käsklusega lisatakse sissepandud
väärtus olemasolevate lõppu. Käsuga Contains võib kontrollida otsitava elemendi olemasolu.
Count näitab elementide arvu. Insert-käsklus lisab uue elemendi soovitud järjekorranumbriga
kohale, lükates ülejäänud ühe koha võrra edasi. IndexOf aitab soovitud väärtust otsida.
Viimase puudumisel tagastatakse järjekorranumbrina -1. Ning foreach-tsükkel sobib kõigi
elementide läbi käimiseks.
using System;
using System.Collections;
class Kollektsioon1{
public static void Main(string[] arg){
ArrayList nimed=new ArrayList();
nimed.Add("Kati");
nimed.Add("Mati");
nimed.Add("Juku");
if(nimed.Contains("Mati")){
Console.WriteLine("Mati olemas");
}
Console.WriteLine("Nimesid kokku "+nimed.Count);
nimed.Insert(1, "Sass");
Console.WriteLine("Mati asub kohal "+nimed.IndexOf("Mati"));
Console.WriteLine("Mari asub kohal "+nimed.IndexOf("Mari"));
foreach(string eesnimi in nimed){
Console.WriteLine(eesnimi);
}
}
}
/*
D:\kodu\0606\dotnet>Kollektsioon1
Mati olemas
Nimesid kokku 3
Mati asub kohal 2
Mari asub kohal -1
Kati
Sass
Mati
Juku
*/
Sortimine
Andmete järjestamiseks on välja mõeldud hulk algoritme, millel enamikul mõni eriline koht,
kus ta teistest kiiremini töötab. Kui aga meid rahuldab korralik "Harju keskmine" tulemus, siis
võib kasutada ArrayListile sisseehitatud käsku Sort, mis elemendid kasvavasse järjekorda
sätib.
Tahtes andmeid trükkides teada, mitmenda elemendi juures ollakse, tuleb ükshaaval neid
järjekorranumbri abil küsida. ArrayListi elemendi poole saab pöörduda sarnaselt nagu
massiivigi elemendi poole kantsulgude abil.
using System;
using System.Collections;
class Kollektsioon1a{
public static void Main(string[] arg){
ArrayList nimed=new ArrayList();
nimed.Add("Kati");
nimed.Add("Mati");
nimed.Add("Juku");
nimed.Sort();
for(int i=0; iKollektsioon1a
Juku
Kati
Mati
*/
Tüübimäärang
Eelkirjeldatud ArrayList on lahke - lubab enesesse panna ja sealt võtta igasugu andmetüüpe.
Mõnikord on see mugav, kuid vähegi pikemate programmide juures võivad kogemata nimistud
sassi minna ja näiteks sünniaasta andmed sattuda näiteks hoopis perekonnanime kohale. Et
programmeerimiskeeltes püütakse vea võimalusi vältida, siis on alates .NET 2.0st lisatud uus
nimeruum System.Collections.Generic, kus kasutatavate andmestruktuuride juures tuleb
kohe algul ära määrata, millist tüüpi andmeid kollektsiooni panna tohib. Ehk siis tekstiliste
andmete hoidmiseks sobib
LinkedList nimed=new LinkedList();
Kui tegemist oleks arvudega, siis peaks <> märkide vahel olema sõna int, mõne muu
andmetüübi puhul selle nimi. Lisamiseks ja küsimiseks mõnevõrra teistsugused käsud, aga kõik
vajaliku saab tehtud. Kuna ArrayListi puhul hoitakse andmeid mälus massiivina, siis on
arvuti jaoks lihtne ülesanne anda vastavalt järjekorranumbrile element. Samas aga jada
algusesse lisamine võib suurema andmehulga puhul ootamatult palju ressursse nõuda.
LinkedListiga on vastupidi: konkreetse elemendi poole pöördumine võib raske olla. Mööda
ahelat edasi-tagasi liikumine ning elementide lisamine või eemaldamine käib kiiresti ka pika
ahela juures.
using System;
using System.Collections.Generic;
class Kollektsioon2{
public static void Main(string[] arg){
LinkedList nimed=new LinkedList();
//lubab ainult stringe
nimed.AddLast("Kati");
nimed.AddLast("Mati");
nimed.AddLast("Juku");
if(nimed.Contains("Mati")){
Console.WriteLine("Mati olemas");
}
Console.WriteLine("Nimesid kokku "+nimed.Count);
nimed.AddAfter(nimed.Find("Kati"), "Sass");
LinkedList.Enumerator enumr=nimed.GetEnumerator();
while(enumr.MoveNext()){
string eesnimi=enumr.Current;
Console.WriteLine(eesnimi);
}
}
}
/*
D:\kodu\0606\dotnet>Kollektsioon2
Mati olemas
Nimesid kokku 3
Kati
Sass
Mati
Juku
*/
Järjekord
Näiteks graafikaülesannete juures on vajalik andmeid panna järjekorda ootele ning neid siis
sealt sissepaneku järjekorras välja küsida. Iseenesest on sarnane toiming ka LinkedListi abil
tehtav, aga juba .NET versioonis 1.0 oli selle tarvis omaette klass loodud, nimeks Queue.
Kasutamine lihtne: käsuga Enqueue lisatakse andmeid ning Dequeue võetakse neid teisest
otsast ära.
using System;
using System.Collections;
class Kollektsioon3{
public static void Main(string[] arg){
Queue jarjekord=new Queue();
jarjekord.Enqueue("Juku");
jarjekord.Enqueue("Kati");
jarjekord.Enqueue("Mati");
while(jarjekord.Count>0){
string eesnimi=jarjekord.Dequeue() as string;
Console.WriteLine(eesnimi);
}
}
}
/*
D:\kodu\0606\dotnet>Kollektsioon3
Juku
Kati
Mati
*/
Paisktabel
Vahend andmepaaride hoidmiseks. Kord indekseerimise juures juba tutvusime selle
vahendiga, siin nüüd vaatame talle veel korra otsa. Paisktabelis sobib hoida näiteks
konfiguratsioonifailist loetud omaduste väärtusi, kasutajanimele vastavaid seadeid või
tõlkefaili andmeid. Põhiliseks tingimuseks on, et võti (kasutajanimi või omaduse nimi) ei
kordu ning võtme järgi saab küsida väärtuse. Siin näites hoitakse inimeste nimedele vastavaid
hindeid.
if(ht.ContainsKey("Kati")){
Console.WriteLine("{0}", ht["Kati"]);
}
Kontrollitakse, kas Kati on nimede hulgas olemas. Kui jah, siis trükitakse ta hinne.
ht["Sass"]=((int)ht["Sass"])-1;
Sassi hinnet alandatakse ühe võrra.
ht.Remove("Mati");
Mati eemaldatakse nimekirjast.
Tahtes kõik andmed kätte saada, aitab jälle enumeraator, ainult et igal enumeraatori elemendil
on võti ja väärtus. Siin trükitakse nad lihtsalt välja, aga eks igaüks tea ise paremini, mida tal
oma programmis nendega kõige mõistlikum teha on.
IDictionaryEnumerator enumr=ht.GetEnumerator();
while(enumr.MoveNext()){
string eesnimi=enumr.Key as string;
int hinne=(int)enumr.Value;
Console.WriteLine("{0}: {1}", eesnimi, hinne);
}
Ning kogu näide tervikuna.
using System;
using System.Collections;
class Kollektsioon4{
public static void Main(string[] arg){
Hashtable ht=new Hashtable();
ht.Add("Juku", 3);
ht.Add("Kati", 5);
ht.Add("Mati", 4);
ht.Add("Sass", 4);
if(ht.ContainsKey("Kati")){
Console.WriteLine("{0}", ht["Kati"]);
}
ht["Sass"]=((int)ht["Sass"])-1;
ht.Remove("Mati");
IDictionaryEnumerator enumr=ht.GetEnumerator();
while(enumr.MoveNext()){
string eesnimi=enumr.Key as string;
int hinne=(int)enumr.Value;
Console.WriteLine("{0}: {1}", eesnimi, hinne);
}
}
}
/*
D:\kodu\0606\dotnet>Kollektsioon4
5
Kati: 5
Juku: 3
Sass: 3
*/
Ülesandeid
* Küsi kasutajalt arve, kuni ta sisestab nulli. Salvesta ArrayListi. Väljasta need arvud
tagurpidises järjekorras.
* Proovi eelmine ülesanne lahendada LinkedListi abil. Omadus Last annab loetelu viimase
elemendi, RemoveLast() kustutab viimase.
* Loe tekstifailist arvud, väljasta nad sorteerituna teise tekstifaili.
* Loe tekstifailist arvud. Teise tekstifaili väljasta, mitu korda iga arv esines.
Mallid
Objektorienteeritus võimaldab alamklasside eksemplare omistada ülemklassi tüüpi
muutujatele. Nõnda saab lahendada enamiku olukordi, kus koodilt nõutakse paindlikkust ning
võimet veidi erinevaid objekte ühiselt hoida või käidelda. Kus pole võimalik objekte omistada
muidu pärimispuu järgi, seal tuleb appi teadmine, et kõik pärineb ühisest ülemklassist
System.Object. Või siis saab eri pärimispuudest tulnud klasside ühiseid käsklusi kasutada
liideste abil. Nii et kõik vajalik peaks sellega olemas olema.
Ometigi on C# juurde kaasa võetud C++ist mallid ehk šabloonid ehk geneerilisus. Ehk siis
võimalus kasutatavaid andmetüüpe määrata pärast kasutatava klassi koodi enese
valmiskirjutamist. Sellega kaasneb vähemasti kaks head omadust:
* Kui andmetüüp on täpselt määratud, siis on karta vähem valest omistamisest tingitud vigu.
* Kompilaatoril on võimalik koodi optimeerida konkreetse andmetüübi omadustest lähtudes
ning programmi töö käigus ei pea kulutama aega tegeliku andmetüübi kontrollimisele.
Võimalust kasutatakse tihti geneeriliste andmekollektsioonide juures. Kui muidu oli
hoiustatud andmete kohta teada ainult, et need on klassi Object järglased (ehk siis nagu
polnudki tüübi kohta suurt midagi teada), siis geneerilise Listi puhul saab määrata näiteks, et
loetelus esinevad elemendid on täisarvud. Ning selle põhjal on edaspidises kasutuses teada, et
vastavast loetelust välja võetavad andmed on ka sama tüüpi. Nagu järgnevast näitest näha, siis
ka loetelu läbimiseks mõeldud enumeraatorile tuleb sama tüüp määrata.
Andmestruktuuri ülesehituse eripärast lähtudes on LinkedListi läbikäimine enumeraatori abil
tunduvalt ökonoomsem kui järjestikuste andmete küsimine elemendi järjekorranumbri järgi.
Konkreetse järjekorranumbriga kohani jõudmiseks tuleb järjekorranumbri puhul enne kõik
elemendid listisiseselt läbi jalutada. Enumeraator aga mõistab andmeid ilusti järjest võtta.
using System;
using System.Collections.Generic;
class GeneerilineList{
static void Main(string[] args)
{
LinkedList loetelu = new LinkedList();
loetelu.AddLast(5);
loetelu.AddLast(3);
LinkedList.Enumerator ahel = loetelu.GetEnumerator();
while (ahel.MoveNext()) {
Console.WriteLine(ahel.Current);
}
}
}
Geneerilisi klasse saab ka ise luua. Järgneva näitena pandi kokku lihtne väärtusehoidla. Kui
klassi nime taga on kirjeldamise ajal <> märkide vahel täht, siis seda tähte saab klassi sees
kasutada andmetüübi kirjeldamiseks. Sarnaselt, nagu võin andmetüübiks märkida int või
string, sarnaselt võin andmetüübiks kirjutada T. Ja alles pärast - siis kui klassist luuakse
eksemplar. Alles siis määratakse täpsemalt, millist tüüpi seal andmete hoidmiseks tegelikult
kasutatakse.
Kui katseprogrammi loomisel kirjutatakse, et
Hoidla h=new Hoidla();
siis sellega määratakse selles konkreetses hoidlas hoitavate väärtuste tüübiks int ning midagi
muud sinna panna ei saa.
namespace Geneeriline1{
public class Hoidla{
T sisu;
public void Pane(T sisu){
this.sisu=sisu;
}
public T Kysi(){
return sisu;
}
}
public class Katsetus{
public static void Main(string[] arg){
Hoidla h=new Hoidla();
h.Pane(3);
System.Console.WriteLine(h.Kysi());
}
}
}
Kasutatavatele andmetüüpidele saab ka mõningasi piiranguid seada - selleks, et nendega
koodis mõnevõrra rohkem midagi hiljem teha oleks. Sest ilma määramata pole isegi teada, kas
kasutatav tüüp on struct või class. Neil aga mäluhalduse poolest küllalt erinevad omadused.
Kui aga siin teatan, et T on klass, siis on vastavat tüüpi muutujale võimalik anda algväärtuseks
null näitamaks, et selle muutuja kaudu ühegi objekti juurde ligi ei pääse. Ning sealtkaudu
samuti võimalik küsida, et kas me hoidlas on juba sisu olemas.
using System;
namespace Geneeriline2{
public class Hoidla where T:class{
T sisu=null;
public void Pane(T sisu){
this.sisu=sisu;
}
public T Kysi(){
return sisu;
}
public bool KasOlemas(){
return sisu!=null;
}
}
public class Katsetus{
public static void Main(string[] arg){
Hoidla h=new Hoidla();
h.Pane("Kuku");
if(h.KasOlemas()){
System.Console.WriteLine(h.Kysi());
}
}
}
}
Enese loodud geneerilistes klassides saab kasutada ka varemvalminud geneeriliste klasside
võimalusi. Ehk siis kui on põhjust või tahtmist andmete hoidmist omale sobival moel täiustada
või piirata, siis selleks on täiesti võimalus olemas. Omaloodud klassi külge antud geneerilise
andmetüübi võib rahumeeles edasi kanda klassi sees loodud andmekollektsiooni eksemplarile
ning sinna hiljem vastavat tüüpi andmeid edastada. Siin näites on loodud klass, mis hoiab oma
andmeid Listi sees. Tuues muuhulgas aga juurde käskluse loetelust juhusliku elemendi
tagastamiseks.
using System;
using System.Collections.Generic;
namespace Geneeriline3{
public class Hoidla where T:class{
List loetelu=new List();
Random r=new Random();
public void Pane(T sisu){
loetelu.Add(sisu);
}
public T Kysi(){
return loetelu[r.Next(loetelu.Count)];
}
public bool KasOlemas(){
return loetelu.Count>0;
}
}
public class Katsetus{
public static void Main(string[] arg){
Hoidla h=new Hoidla();
h.Pane("Kuku");
h.Pane("Ahoi");
h.Pane("Tere");
if(h.KasOlemas()){
System.Console.WriteLine(h.Kysi());
}
}
}
}
Ülesandeid
* Katseta LinkedListi string-tüüpi andmetega
* Katseta LinkedListi omaloodud klassiga tüübist andmetega
* Loo klass kahe samast tüübist väärtuse hoidmiseks.
* Loo klass, mille puhul saab määrata elementide maksimummäära, mis klassi sees olevasse
hoidlasse panna tohib.
Atribuudid
Atribuute kasutakse klassi ja seal sees leiduva omaduste kirjeldamiseks. Teisele
programmeerijale võib kommentaaride teel kirja panna, et mida miski käsklus teeb või kuidas
oleks seda hea kasutada. Teine rakendus aga üldjuhul inimkeeles kirjutatud kommentaare
lugeda ei mõista. Samas on aga hea, kui saab käsule juurde panna seletuse, kuidas seda mõnes
graafilises arendusvahendis näitama peaks. Või siis teate, et seda käsku pole vaja praegu
käivitada. Atribuudid just sellisteks teadeteks mõeldud on. Mingi hulk on neid olemas .NETi
enese poolt. Kui aga omal suurema eripärase raamistiku ehitamine käsil, siis võivad ka
omaloodud atribuudid omal kohal olla. Algusnäiteks sobibki tingimuslik käivitamine.
Alamprogrammile tryki on lisatud atribuut Conditional. See süsteemne atribuut teatab, et
käsklus pannakse tööle vaid juhul, kui konstant nimega TESTSEISUND on defineeritud. Kui
sellist konstanti pole, siis jääb käskluse töö lihtsalt tegemata.
Konstanti saab kompileerimisel defineerida näiteks järgnevalt
D:\kasutaja\jaagup\proov3>csc /define:TESTSEISUND Logimine1.cs
Sellisel juhul kompilaator leiab, et konstant on olemas ning käsk tuleb oma väljakutsel käima
panna nagu tavaliselt.
Kui aga kompileerida ilma defineerimata,
D:\kasutaja\jaagup\proov3>csc Logimine1.cs
siis käsklust ei käivitata. Lihtne.
using System;
using System.Diagnostics;
class Logimine1{
[Conditional("TESTSEISUND")]
static void tryki(string teade){
Console.WriteLine(teade);
}
public static void Main(string[] arg){
tryki("algus");
}
}
/*
D:\kasutaja\jaagup\proov3>csc Logimine1.cs
Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.42
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.
D:\kasutaja\jaagup\proov3>Logimine1
D:\kasutaja\jaagup\proov3>csc /define:TESTSEISUND Logimine1.cs
Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.42
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.
D:\kasutaja\jaagup\proov3>Logimine1
algus
*/
Defineerida võib ka koodi sees. Et kui parajasti soovitakse abiteateid trükkida, siis kirjutatakse
koodi algusesse
#define TESTSEISUND
ning kompilaatori jaoks ongi vastav konstant olemas ja käsklus käivitatakse. Kui mitte, siis
mitte nagu ennegi.
#define TESTSEISUND
using System;
using System.Diagnostics;
class Logimine1a{
[Conditional("TESTSEISUND")]
static void tryki(string teade){
Console.WriteLine(teade);
}
public static void Main(string[] arg){
tryki("algus");
}
}
/*
D:\kasutaja\jaagup\proov3>csc Logimine1a.cs
Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.42
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.
D:\kasutaja\jaagup\proov3>Logimine1a
algus
*/
Omaloodud atribuut
Atribuute võib ka ise luua. Näiteks selleks, et mõningad käsklused vajadust mööda esile tuua.
Selleks tuleb teha klassi System.Attribute alamklass ning sinna vajadust mööda
konstruktorisse või omadustesse andmeid lisada. Käskluse lihtsalt ära märkimiseks pole aga
muud vaja, kui selle ette atribuut panna. Hiljem ühe klassi koodi sees teist klassi uurides saab
uudistada, et millised atribuudid viimase külge pandud on ning mis neist järeldada võib. Siin
pannakse käima sellised käsklused, millele on lisatud HuvitavMeetodAttribute. Ja enne seda
trükitakse sõna "huvitav", et oleks näha, millise käsklusega tegu on.
using System;
using System.Reflection;
namespace Atribuudid2{
[AttributeUsage(AttributeTargets.Method)]
class HuvitavMeetodAttribute: System.Attribute{
}
public class ValitudMeetodid{
[HuvitavMeetodAttribute()]
public void nuputa(){
Console.WriteLine("Rakendus nuputab");
}
public void tervita(){
Console.WriteLine("Tere");
}
}
public class AtribuudiProov{
public static void Main(string[] arg){
ValitudMeetodid v=new ValitudMeetodid();
MethodInfo[] m= typeof(ValitudMeetodid).GetMethods();
foreach(MethodInfo mi in m){
MethodAttributes ma=mi.Attributes;
foreach(Attribute at in Attribute.GetCustomAttributes(mi)){
if(at.GetType()==typeof(HuvitavMeetodAttribute)){
mi.Invoke(v, null);
Console.Write("huvitav ");
}
}
Console.WriteLine(mi.Name);
}
}
}
}
/*
D:\ctrell>Atribuudid2
Rakendus nuputab
huvitav nuputa
tervita
GetType
ToString
Equals
GetHashCode
*/
Atribuutide parameetrid
Atribuutidele kannatab ka andmeid ette anda. Olgu siis kohustuslikud väljad või vaikimisi
väärtustega valikulised väljad. Esimesed antakse konstruktori parameetrina, teised
omadustena. Ning omadustega atribuudi puhul peab andmete sisestamisel ette ütlema, millise
nime alla väärtus läheb.
Järgnevas näites antakse soovitav käivituskordade arv kaasa konstruktorist ning selle väärtuse
saab kätte omadusest Kogus. Valikuline atribuut Koostaja antakse sisse omaduse kaudu. Ning
nagu näha käskluse Tervita juurest, võib selle ka andmata jätta.
Atribuudispetsiifiliste omaduste jaoks tuleb infokogumise juurest leitud atribuut kõigepealt
sobivasse tüüpi muundada.
HuvitavMeetodAttribute ha=at as HuvitavMeetodAttribute;
Edaspidi saab sealt andmeid kätte nagu tavalisest objektist. Küsitakse, kelle loodud
käsklusega tegu, mitu korda käivitada ning ongi tegutsemisjuhised olemas.
using System;
using System.Reflection;
namespace Atribuudid3{
[AttributeUsage(AttributeTargets.Method)]
class HuvitavMeetodAttribute: System.Attribute{
private int _kogus;
private string _koostaja="tundmatu";
public HuvitavMeetodAttribute(int Ukogus){
_kogus=Ukogus;
}
public int Kogus{
get{return _kogus;}
}
public string Koostaja{
get{return _koostaja;}
set{_koostaja=value;}
}
}
public class ValitudMeetodid{
[HuvitavMeetodAttribute(3, Koostaja="Jaagup")]
public void nuputa(){
Console.WriteLine("Rakendus nuputab");
}
[HuvitavMeetodAttribute(1)]
public void tervita(){
Console.WriteLine("Tere");
}
}
public class AtribuudiProov{
public static void Main(string[] arg){
ValitudMeetodid v=new ValitudMeetodid();
MethodInfo[] m= typeof(ValitudMeetodid).GetMethods();
foreach(MethodInfo mi in m){
MethodAttributes ma=mi.Attributes;
foreach(Attribute at in Attribute.GetCustomAttributes(mi)){
if(at.GetType()==typeof(HuvitavMeetodAttribute)){
HuvitavMeetodAttribute ha=at as HuvitavMeetodAttribute;
for(int i=0; iAtribuudid3
Rakendus nuputab
Rakendus nuputab
Rakendus nuputab
Koostas Jaagup
huvitav nuputa
Tere
Koostas tundmatu
huvitav tervita
GetType
ToString
Equals
GetHashCode
*/
Ülesandeid
* Katseta omaloodud klassis tingimusliku käivitamise atribuuti
* Otsi abiinfost üles atribuut System.Obsolete ning märgi selle abil mõni omaloodud klassi
meetod vananenuks. Tutvu teadetega kompileerimisel
* Loo omaloodud atribuut meetodi loomise kuupäeva ning meetodi kohta selgitusi sisaldava
URLi hoidmiseks. Kuupäev on kohustuslik, selgituste jaoks pane vaikimisi aadressiks oma
koduleht. Koosta koodi uuriv programm, mis atribuudi olemasolul näitaks välja käskluse
loomise kuupäeva. Abiinfo URLilt saabuvad andmed salvesta võimaluse korral omaette faili.
Andmebaasiliides
Ühenduse loomine, päring
Kui juhtub, et arvutis või kättesaadavas võrgus on kasutada mõni andmebaas, siis suure
tõenäosusega saab sealsete andmete poole pöörduda ja omakoostatud C#-programmi kaudu.
Ühe-kaks väärtust võib olla lihtsam baasi enese juurde käiva halduskeskkonna abil paika
sättida. Kui aga andmeid on pidevalt ja kümneid. Või siis pole andmetega tegelejaks mitte teie
ise, vaid keegi muu. Või soovite, et teated toimingutest jõuaksid automaatselt andmebaasi –
sellisel juhul on kasulik koostatud programmilõigu peale mõelda.
Esimene näide eeldab, et masinas nimega RINDE on üles seatud SQL-serveri eksemplar
nimega SQLEXPRESS. Ning sinna sisse on loodud andmebaas nimega proovibaas. Ja selles
baasis on tabel nimega "inimesed". Ning kõige esimeseks tulbaks inimeste tabelis on eesnimi.
Sellisel juhul, kui kõik etapid õnnestuvad, võib loeteluna näha tabelis olevate inimeste
eesnimesid. Eks igaüks saab masina, tabeli ja muud andmed enese omade vastu vahetada, ning
ongi lihtne üldkasutatav näide, kuidas vajalikke andmeid baasist oma programmi sisse välja
meelitada. Mõningad lähemad seletused toimuva kohta.
Muutujasse constr (connection string) salvestatakse kõigepealt seletus programmi jaoks, kust
andmebaas üles leida.
string constr="Data Source=RINDE\\SQLEXPRESS;"+
"Initial Catalog=proovibaas; "+
"Integrated Security=SSPI; Persist Security Info=False";
Eraldi muutujasse pannakse kirja SQL-lause, mille abil loodame andmeid küsida.
string lause="SELECT eesnimi FROM inimesed";
Järgnevalt luuakse ja avatakse ühendus andmebaasiga. See toiming võib mõnikord märgatava
hulga sekundeid aega võtta.
SqlConnection cn=new SqlConnection(constr);
cn.Open();
Et arvuti oskaks andmeid küsida, selleks luuakse SQL-käsklus, kus baasiühendusega seotakse
SQL-lause. Siinse vaheetapi juures on keerulisematel juhtudel näiteks võimalus käsklusele
parameetreid lisada. Siin aga piirduma lihtsama variandiga.
SqlCommand cm = new SqlCommand(lause, cn);
Saabuvate andmete püüdmiseks on SQL-serveri puhu SqlDataReader. SqlCommandi käsklus
ExecuteReader väljastab vastavat tüüpi objekti, mille kaudu programm omakorda saab
andmeid küsima hakata. Selline vaheetapp on vajalik, et programm saaks vajadusel hakkama
ka väga suure andmehulgaga. Kui andmete vahendamise jaoks on omaette objekt, kelle kaudu
vaikselt andmeid küsima hakatakse, siis ei pea programm saabuvaid andmeid kõiki korraga
enesele mällu laadima, vaid jätab selle töö SqlDataReaderi hooleks.
SqlDataReader reader=cm.ExecuteReader();
Alles edaspidises tsüklis võetakse inimeste andmed ükshaaval ja toimetatakse nendega.
Käsklus Read viib lugemiskursori ühe rea võrra edasi – esimesel korral siis esimese inimese
juurde. Ning käsklus GetString annab etteantud järjekorranumbriga veerust andmed kätte.
Nagu näha, hakkavad veerud lugema nullist.
while(reader.Read()){
Console.WriteLine(reader.GetString(0));
}
Iga ühenduse kasutamise järel on viisakas see kinni panna. Mis juhtub, kui ühendus lahti
jäetakse sõltub juba otsestest oludest. Aga kinni pandult on ressursside raiskamise mure
programmeerija käest ära.
cn.Close();
Ning kood tervikuna.
using System;
using System.Data.SqlClient;
class Baasiproov1{
public static void Main(string[] arg){
string constr="Data Source=RINDE\\SQLEXPRESS;"+
"Initial Catalog=proovibaas; "+
"Integrated Security=SSPI; Persist Security Info=False";
string lause="SELECT eesnimi FROM inimesed";
SqlConnection cn=new SqlConnection(constr);
cn.Open();
SqlCommand cm = new SqlCommand(lause, cn);
SqlDataReader reader=cm.ExecuteReader();
while(reader.Read()){
Console.WriteLine(reader.GetString(0));
}
cn.Close();
}
}
/*
D:\kodu\0606\dotnet>Baasiproov1
Juku
Mati
Sass
*/
Kui soovitakse baasiga rohkem inimkeeli suhelda ja küsida andmeid veeru pealkirja ja mitte
järjekorranumbri järgi, siis sobib arvuti jaoks suupäraseks teisendamiseks SqlDataReaderi
käsklus GetOrdinal.
using System;
using System.Data.SqlClient;
class Baasiproov1a{
public static void Main(string[] arg){
string constr="Data Source=RINDE\\SQLEXPRESS;"+
"Initial Catalog=proovibaas; "+
"Integrated Security=SSPI; Persist Security Info=False";
string lause="SELECT eesnimi FROM inimesed";
SqlConnection cn=new SqlConnection(constr);
cn.Open();
SqlCommand cm = new SqlCommand(lause, cn);
SqlDataReader reader=cm.ExecuteReader();
while(reader.Read()){
Console.WriteLine(reader.GetString(reader.GetOrdinal("eesnimi")));
}
cn.Close();
}
}
/*
D:\kodu\0606\dotnet>Baasiproov1
Juku
Mati
Sass
*/
Sugugi alati ei pruugi kasutatavad andmed olla Microsofti SQL-serveris. Levinud veidi
väiksemate andmetega ümber käimise programmiks on näiteks Access. Kui saab talle sobiva
draiveriga sobiva versiooni faili külge minna, siis on täisesti lootust ka nõnda otse sidet
pidada. Kusjuures nõnda programmi kaudu andmeid lugedes/kirjutades ei pea Accessi ennast
üldse olema masinasse installeeritud. Piisab vaid sobivast draiverist, mis on üldjuhul juba
operatsioonisüsteemiga kaasas.
using System;
using System.Data.Odbc;
class Baasiproov2a{
public static void Main(string[] arg){
string constr="Driver={Microsoft Access Driver (*.mdb)}; "+
"DBQ=d:\\kodu\\0606\\dotnet\\proovibaas2.mdb; "+
"Trusted_Connection=yes";
string lause="SELECT mark FROM autod";
OdbcConnection cn=new OdbcConnection(constr);
cn.Open();
OdbcCommand cm = new OdbcCommand(lause, cn);
OdbcDataReader reader=cm.ExecuteReader();
while(reader.Read()){
Console.WriteLine(reader.GetString(0));
}
cn.Close();
}
}
Kui aga tegemist pole Accessiga, vaid mõne muu andmebaasikeskkonnaga, mis aga
sellegipoolest on Control Paneli kaudu ODBC alt kättesaadav, siis ka sealtkaudu saab oma
andmetele ligi. Olgu näiteks olemas juba toimiv veebibaas PHP ja MySQLi abil – kuhu aga
tahetakse ka kohalikus arvutis toimiva programmi kaudu pilku peale visata. Tehes see algne
baas ODBC kaudu nähtavaks nime all näiteks proovibaas2, näeks sidepidamisprogramm välja
nagu järgnevalt. Tasub tähele panna, et võrreldes SQL-serveriga on kasutatavateks
objektiklassideks OdbcConnection ja OdbcCommand. Aga andmetega ümber käiakse ikka
samamoodi.
using System;
using System.Data.Odbc;
class Baasiproov2{
public static void Main(string[] arg){
string constr="DSN=proovibaas2";
string lause="SELECT mark FROM autod";
OdbcConnection cn=new OdbcConnection(constr);
cn.Open();
OdbcCommand cm = new OdbcCommand(lause, cn);
OdbcDataReader reader=cm.ExecuteReader();
while(reader.Read()){
Console.WriteLine(reader.GetString(0));
}
cn.Close();
}
}
Kui baasist küsitakse vaid ühte väärtust ja mitte tervet tabelit, siis selle tarvis on .NET
andmebaasi programmeerimise vahendite juurde loodud lihtne ja asjalik käsklus:
ExecuteScalar. Küsitakse vaid inimeste arv, siis ühe käsuga saab selle kätte ilma, et peaks
vahepeal andmepuhvrit looma.
using System;
using System.Data.SqlClient;
class Baasiproov1b{
public static void Main(string[] arg){
string constr="Data Source=RINDE\\SQLEXPRESS;"+
"Initial Catalog=proovibaas; "+
"Integrated Security=SSPI; Persist Security Info=False";
string lause="SELECT COUNT(*) FROM inimesed";
SqlConnection cn=new SqlConnection(constr);
cn.Open();
SqlCommand cm = new SqlCommand(lause, cn);
Console.WriteLine("Inimeste arv: "+cm.ExecuteScalar());
cn.Close();
}
}
/*
D:\kodu\0606\dotnet>Baasiproov1b
Inimeste arv: 3
*/
Andmete lisamine
Eraldi moodus on käskluste jaoks, mis pole päringud: lisamine, muutmine, kustutamine. SQL-
lause tuleb valmis teha nagu ikka, käivitamise jaoks aga käsklus ExecuteNonQuery.
using System;
using System.Data.SqlClient;
class Baasiproov1c{
public static void Main(string[] arg){
string constr="Data Source=RINDE\\SQLEXPRESS;"+
"Initial Catalog=proovibaas; "+
"Integrated Security=SSPI; Persist Security Info=False";
string lause="INSERT INTO inimesed (eesnimi) VALUES ('Siiri')";
SqlConnection cn=new SqlConnection(constr);
cn.Open();
SqlCommand cm = new SqlCommand(lause, cn);
cm.ExecuteNonQuery();
Console.WriteLine("Andmed lisatud");
cn.Close();
}
}
/*
D:\kodu\0606\dotnet>Baasiproov1c
Andmed lisatud
*/
SQL-parameeter
Tahtes kasutaja andmeid SQL-lausesse lisada – olgu siis andmete küsimisel piirangu
seadmiseks või andmete lisamisel salvestamiseks – on seda hea teha parameetri kaudu. Sellisel
juhul ei pea SQL-lauset koostades muret tundma, et kasutaja sisestus kuidagi jutumärke või
ülakomasid omavahel sõlme võiks ajada. Samuti püsib nõnda ka lause ülesehitus selgemana –
lihtsalt @-märgiga tähistatud sõna asendatakse pärast sobiva väärtusega. Siin tähistatakse
@algaastaga arv, millisest aastast alates sündinud laste nimesid tahetakse näha.
string lause="SELECT eesnimi, synniaasta FROM lapsed "+
"WHERE synniaasta >= @algaasta";
Enne käskluse käivitamist tuleb siis parameetri kohta sobiv väärtus paigutada. Siin näites on
kaks toimingut ühel real. Kõigepealt lisatakse käsklusele cm parameeter nimega @algaasta.
Seejärel omistatakse loodud objekti omadusele Value väärtus 1997. Viimatine aastaarv on
praegu küll kirjutatud lühiduse mõttes arvuna koodi, aga sinna saab paigutada kasutajalt
tulnud väärtuse.
cm.Parameters.Add("@algaasta", SqlDbType.Int).Value=1997;
Vastuseks saame andmete loetelu nii nagu ikka.
using System;
using System.Data;
using System.Data.SqlClient;
class Baasiparameeter1{
public static void Main(string[] arg){
string constr="Data Source=RINDE\\SQLEXPRESS;"+
"Initial Catalog=baas1; "+
"Integrated Security=SSPI; Persist Security Info=False";
string lause="SELECT eesnimi, synniaasta FROM lapsed "+
"WHERE synniaasta >= @algaasta";
SqlConnection cn=new SqlConnection(constr);
cn.Open();
SqlCommand cm = new SqlCommand(lause, cn);
cm.CommandType=CommandType.Text;
cm.Parameters.Add("@algaasta", SqlDbType.Int).Value=1997;
SqlDataReader reader=cm.ExecuteReader();
while(reader.Read()){
Console.WriteLine(reader.GetString(0)+": "+
reader.GetInt32(1));
}
cn.Close();
}
}
/*
D:\kodu\0606\dotnet>Baasiparameeter1
Juku: 1997
Kati: 1997
Siim: 1997
*/
Salvestatud protseduur
Tahtes programmi kaudu salvestatud protseduurile andmeid jagada on kasulik jällegi
parameetrit pruukida. Parameetrile antakse nime järgi väärtus ning protseduur saabki selle
kätte. Siinne protseduur loodi käsuga
CREATE PROCEDURE kysiLapsed(@algaasta decimal)
AS
SELECT eesnimi, synniaasta FROM lapsed
WHERE synniaasta>=@algaasta
Andmete lugemine tabeli väljastavast salvestatud protseduurist on sarnane hariliku päringu
vastuste lugemisele. Ning tulemus on samuti eelmise näitega sarnane.
using System;
using System.Data;
using System.Data.SqlClient;
class Baasiparameeter1{
public static void Main(string[] arg){
string constr="Data Source=RINDE\\SQLEXPRESS;"+
"Initial Catalog=baas1; "+
"Integrated Security=SSPI; Persist Security Info=False";
string lause="kysiLapsed";
SqlConnection cn=new SqlConnection(constr);
cn.Open();
SqlCommand cm = new SqlCommand(lause, cn);
cm.CommandType=CommandType.StoredProcedure;
cm.Parameters.Add("@algaasta", SqlDbType.Int).Value=1997;
SqlDataReader reader=cm.ExecuteReader();
while(reader.Read()){
Console.WriteLine(reader.GetString(0)+": "+
reader.GetInt32(1));
}
cn.Close();
}
}
/*
D:\kodu\0606\dotnet>Baasiparameeter2
Juku: 1997
Kati: 1997
Siim: 1997
*/
Ülesandeid
* Loo andmebaasitabelid: maakonnad (id, maakonnanimi), autod (id, mark, aasta,
maakonna_id). Lisa mõned andmed.
* Küsi programmi abil ekraanile kõikide maakondade nimed
* Küsi ekraanile kõikide autode andmed koos maakondade nimedega, kus nad registreeritud.
* Loo käsklus uue auto lisamiseks. Sisendiks mark, aasta, maakonna_id.
* Olematu id-ga maakonna puhul näidatakse olemasolevaid maakondade nimesid ja id-sid
ning palutakse viimane uuesti sisestada.
* Loo võimalus etteantud id-ga auto maakonna muutmiseks.
* Loo salvestatud protseduur näitamaks enne parameetrina antud aastat tehtud autosid.
Käivita protseduur ja vaata tulemusi C# kaudu.
* Loo klassid auto ning maakonna andmete hoidmiseks. Katseta paari väärtusega.
* Loo klass AutoRegister toimetamaks andmebaasis paiknevate autodega. Järjekorranumbri
abil peetakse meeles jooksvat autot, samuti on klassi eksemplaril meeles baasis leiduvate
autode id-d (ArrayListina). Lisa klassile käsud jooksva auto küsimiseks, andmete muutmiseks,
id järgi järgmise/eelmise auto juurde liikumiseks, auto lisamiseks lõppu. Andmete küsimisel
väljastatakse auto klassi eksemplar, mis viitab maakonnaeksemplarile.
* Ehita AutoRegistri klassi ümber kasutajaliides, mille kaudu on võimalik käsurealt autode
andmetega toimetada: loetelu vaadata, aktiivset autot üles/alla määrata, aktiivse auto andmeid
muuta, autosid lisada/kustutada. Autosid soovitud tunnuse alusel sorteerida/filtreerida.
Funktsiooni delegaadid
Kui on programmi käitumise juures vaja valida mitme võimaluse vahel, siis tavalisimaks
vahendiks on if/else plokk, millega tööjärge suunata.
Objektorienteeritud programmeerimese juures kui soovitakse objektid sarnastes kohtades
erinevalt käituma panna, siis luuakse algsele klassile iga märgatavalt erineva käitumismooduse
jaoks alamklass ning selles kaetakse vastav meetod üle nõnda nagu see parasjagu vajalik on.
Hiljem saab soovitud käitumise väljakutsumise jaoks anda sobivasse kohta ette õige alamklassi
sobivalt seadistatud eksemplari. Nõnda tuleb näiteks ArrayListi omapoolselt määratud
järjestusse sorteerides ette anda liidest IComparer realiseeriva klassi eksemplar, mille juures
tuleb üle katta funktsioon nimega Compare. Nimetatud meetodi ülesandeks on kahe etteantud
objekti põhjal otsustada, kumb neist järjestuses ettepoole saab. Sedasi võib tekstid järjestada
näiteks pikkuse ja mitte tähestiku järgi.
Kes on esimese keelena kirjutanud Cs või C++is, sel tekib nõnda alamklasse luues kohe
küsimus, et miks nii keeruliselt peab tegema. Et eelnimetatud keeltes kasutatakse selliseks
sortimisjärjekorra määramiseks funktsiooniviitu. Kuna C# püüab eneses ühendada rangemate
keelte struktueeritust ning masinalähedasemate keelte võimalusi, siis on siia loodud
funktsiooni delegaadid. Lahtiseletatult on tegemist eripärase muutujaga, mille kaudu saab selle
külge omistatud funktsiooni välja kutsuda. Seletus järgmise näite põhjal.
Tervituseks on loodud kaks funktsiooni: RahulikTervitus ning TragiTervitus. Programmi
mingis osas saab valida, millist funktsiooni neist kasutatakse. Et funktsiooni delegaadina
meelde jätta, tuleb see programmi algul deklareerida.
public delegate void Tervitusfunktsioon();
Edasi võib sobival ajal omistada loodud delegaatmuutujale tegeliku funktsiooni eksemplari.
Tervitusfunktsioon tervitaja=new Tervitusfunktsioon(RahulikTervitus);
Delegaatmuutujale sulgude taha kirjutamisega pannaksegi soovitud funktsioon tööle.
tervitaja();
Ja tulemusena näeme sel korral lihtsat "Tere".
using System;
public delegate void Tervitusfunktsioon();
class Delegaat1{
static void RahulikTervitus(){
Console.WriteLine("Tere");
}
static void TragiTervitus(){
Console.WriteLine("Ahoi!");
}
public static void Main(string[] arg){
Tervitusfunktsioon tervitaja=new Tervitusfunktsioon(RahulikTervitus);
tervitaja();
}
}
/*
D:\kodu\0606\dotnet>Delegaat1
Tere
*/
Funktsioonide komplekt
Mõnikord tuleb talletada terve tegevuste jada. Et delegaatmuutujad käituvad küllalt tavaliste
objektide sarnaselt, saab neid ka kogumis (siin ArrayList) talletada ja pärast välja kutsuda.
Sedasi on võimalik ühes koodiosas teine käsujada kokku panna ja seda hiljem pruukida.
using System;
using System.Collections;
public delegate void Tervitusfunktsioon();
class Delegaat2{
static ArrayList tervitused=new ArrayList();
static void RahulikTervitus(){
Console.WriteLine("Tere");
}
static void TragiTervitus(){
Console.WriteLine("Ahoi!");
}
public static void Main(string[] arg){
tervitused.Add(new Tervitusfunktsioon(RahulikTervitus));
tervitused.Add(new Tervitusfunktsioon(TragiTervitus));
tervitused.Add(new Tervitusfunktsioon(RahulikTervitus));
foreach(Tervitusfunktsioon tervitaja in tervitused){
tervitaja();
}
}
}
/*
D:\kodu\0606\dotnet>Delegaat2
Tere
Ahoi!
Tere
*/
Sündmused
Sarnane käskude jada salvestamine on mõnes olukorras nõnda tavaline, et selle jaoks on eraldi
välja mõeldud tüüp event. Nii nagu võis Add käsuga lisada andmeid ArrayListi, nii saab +=
operaatoriga panna juurde funktsioonidelegaate sündmuste loendisse. Ja tulemus sarnane nagu
eelmisel korral. Ette rutates märkides kasutatakse sellist delegaatide sündmusteahelasse
lisamist näiteks graafilistes programmides nupuvajutuse korral, kus iga nupu alla võib panna
käivituma mitu funktsiooni. Siin aga lihtsalt taas tervitused.
using System;
public delegate void Sisenemine();
class Syndmused1{
static event Sisenemine InimeneSisenes;
static void RahulikTervitus(){
Console.WriteLine("Tere");
}
static void TragiTervitus(){
Console.WriteLine("Ahoi!");
}
public static void Main(string[] arg){
InimeneSisenes+=new Sisenemine(RahulikTervitus);
InimeneSisenes+=new Sisenemine(TragiTervitus);
InimeneSisenes+=new Sisenemine(RahulikTervitus);
InimeneSisenes();
}
}
/*
D:\kodu\0606\dotnet>Syndmused1
Tere
Ahoi!
Tere
*/
Ilmajaamad
Järgnevalt eelneva põhjal kokku pandud näide, kus piirkonda hajusalt paigutatud 10
vaatlusjaama omi mõõteandmeid keskusesse saadavad ning kuidas saabunud andmetele
reageeritakse.
Peaprogramm loob kõigepealt ilmajaamade objektid, siis määrab nende juures, milline
funktsioon panna käima tavateate juures ning mida teha juhul, kui tegemist on mõne
hoiatusega. += operaator lubab vajadusel panna vastava sündmuse külge ka mitu teadet või
jätta sootuks ilma teateta.
Edasi palutakse kõigil jaamadel välja mõelda (mõõta) andmed ning keskusesse saata teated
nende kohta. Ja siis küsitakse kasutajalt, kas ta soovib veel uuesti andmeid küsida. Et teabe
trükkimise funktsioonid olid juba ilmajaamade teatesündmuste külge lisatud, siis lähevad nad
sündmuste käivitamisel ise käima.
Jaamal on meeles enese number. Teate saatmisel küsitakse andurilt temperatuur ning pannakse
kokku keskusesse minev vajalik andmeplokk. Temperatuuri küsimisel arvestab programm kuu
järjekorranumbrit ning selle suhtes arvutab välja sellele aastaajale tõenäolise temperatuuri -
lihtsalt üks viis kuigivõrd usutavaid vastuseid simuleerida.
int kysiTemperatuur(){
return 15-Math.Abs(6-System.DateTime.Now.Month)*5+
arvugeneraator.Next(20);
}
Et sündmuse käivitamisel saaks sinna andmeid kaasa saata, selleks peavad andmed olema koos
ühes terviklikus objektis ning selle objekti klass peab olema klassi EventArgs alamklass - mis
siis lubab neil andmetel teatega kaasa minna.
public class IlmajaamaParameetrid: EventArgs{
...
}
Kui jaam teateid saadab, siis kõigepealt pannakse kokku eelnäidatud tüübist andmeplokk.
Edasi käivitatakse igal juhul tavateate saatmine (sündmus) ning kui põhjust, siis selle järel ka
hoiatusteate saatmine. Mis teatesaatmissündmuse puhul tegelikult tehakse, on eespool
ilmajaama loomise juures juba kirjeldatud.
public void saadaTeated(){
IlmajaamaParameetrid andmed=new IlmajaamaParameetrid(
jaamaNr, ++teateNr, kysiTemperatuur());
tavaTeade(andmed);
if(andmed.temperatuur<0 ||
andmed.temperatuur>20){hoiatusTeade(andmed);}
}
Ja ongi kogu näide, mida võib edaspidi sarnaste rakenduste loomisel aluseks võtta.
using System;
namespace Ilmaandmed{
public delegate void IlmajaamaTeade(IlmajaamaParameetrid p);
class Syndmused2{
static void HarilikTeave(IlmajaamaParameetrid p){
Console.WriteLine(p);
}
static void HoiatusTeave(IlmajaamaParameetrid p){
Console.WriteLine(" Hoiatus: "+p);
}
public static void Main(string[] arg){
Ilmajaam[] jaamad=new Ilmajaam[10];
for(int i=0; i20){
hoiatusTeade(andmed);}
}
}
public class IlmajaamaParameetrid: EventArgs{
public readonly int temperatuur;
public readonly int jaamaNr;
public readonly int teateNr;
public IlmajaamaParameetrid(int uusJaamaNr, int uusTeateNr,
int uusTemperatuur){
temperatuur=uusTemperatuur;
jaamaNr=uusJaamaNr;
teateNr=uusTeateNr;
}
public override string ToString(){
return "Jaam "+jaamaNr+", teade "+teateNr+" temperatuur "+
temperatuur;
}
}
}
/*
D:\kodu\0606\dotnet>Syndmused2
Jaam 1, teade 1 temperatuur 14
Jaam 2, teade 1 temperatuur 25
Hoiatus: Jaam 2, teade 1 temperatuur 25
Jaam 3, teade 1 temperatuur 15
Jaam 4, teade 1 temperatuur 26
Hoiatus: Jaam 4, teade 1 temperatuur 26
Jaam 5, teade 1 temperatuur 16
Jaam 6, teade 1 temperatuur 27
Hoiatus: Jaam 6, teade 1 temperatuur 27
Jaam 7, teade 1 temperatuur 17
Jaam 8, teade 1 temperatuur 28
Hoiatus: Jaam 8, teade 1 temperatuur 28
Jaam 9, teade 1 temperatuur 18
Jaam 10, teade 1 temperatuur 29
Hoiatus: Jaam 10, teade 1 temperatuur 29
Kas veel?
j
Jaam 1, teade 2 temperatuur 12
Jaam 2, teade 2 temperatuur 18
Jaam 3, teade 2 temperatuur 23
Hoiatus: Jaam 3, teade 2 temperatuur 23
Jaam 4, teade 2 temperatuur 29
Hoiatus: Jaam 4, teade 2 temperatuur 29
Jaam 5, teade 2 temperatuur 15
Jaam 6, teade 2 temperatuur 21
Hoiatus: Jaam 6, teade 2 temperatuur 21
Jaam 7, teade 2 temperatuur 27
Hoiatus: Jaam 7, teade 2 temperatuur 27
Jaam 8, teade 2 temperatuur 13
Jaam 9, teade 2 temperatuur 19
Jaam 10, teade 2 temperatuur 25
Hoiatus: Jaam 10, teade 2 temperatuur 25
Kas veel?
e
*/
Graafiline liides
Lõppu veel näide, kuidas sündmusi graafilise keskkonna juures pruugitakse.
Windowsi aknarakenduse puhul on lihtsaks võimaluseks luua oma rakendus klassi Form
alamklassina. Graafilised elemendid saab klassi algul ära kirjeldada ning konstruktoris
ekraanile paigutada. Ekraanikoordinaatide järgi paigutus akna ülanurga suhtes on üks
võimalusi. Kõigepealt määratakse graafikakomponendid asukohapunkti järgi paika ning
Controls.Add käsu abil jõuavad nad sinna nähtavana ekraanile. Tahtes nupuvajutussündmuse
peale miskit käivitada, saab eelpoolvaadatud += operaatori abil lisada omaloodud funktsiooni
Click-nimelisele sündmusele.
nupp1_Click funktsioonile tulevad kaks parameetrit. Esimese kaudu saab eristada millist
nuppu vajutati - juhul kui on oht, et sündmusi võib mitmelt poolt tulla. ning teisest võiks
teavet saada näiteks hiire andmete kohta vajutushetkel. Aga kuna praegu meil on tegemist
ainukese nupuga ning soovime käima panna lihtsa teretuse, siis pole meil neid parameetreid
endid pruukida vaja.
using System;
using System.Windows.Forms;
using System.Drawing;
class Tervitaja2:Form{
Button nupp1=new Button();
TextBox tekst1=new TextBox();
public Tervitaja2(){
nupp1.Location=new Point(20, 30);
tekst1.Location=new Point(20, 60);
nupp1.Text="Vajuta";
Controls.Add(nupp1);
Controls.Add(tekst1);
nupp1.Click+=nupp1_Click;
}
void nupp1_Click(object saatja, EventArgs e){
tekst1.Text="tere";
}
public static void Main(string[] arg){
Application.Run(new Tervitaja2());
}
}
Ülesandeid
* Uuri näidet Delegaat1, lisa sinna funktsioon UinuvTervitus, mis trükiks "brrr". Katseta.
* Muuda näidet Syndmused1 nõnda, et lisanduks sündmus InimeneUinus, kuhu oleks tsükli
abil 10 korda lisatud UinuvTervitus. Katseta.
* Lisa ilmajaama näitele tuule kiirus.
* Koosta nupuvajutusnäite abil graafiline kalkulaator, mille abil saab sentimeetreid tollideks ja
tagasi arvutada.
Visual Studio C# Expressi install
Programmikoodi kirjutamise ja käivitamise jaoks on loodud mitmesuguseid abivahendeid.
Microsofti ametlikuks graafiliseks arenduskeskkonnaks on Visual Studio. Express-versioonid
sellest on kõigile vabalt kasutatavad. Tutvumiseks ja allalaadimiseks sobib veebiaadress
http://www.microsoft.com/express.
Sealt pääseb edasi allalaadimise juurde. Pakutakse kõikide Express-versioonide korraga alla
laadimist või üksikute toodete installeerimist veebi kaudu. Ühe toote ühekordseks
paigaldamiseks piisab veebikaudsest installeerimisest. Valime siit sobiva programmi.
Kui ainult ühte masinasse panna, siis võib programmi kohe käivitada. Algul laetakse kohale
vaid lühike, kahemegabaidine lõik kontrollimaks, mis masinas olemas ning mida vaja sinna
juurde laadida.
Pärast laadimist võib programmi käivitada.
Sõltuvalt masina seadetest võidakse küsida, et kas tohib rakendus ise luua ühenduse
välisvõrguga. Paigalduse jätkamiseks pole muud teha kui lubada.
Edasi programmi käivitus ning masina ülevaatus
ning asutakse võrgust vajalikke lisatükke tõmbama. Praegusel juhul alla saja megabaidi. Aga
kui lisavõimalusi rohkem valitud, siis võib ka see kogus mitmekordistuda ning aega rohkem
minna.
Küsitakse asukoht programmifailide jaoks. Kui põhikettal piisavalt ruumi, siis võib vaikimisi
asukoht „Program files“i alla jääda. Kui aga näiteks C-ketas täis ja mõnes muus kohas ruumi,
siis on lootust osa asju sinna peita - ehkki vahel kipub tekkima olukord, kus siis kõike kohe
üles ei leita. Ja litsentsilepinguga nõustumisest ei pääse me ka seekordse installatsiooni käigus.
Kui kõik alla laetud, siis järgmine samm tegeliku installeerimise juurde. Paarkümmend minutit
võib kõikide komponentide peale panekuks julgesti aega minna, vahel ka rohkem. Õnneks
progressiribalt on näha, kaugel tegelikult ollakse.
Ja kui rakendus peal, siis palutakse see registreerida. Muidu hakatakse kuu aja pärast kasutajat
kiusama, et see ikka märku annaks, kes ta selline on, kes siinset tasuta tarkvara kasutab.
Suurema installeerimise juures ei pääse sageli ka restardist. Nii ka siin.
Esmasel käivitamisel kulub tavapärasest mõnevõrra pikem aeg, aga seda ka hoiatatakse.
Et hiljem poleks registreerimisega muret, võib selle kohe ära teha. Help-menüüst tuleb
alustuseks vastav rida valida. Saabuvasse aknasse palutakse siis registreerimisvõtit ning
pakutakse viide selle saamiseks.
Kel Live ID ehk siis MSNi kasutajatunnus olemas, sel käib asi lihtsalt – suunatakse sobivale
veebilehele. Kel mitte, peab registreerumiseks omale vastava tunnuse tegema.
Kontrollitakse üle, et ega mu eelistused vahepeal muutunud pole
ning antaksegi pikk täherida vastuseks.
Viimane tuleb tollesse help-menüüst avanenud aknasse kopeerida ning võibki registreerumise
lõppenuks lugeda.
Esimese rakenduse loomine
Keskkonna kaudu kompileerimise eeltingimuseks on projekti olemasolu. Eks ikka failimenüüst
uus.
Saab küll valida sobivate valmisprojektide hulgast ning rakenduse kohe tööle panna. Aga kui
tahtmist ise klasse ja nimeruume nimetada, siis tühja projektiga lihtsam.
Nimeks näiteks Avaprojekt. Ning et pärast ei peaks faile mööda masinat taga otsima, selleks
sobib projekt kohe ka omale sobivasse kausta salvestada. Ning kui liigselt alamkatalooge ei
taha, siis tasub linnuke „Create directory for solution“ tühjaks jätta – üks kaust tehakse
nagunii.
Projekti sisse siis uus klass, et oleks kuhugile kood kirjutada.
Klassile oma jaoks arusaadav nimi
ning ongi koht kus koodi kirjutada.
Main-meetod sisse, et kood mõistaks kusagilt tööd alustada
Ja juba võib proovida käivitada. Debug->Start Without Debugging peaks rakenduse lihtsalt
käima panema ning pärast ka akna ette jätma, et võimalust töö tulemusi imetleda oleks.
using System.Linq rea peale aga kiputakse jorisema – et miski teek puudu. Praeguses näites
võiks küll vastava rea rahus välja võtta, sest ega tegelikult siin Linq-päringutega midagi ei
tehta. Aga kui tuleviku peale mõelda ning arvata, et sellest laiendusest võiks kasu olla, siis
tasub vastav teek projektile külge haakida. Viidete (References) juurest parema klahviga Add
Reference
ning vastava abitüki leiab teegist System.Core.
Edasi võiks käivitamine juba lihtsamalt minna.
Ehk siis „Tere“ tuligi nähtavale.
Sellisest lähenemisest enamasti piisabki. Aga kui tahta end mõnikord mitte keskkonna
trikkidest häirida lasta ning vaadata, kuidas programm „tavalise“ kasutaja juures käituda võiks,
siis saab sedagi. Kõigepealt lahti käsureaaken
Edasi CD-ga sobivasse kataloogi ning võib tekkinud exe-faili ka sealt käima panna.
Tulemuseks ikka sama „Tere“.
Kokkuvõte
Eelpoolsetel lehekülgedel alustati C# pisikese tervitava programmiga ning käidi läbi enamik
tähtsamaid keele süntaksi ja ülesehituse juurde kuuluvaid teemasid. Hulk nüansse ja erijuhte
jäi käsitlemata, kuid olemasolevate põhjal peaks õnnestuma enamik enesele tarvilikke
programme kokku panna ja ette juhtunud valmisprogrammidest aru saada. Samuti võiks pärast
siinse kirjutise läbitöötamist tekkida piisav karkass ja kogemus, mille külge on kergem mujalt
leitud C# ja objektorienteeritusega seotud lõike haakida.
Siinsed programmid käivitati käsurealt. Kuid nagu lõpunäitest näha, võib ka
käsurearakendustel olla täiesti graafiline liides. Kui edasi uurida System.Windows.Forms
nimeruumi käske ja näiteid, siis sealtkaudu võib oma rakendusele üha uusi graafilisi vidinaid
juurde lisada. Kui veel käivitada mitte mustast käsureaaknast vaid seada töölauale sobiv
ikoon, siis ongi "harilikule" kasutajale tavaline rakendus valmis.
C# loodi veebiajastul. Selle tõttu on tal kasutada mitmesuguseid mooduseid võrgu kaudu
suhtlemiseks. Nii otseühenduses üle TCP-protokolli näiteks jututoa või võrgumängu tarbeks
kui ka kogu tehnikate komplekt veebirakenduste loomiseks. Sealne käivitus näeb tunduvalt
teistsugune välja, kui siin loodetavasti tuttavaks saanud public static void Main. Aga
sellegipoolest jääb keele põhisisu samaks. Nii muutujad, tsüklid, valikud, klassid ja objektid on
endistviisi vajalikud. Lihtsalt tuleb neile osalt uus ja parasjagu vajalikule toimingule vastav sisu
kokku panna.
SQLi keel
SQL on andmebaaside juhtimiseks kasutusel olnud juba mitu aastakümmet. Pole ta sugugi
ainuke andmekirjelduskeel ega ka mitte päringukeel. Aga levinumates andmebaasides on ta
siiski teinud jõudsa võidukäigu, nii et kel vaja andmetega tihedamalt tegelda, see SQList ei
pääse.
Pea iga andmebaasitootja on keelele oma lisandusi pakkunud, mis rakendustele võimalusi ja
keerukusi juurde toonud. SQL-92ga standardiseeriti kõige üldisemad käsklused. Omi nippe ja
andmetüüpe jagub aga tootjatel küllaga.
Siin kirjutises keskendutakse eripärade juures MS SQL Serveri võimalustele. Näited on tehtud
SQL Server 2005 abil.
Alustatakse "puust ette ja punaseks" seletusest, kuidas oma andmebaas luua, sinna tabel lisada
ja andmed sisse panna. Edasi liigutakse graafilistelt näidetelt koodi suunas, minnes mõnikord
ka tasemeni, mida lihtrakenduste koostamisel hädasti vaja pole. Nii et kui lugedes/õppides
tundub, et näited/seletused ka kolmandal lugemisel arusaamatud tunduvad, võib vähemasti
peatükkide lõpus olevad osad selleks korraks laagerduma jätta ning nende juurde vajadusel
uuesti tagasi tulla: siis, kui tööd tehes paistab, et oleks vaja keerukamaid päringuid kokku
panna, aga lihtsate vahenditega ei taha välja tulla. Selleks ajaks on tõenäoliselt
andmebaasidega ümber käimise juures ka piisav kogemus tekkinud, et on julgust üha
uhkemaid päringuid ette võtta.
Esialgu koosnevad andmebaasipäringud tõenäoliselt kuni kümnekonnast sõnast ning nendega
on võimalik enamik ettetulevaid muresid andmeotsingu vallas ära lahendada. Aga koos
soovide ja tahtmiste ning süsteemide suurustega kipuvad ka päringud kasvama. Nii et pole
ime, kui mõne firma andmemajanduse juurde sattudes võib mitmeleheküljelisi päringuid näha,
mis esialgu silme eest kirjuks võtavad. Kui aga asuda rahulikult otsast vaatama, siis selgub, et
selle suure keerukuse saab vähehaaval täiesti eraldatavateks tervikuteks jagada ning viimased
omakorda juba nõnda mõistetavateks tükkideks, et kõigest on võimalik aru saada ja vajadusel
oma tarbeks täiendada. Kõige rohkem on vaja tahtmist, kannatust ja pusimissoovi.
Ilusaid päringuid!
Microsoft SQL Server 2005
SQL Server 2005 on siinse materjali kirjutamise ajaks Microsofti viimane andmebaasimootor.
Tegemist on millegi enamaga kui lihtsalt andmebaasimootoriga. Seetõttu nimetatakse seda
mootorit ka 3nda põlvkonna andmebaasimootoriks.
Lisaks relatsioonilisele andmebaasimootorile kaasneb SQL Server 2005ga mitmeid teisi
komponente:
* Relatsiooniline andmebaasimootor – on SQL Serveri südameks ja pakub turvalist ja
töökindlat keskkonda relatsiooniliste ja XML andmete hoidmiseks, vaatamiseks ja
muutmiseks.
Uute funktsioonidena on lisandunud tabelite ja indeksite partitsioneerimine, DDL
triggerid ja teavitused sündmustest, uued andmetüübid, MARS, uued konstruktsioonid
SQL keeles, uued vahendid turvalisuse tagamiseks, tugi XML andmetele ning Xquery’le,
ühenduvus .NET raamistikuga, paremad replikeerimise võimalused, lihtsamad
administreerimisvahendid
* Analysis Services – Teenused ja vahendid andmete kaevamiseks ja analüüsiks (OLAP).
Loodud on täiesti uus kasutajaliides ning mootor andmete analüüsimiseks, mis võimaldab
paremini luua ning hallata andmekuubikuid ja kaevata andmeid.
* SQL Server Integration Services (SSIS) – Mootor andmete importimiseks ja
eksportimiseks ning teisendamiseks.
Tegemist on uue mootoriga, mis võimaldab paremini juhtida imporditavaid andmevooge.
Uues SSIS disaineris on eraldatud andmevood ning programm andmete importimiseks,
mis annab parema ülevaate andmete liigutamise loogikast.
* Notification Services – Raamistik lahendustele, kus huvilistele saadetakse teade, kui mingi
sündmus on aset leidnud.
Tegemist on registreerimisel põhineva teavitamismetoodikaga, mis võimaldab lihtsa
vaevaga huviliste teavitamist neile olulistest sündmustest.
* Reporting Services – Teenus, mis muudab SQLis olevad andmed ilusateks aruanneteks,
mida on võimalik kasutada nii läbi Visual Studiol baseeruva aruande disaineri kui ka
vastavast veebikeskkonnas.
* Service Broker – Transaktsiooniline teadetel (nt e-kiri) põhinev süsteem teenustevaheliseks
suhtlemiseks
* Native HTTP Support – SQL Server 2005 suudab vastata HTTP päringutele, mis
võimaldab luua veebiteenuseid ilma IISi (Internet Information Service) vahele segamata.
* SQL Server Agent – Graafiku alusel töötav mootor, mis võimaldab automatiseerida
andmebaasi haldust ning tööde, sündmuste ja teadete haldamist.
* .NET Common Language Runtime – Võimaldab SQL Serveris kasutada andmete
haldamiseks ning hoidmiseks koodi, mis on kirjutatud .NET keeltes nagu nt C# ja
VB.NET
* Replication – Komplekt tehnoloogiaid, mis võimaldab kopeerida andmebaase või selle osi
ühest serverist teise ning hiljem tegeleb nende koopiate sünkroniseerimisega.
Paranenud on kontroll replikeeritavate andmete üle, lubatud on schema muudatused
replikeeritud tabelites, ning on lisatud mitmeid uusi võimalusi replikatsiooni
korraldamiseks.
* Full-Text Search – Kiire ja paindlik täistekstiotsing võimaldades suurtelt tekstiväljadelt
võtmesõna ja loogikaavaldise põhjal otsingut.
SQL Server 2005 perekond
SQL Server 2005 perekonda kuulub päris palju liikmeid. Kõik liikmed omavad ühtemoodi
head relatsioonilist andmebaasimootorit, kuid erinevad üksteisest kas riistvara kasutamise
oskuste või lisavõimaluste poolest.
* Enterprise Edition – sisaldab kogu SQL Server 2005 funktsionaalsust ning
maksimaalselt laiendamise võimalusi ning on optimeeritud 64-bit protsessorite jaoks.
* Standard Edition – sisaldab kõiki olulisemaid SQL Server 2005 funktsioone ning on
mõeldud väikeste ja keskmiste ettevõtete tarbeks. Saadaval nii 32 kui ka 64 bitised
versioonid.
* Workgroup Edition – SQL Serveri põhifunktsionaalsus väikestele ettevõtetele ja
töörühmadele.
* Express Edition – SQL Serveri tasuta versioon - õppimiseks, arenduseks ning väikeste
nõudmistega ärilahenduste teenindamiseks
* Compact Edition – spetsiaalne SQLi versioon mobiilsete seadmete tarbeks
* Developer Edition – server arendustööde tegemiseks, sisaldab Enterprise
funktsionaalsust kuid on saadaval nii 32 kui ka 64 bitise versioonina.
Täpsem info erinevate SQL Server 2005 perekonnaliikmete kohta leiate aadressidelt
http://www.microsoft.com/sql/editions/ ja
http://www.microsoft.com/sql/prodinfo/features/compare-features.mspx
SQL Server 2005 Express Edition
SQL Server 2005 Express Edition on mõeldud SQL Serveriga tutvumiseks ning ka
ärikasutuseks väikeste lahenduste puhul. Express versiooni saab tõmmata aadressilt
http://msdn.microsoft.com/vstudio/express/sql/download.
Express versioonile on seatud järgmised piirangud: Töötab kuni 1 protsessoriga, kasutab ära
kuni 1 GB mälu on saadaval ainult 32-bit versioonis (64-bit Windowsi all võimalik panna tööle
tänu WOW’le. Windows on Windows funktsionaalsusele) ja andmebaasi maksimaalne maht
on 4 GB. Lisaks sellele on sealt veel ära võetud mõned teenused, mis on vajalikud suurte
andmebaaside ja keerukate programmidega toimetamisel. Täpne funktsionaalsuse kirjeldus on
saadaval aadressil http://www.microsoft.com/sql/prodinfo/features/compare-features.mspx
Install ja seadistus
SQL Server Express Edition vajab masinat, kus on vähemalt 512 MB mälu (soovituslik 1 GB
või rohkem), ca 600 MB vaba kettaruumi, 600 MHz protsessorit (soovituslik üle 1 GHz).
Operatsioonisüsteemidena sobib Windows 2000, Windows XP ja Windows 2003.
Kogu vajalik tarkvara ja ka paigaldamise juhend on saadaval aadressil
http://msdn.microsoft.com/vstudio/express/sql/download/
Lühidalt on protsess järgmine:
1. Tuleb installeerida .NET raamistik 2.0
2. Tuleb eemaldada kõik SQL Server 2005, Visual Studio 2005 ja .NET raamistik 2.0
Beta ja CTP versioonid
3. Installida SQL Server 2005 Express koos viimase teeninduspaketiga
4. Võite installeerida lisakomponente nagu demoandmebaasid ja abiinfo e. Books Online
5. Võite toote registreerida ja saate Microsoftilt kingitusi :)
SQL Serveri haldamiseks on olemas käsurea liides, Visual Studio ja SQL Management Studio.
Töö alustamine
SQL Server on väga hea andmete hoidaja ja töötleja, kuid tal puudub kasutajaliides käskude
sisestamiseks. Selleks, et panna SQL Server enda pilli järgi tantsima, on Teil vaja mingit
programmi, mille kaudu SQL Serverile korraldusi jagada. Selleks programmiks võib olla nii
teie enda loodud programm nt mõnes .NET keeles, Microsoft Office paketist Excel ja Access
või siis mõni Microsofti poolt pakutav töövahend. SQLi haldamiseks pakub Microsoft
põhiliselt kolme vahendit: Visual Studio, SQL Server Management Studio ning SQL Server
Command Line Tool e. sqlcmd (varem tuntud kui oSql utiliit).
Visual Studio on mõeldud professionaalsetele programmeerijatele, kes oma andmete
hoidmiseks kasutavad SQL Serverit.
SQLi käsurea utiliit (sqlcmd) on mõeldud administraatoritele, kes ei soovi oma serverisse
installeerida graafilisi vahendeid või soovivad mingeid kindlaid toimingid ajastada kasutades
Windowsi ajastatud töid e Scheduled Task’e. Puhtalt SQL Serveriga tegelemiseks on kõige
parem vahend SQL Management Studio, mis võimaldab kasutada SQL läbi graafilise liidese
ning ühtlasi võimaldab ka edastada SQL käske kirjalikult.
SQL Management Studio on tasuta utiliit, mida on võimalik endale muretseda Microsofti
kodulehelt.
Management Studio käivitamisel küsitakse, millist teenust soovite kasutada, millise serveri
külge ühenduda ning millist autentimismoodust kasutada.
Teenustest on valida:
* Database Engine e. SQL andmetega manipuleerimine
* Analysis Services e andmete kaevamine
* Reporting Services e. aruannete koostamine
* SQL Server Mobile e. lubatakse SQL Serveri andmefaili peal kasutada vaid neid
funktsioone, mida toetab SQL Serveri mobiilne versioon
* Integration Services e. andmete import, eksport ning teisendamine
Kui Serverite sirvimine on keelatud peate teadma SQL Serveri nime. SQL Serveri nime ütleb
Teile SQLi administraator. Kuna ühte masinasse on võimalik installeerida mitu SQL Serverit
siis võib juhtuda, et on vaja lisaks serveri nimele teada ka SQL nimelise instantsi (eksemplari)
nime. Instantsi nime pole vaja kui soovite pöörduda Serveri vaikimisi instantsi poole. Serveri
nimi tuleb kirjutada kujul \. SQL Express installeeritakse tavaliselt
SQLEXPRESS nimelise instantsi alla e. serveri nimeks võiksite panna
ARVUTINIMI\SQLEXPRESS.
SQL Serveris peab teil iga konkreetse tegevuse jaoks olema õigus. Selleks, et õigusi
kontrollida on vaja SQL Serverile öelda, kes Te olete. Ütlemise võimalusi on kaks:
* Windows Authentication – kasutatakse Windowsi autentimist. Ühendute SQL Serveri
külge sama nimega, millega sisse logisite. See on kasutatav juhul, kui SQL Server on
installeeritud Teie arvutisse või kui on ülesse seatud Windowsi domeen. Kasutades
Windowsi autentimist kontrollib teie konto kehtivust ning parooli Windows ning SQL
usaldab Windowsilt saadud infot. Tegemist on soovitusliku autentimise meetodiga
kuna Windowsi turvapoliitikad ning kontrollmehhanismid on märksa intelligentsemad
kui SQL Serveri enda omad.
* SQL Server Authentication – SQL Serveris on kirjeldatud kasutajad ning SQL Server
peab ise kontrollima kas Teie parool on õige. Sellist lahendust kasutatakse vaid juhul,
kui Windowsi autentimise kasutamine pole võimalik e. olukorras kus on vaja serveriga
suhelda erinevatel klientarvutitel, mis pole domeeni liikmed ning keda üldjuhul ei
usaldata. Vaikimisi on selline suhtlus keelatud.
Autentimise meetod ning vajadusel kasutajanimi ning parool on taas väärtused, mille ütleb
teile SQL Serveri administraator.
Kui ühendamine õnnestus, tuleb ette Object
Exploreri aken, mille kaudu võib serveris paiknevat ja
toimuvat näha ja muuta. Paremal pool asuvast
Summary aknast saate vaadata detailsemat infot
valitud objekti kohta
Andmebaasi loomine
Andmebaasi loomiseks klõpsake
Databases kataloogi peal hiire paremat
klahvi ning valige New Database…
Baasi loomise juures küsitakse kõigepealt andmebaasi nime. Saagu selleks baas1. Baasile saab
hulga omadusi määrata, mis suuremate ja tihedasti kasutatavate baaside administreerimisel on
küllalt tähtsad.. Path teatab, kuhu kettale ja kataloogi andmed salvestatakse. Initial Size kaudu
öeldakse algne baasi jaoks reserveeritud kettamaht. Üldiselt oleks kasulik andmebaasi eeldatav
suurus välja arvutada ning teha andmebaas kohe õige suurusega. Sellisel juhul fragmenteerub
andmebaasifail ketta peal vähem ning üldine jõudlus on parem. Autogrowth ütleb, kui suurte
sammudega ja kui suure mahuni on salvestatavatel andmetel lubatud arvutis kasvada. Nagu
näha on eraldi failid andmete eneste ja logide jaoks. Kõik muudatused andmebaasis
kirjutatakse esmalt logisse ning seejärel salvestatakse andmebaasi. Selline kahekäiguline
täitmine võimaldab vajadusel teatud tegevusi e. transaktsioone tagasi kerida. Väikestel
andmebaasidel on enamasti üks andmefail ning üks logifail. Suurematel ja keerukamatel
andmebaasidel võib nii logi kui ka andmefaile olla mitmeid. Kasulik on see näiteks juhul, kui
mõni ketas on kiirem kui teine – sinna saab panna sagedamini vajaminevaid andmeid. Samuti
võib juhtuda, et mõningaid andmeid kasutatakse omavahel tihemini koos. Kõik see on
peenema programmeerimise ning administreerimise rida, praeguse alguse juures piisab, kui
vajutada OK ning baas ongi olemas. Pärast refresh-menüü valikut andmebaaside alt võib uue
nimega baasi ka loetelus näha.
Graafiline liides on tore katsetamiseks ning uurimiseks, kuid tõsisemaks programmeerimiseks
oleks vaja välja uurida kuidas SQL tegelikult asjadest aru saab ning kuidas teha nii, et peale
programmeerimise lõppu oleks lihtne kogu loodud tarkus teise serverisse transportida.
Selgub, et andmebaasiserveriga suhtlemiseks on loodud omaette keel SQL-keel, milles on
võimalik teha kõiki samu tegevusi, mis graafiliselt ja veel palju enamatki.
Kui soovite teada, milline on SQL käsk, mis teeb neid samu asju, mida püüdsite äsja
graafiliselt teha võite vajutada akna ülaosas olevat Script nuppu.
Tulemuseks on järgmine SQL konstruktsioon
CREATE DATABASE baas1 ON PRIMARY
( NAME = N'baas1',
FILENAME = N'D:\MSSQL\DATA\baas1.mdf' ,
SIZE = 3072KB ,
FILEGROWTH = 1024KB )
LOG ON
( NAME = N'baas1_log',
FILENAME = N'D:\MSSQL\DATA\baas1_log.ldf',
SIZE = 1024KB,
FILEGROWTH = 10%)
GO
Saadud SQL lausest on näha, et andmebaasi saab tekitada kasutades CREATE DATABASE
lauset
Siinsel juhul tehakse andmebaas nimega baas1, mille 3MB suurune andmefail baas1.mdf
pannakse D: kettal kausta MSSQL\DATA kuhu läheb ka 1 MB suurune logifail baas1_log.ldf.
Kui failimaht saab täis siis andmefail hakkab kasvama 1MB kaupa ning logifail 10% kaupa.
Skriptist on näha ka see, et igal failil on kaks nime: üks määratud NAME ja teine FILENAME
atribuudiga. Neist nimedest esimene on faili loogiline nimi, mida kasutab SQLi administraator
ning teine on mõeldud operatsioonisüsteemile. Need nimed võivad olla erinevad kuid lihtsuse
mõttes on kasulikum hoida need nimed ühesugused.
Tabeli loomine
Palja baasi olemasolust veel andmete hoidmiseks ei piisa. Relatsiooniliste andmebaaside puhul
paiknevad andmed tabelites. Siin käime esialgu läbi tabeli loomise graafilise tee, hiljem
vaatame ka programmikäskudega sättimise võimalusi.
Harilikku tabelit kujutab igaüks ette. Hulk ridasid ja veerge, andmeid täis. Eks
andmebaasitabelid ole üsna sarnased – lihtsalt mõned reeglid ja piirangud on juures. Näiteks
peab igal veerul olema andmetüüp: täisarv, reaalarv, kuupäev, tekst või midagi muud lubatut.
Ja kõik vastavas veerus paiknevad andmed peavad vastavat tüüpi olema. Kui andmed
erinevad üksteisest omaduste poolest – näiteks inimeste kontaktandmed ja autode tehnilised
parameetrid – siis peavad need eri tüüpi kirjed olema eri tabelites. Katsetuseks aga üks lihtne
linnanimede ja rahvaarvude tabel, mille puhul ei tohiks eksimist ja segadusi olla.
Avage oma andmebaas, vajutades hiirega andmebaasi ees olevat + märki ning Tables alt valik
"New Table..." ja juba võibki hakata veergusid looma. Igale veerule nimi, andmetüüp ning
linnuke selle kohta, kas väärtus võib puududa (allow nulls). Programmeerija elu on üldjuhul
lihtsam, kui väärtus on alati olemas, st. NULLid pole lubatud. Aga kui päriselus sellegipoolest
võib juhtuda, et mõnda lahtrisse pole väärtust kusagilt võtta, siis on tühjus siiski enamasti
parem, kui mõni kokkuleppeline muu väärtus. Kuigi – vahel pannakse arvuliste andmete juures
teadmata kohale nt. -1 või siis 999. Viimane näiteks inimese pikkuse juures, kus usutavad
väärtused kuhugi paarisaja kanti ning üheksate riviga on kohe näha, et tegemist pole õige
asjaga.
Esimesele veerule saab nimeks id või kood. Kui pole erilist põhjust selle veeru ära jätmiseks,
siis üldjuhul tasub see id-veerg alati panna. Nõnda on igal real järjekorranumber, mille järgi
saab rea poole pöörduda. Muidu võib kergesti juhtuda, et kahel asulal või kahel inimesel on
sama nimi ning hilisemate päringute või muutmiste juures pole selge, millise reaga tegeldakse.
Kui aga panna loenduriga tulp, millel traditsiooniliselt on nimeks id, siis sellist muret ei teki.
Kuna tabeleid võib andmebaasis olla mitu ning nende vahel on enamasti seosed, on kasulik
ID’le lisaks kirjutada ka mõni täpsustav märkus nt LinnID. Sellisel juhul saame kõikjal, kus
vaja viidata linnale kasutada sama väljanime.
Kõik nimed peavad algama tähega, millele võivad järgneda nii tähed kui ka numbrid. Täheks
loetakse ka alakriipsu. SQL Server 2005 lubab kasutada ka täpitähti ja tühikuid, kuid nende
kasutamine pole soovitav kuna võib tekitada probleeme andmebaasi liigutamisega erinevate
serverite vahel ning seab lisakohustusi päringute koostamisel. Nime maksimaalne pikkus on
128 märki.
Andmetüübid
Igale veerule tuleb valida andmetüüp. Esialgu tundub neid loetelus arutu hulk olema. Lähemal
vaatlusel aga selgub, et tüüpe polegi liialt palju. Rohkem kasutatavad ehk täisarvud,
reaalarvud, aeg, tekst, binaarvorming ja XML. Keerukamate andmebaaside tarbeks on
võimalik andmetüüpe isegi juurde tekitada.
Täisarvud: tinyint, smallint, int ja bigint võtavad 1, 2, 4 ja 8 baiti mälus, ehk siis vastavalt on
määratud suuruspiirid, milleni vastavas veerus andmeid salvestada saab. Tavalisim int on 4
baiti ja lubatud suurimad arvud seega ±2 miljardi kanti.
Reaalarvud: float, decimal, money, numeric, real, smallmoney. Numeric ja decimal
sünonüümid ning põhjused, miks nii on juhtunud on ajaloolised. Reaalarvude hoidmiseks on
kaks moodust:
* kasutada täisarve millel teatud arv kohti on reserveeritud murdosa tarbeks
* kasutada ligikaudseid numbreid e. salvestada arvust vaid esimesed x numbrit
Esimest meetodit kasutavad numeric ja decimal andmetüübid ning teist meetodit float ning
real andmetüübid.
Erinevus seisneb selles, et kasutades täisarvu on alati teada, et nt kui kirjutan decimal(15,5) siis
10 kohta on enne koma ning 5 kohta peale koma. Kui üritan kirjutada enne koma 11 kohalise
numbri, saan veateate ning kui kirjutan peale koma 6 kohta siis number ümardatakse 5 kohani
peale koma. Kui kasutan aga float(15) andmetüüpi, salvestatakse arvust 15 esimest numbrit
ning kümneaste ning lõpp ümardatakse. See võimaldab salvestada samale väljale nt 1000
kohalise arvu kuid selle arvu täpsus on määratud 15 esimese numbriga ning lõpp on ära
lõigatud.
Money ja SmallMoney kasutavad salvestamiseks esimest metoodikat ning on mõeldud
rahaliste väärtuste hoidmiseks.
Aeg: datetime – täpsus 3ms, smalldatetime – täpsus 1 minut
Tekst: char, varchar ja text tekstide jaoks. Esimene kindla pikkusega väljadele (nt. isikukood),
teine muutuva pikkusega tekstide jaoks (nt. asutuse nimetus) ning kolmas pikematele
tekstidele. Sinna kõrvale käivad nchar (national char), nvarchar ja ntext Unicode kodeeringut
kasutavate tekstide tarbeks.
Enamiku lihtsamate väiksemate andmebaaside puhul saab tavaliselt hakkama nelja tüübiga: int,
float, datetime ja varchar. Kui võib eeldada jõudlusprobleeme või muid olukordi, kus
andmebaasiosa saab rakenduse pudelikaelaks, siis tasub lähemalt uurida andmebaasi poolt
võimaldatavaid töö kiirendamise või andmemahtude kokkuhoiu mooduseid.
Tabeli nimi võiks tabeli sisuga seotud olla. Table_1 ei ütle sisu kohta suurt midagi. Pigem saab
paremast tulbast tabeli nime ära muuta, andes talle siin nimeks "linn".
Tabeli nimed võiksid olla ainsuse nimetavas käändes.
Kuna kõigi andmebaasiobjektide nimed peavad olema erinevad võib nime külge panna
erinevaid ees ja järelliiteid. Nt Linn_tbl, mis ütleb, et tegemist on tabeliga, millest leiad linnade
nimed.
Primaarvõti
Tulpa, mille järgi tabeli ridadele viidatakse ning mille juures kindlasti on kõik väärtused
erinevad, nimetatakse üldjuhul primaarvõtmeks. See on viisakas tabeli loomise juures ka ära
määrata. Parema klahvi klõps tulba juures ning valik "Set Primary Key" ning tulba ette tekkiski
primaarvõtit tähistav ikoon. Edasi tasub arvutile selgeks teha, et ridade numbreid automaatselt
loendataks. Selleks võib id-tulba alt otsida sektsiooni "Identity Specification" ning sealt
omaduse "Is Identity" väärtuseks panna Yes. Ülejäänud vaikeseaded võivad paika jääda juhul,
kui oleme rahul olukorraga, kus loendama hakatakse numbrist 1 ning järgmine arv tuleb igal
korral ühe võrra suurem.
Vajutades salvestusnuppu, kantakse tehtud muudatused tegelikult andmebaasi ning vasakul
andmemenüüs võib näha tabelite all uut tekkinud tabelid dbo.linnad. Andmete mugavamaks
sisestamiseks hiire parema klahviga klõps tabeli nimele ja valik "Open Table".
Taas on võimalik kõik kogu protsess teisendada SQL käskudeks. Selleks tuleb valida Table
Designer\Generate Change Script ning tulemus on järgmine:
CREATE TABLE dbo.Linn_tbl
(
LinnID int NOT NULL IDENTITY (1, 1) PRIMARY KEY
, Nimi varchar(50) NOT NULL
, Rahvaarv int NULL
)
GO
Nagu näha saab tabeleid teha CREATE TABLE lausega, mille järgi tuleb kirjutada tabeli nimi
ning selle järele sulgudesse komadega eraldatud loetelu tabelis olevatest väljadest koos
andmetüübi ning muude omadustega.
Andmete sisestus
Avanenud aknas saab rahumeeli andmeid juurde lisada. Identifitseerimistulba LinnID-
numbrid kasvavad automaatselt, nimi ja rahvaarv tuleb ise sisse kirjutada.
Lisamislause tekitamine pole enam kahjuks nii lihtne kui andmebaasi või tabeli
loomise/muutmise tegevus. Lisamise lause tekitamiseks on kasulik moodustada endale väike
spikker valides Object Explorerist hiire parema klahviga Script Table as/INSERT to/New
Query Editor Window.
Tulemuseks on järgnev SQL:
INSERT INTO [baas1].[dbo].[Linn_tbl]
( [Nimi], [Rahvaarv])
VALUES
( ,)
Seega on andmete lisamiseks INSERT INTO lause, millele järgneb tabel, kuhu andmed
lisatakse ning selle järgi sulgudes väljad, millele väärtused omistada. Peale VALUES
märksõna tuleb samas järjekorras kirjutada ka lisatavad väärtused.
Nagu näha pole vaja väärtust omistada automaatselt nummerduvat välja LinnID ning lisaks
sellele võib jätta väärtustamata kõik väljad millele on määratud vaikeväärtus või lubatud
määramatus e. NULL.
Kui soovime lisada linnade loetellu nt Tartu 110000 elanikuga tuleks muuta saadud SQLi
järgmiselt:
INSERT INTO [baas1].[dbo].[Linn_tbl]
([Nimi], [Rahvaarv])
VALUES
('Tartu', 110000)
Harjutus (tabeli loomine)
Tekitame tabeli Laps, milles kirjas lapse nimi, pikkus, sünniaasta ning sünnilinn.
Kes soovib võib teha tabeli kasutades graafilisi vahendeid kuid soovitan harjutada SQL keelt
ning teha tabel kasutades SQL lauseid. SQL laused võimaldavad
* paremini kontrollida serverile antavaid käske
* laused on võimalik salvestada skriptidesse, mille käivitamisel saame kogu protsessi
korrata
Management Studios SQL-lausete kirjutamiseks tuleb luua vastav tekstiaken. Selleks
File->New->Query with Current Connection
Igas tabelis peab olema primaarvõti e. lisaks eelpool loetletud väljadele võtame kasutusele ka
välja LapsID, mis võiks olla taas automaatselt nummerduv.
Lihtsuse mõttes salvestame tabelisse vaid eesnimed ning loodame, et nimi pole üle 40 tähe
pikk (enamasti salvestatakse inimeste nimed kahel väljal ees- ja perekonnanimi, mis võimaldab
andmeid paremini otsida ja sorteerida)
Pikkust hoiame sentimeetrites keskmise suurusega täisarvulisel väljal.
Kuna soovime hoida andmebaasis ainult sünniaastat, mitte sünnipäeva siis kasutame ka
sünniaasta tarbeks täisarvulist numbrivälja, mitte kuupäeva välja. Arvutatavaid väärtusi
(näiteks vanus) üldjuhul tabelis ei hoita vaid arvutatakse vajalikult hetkel. Kui arvutatavat
välja on vaja väga tihti võib selleks alates SQL Server 2000st teha eraldi valemit sisaldava
välja (computed column)!
Linna tarbeks kasutame juba varem loodud Linn_tbl tabelis olevaid linnakoode. Et kahe tabeli
ühendamine oleks lihtsam, peavad mõlemal pool olema väljad ühte tüüpi ning ühesuurused.
Seega võiks laste tabeli teha järgneva SQL lausega:
CREATE TABLE dbo.Laps_tbl
(
LapsID INT NOT NULL IDENTITY (1,1) PRIMARY KEY
, Nimi VARCHAR(40) NOT NULL
, Pikkus SMALLINT NULL
, Synniaasta SMALLINT NULL
, SynniLinn INT NULL
, Vanus AS YEAR(GETDATE()) - Synniaasta
)
Väli vanus on arvutuslik. Funktsioon GETDATE() annab hetke aja e. kuupäev kellaaeg.
Funktsioon YEAR eraldab antud kuupäevast aasta. Seega leitakse vanus arvutusega jooksev
aasta miinus sünniaasta.
Lisame sellesse tabelisse ka mõned andmed:
Nimi
Pikkus
Synniaasta
SynniLinn
Juku
155
1997
1
Kati
158
1997
2
Mati
164
1995
2
Ats
163
1996
1
Siiri
153
1996
1
Madis
174
1995
1
Siim
163
1997
2
Selleks moodustame iga sisestatava rea tarbeks eraldi SQL lause:
INSERT INTO [baas1].[dbo].[Laps_tbl]
([Nimi], [Pikkus], [Synniaasta], [SynniLinn])
VALUES ('Juku', 155, 1997, 1)
INSERT INTO [baas1].[dbo].[Laps_tbl]
([Nimi], [Pikkus], [Synniaasta], [SynniLinn])
VALUES ('Kati', 158, 1997, 2)
INSERT INTO [baas1].[dbo].[Laps_tbl]
([Nimi], [Pikkus], [Synniaasta], [SynniLinn])
VALUES ('Mati', 164, 1995, 2)
INSERT INTO [baas1].[dbo].[Laps_tbl]
([Nimi], [Pikkus], [Synniaasta], [SynniLinn])
VALUES ('Siiri', 153, 1996, 1)
INSERT INTO [baas1].[dbo].[Laps_tbl]
([Nimi], [Pikkus], [Synniaasta], [SynniLinn])
VALUES ('Madis', 174, 1995, 1)
INSERT INTO [baas1].[dbo].[Laps_tbl]
([Nimi], [Pikkus], [Synniaasta], [SynniLinn])
VALUES ('Siim', 163, 1997, 2)
Avades tabeli vaatamiseks või kirjutades SQL Lause kujul:
SELECT *
FROM dbo.Laps_tbl
Saame tulemuseks tabeli, milles 6 rida:
Nagu näeme on kõigile lastele tekitatud (sisestamise järjekorras) koodid ning vaatamiseks
arvutatud vanused. Vanuseid reaalselt kusagile salvestatud ei ole ning need arvutatakse
lahutades käesolevast aastast sünniaasta.
Lihtsamad päringud
Enne, kui andmebaasi loomisega edasi läheme vaatame, kuidas oleks võimalik juba sisestatud
andmeid vaadata ja uurida.
Olemasolevate andmete kättesaamiseks sobib päringulause SELECT. Lihtsaim käsk kõigi
olemasolevate andmete tabelist kätte saamiseks:
SELECT *
FROM dbo.Laps_tbl
Tulemusena joonistub rakenduse allserva kogu tabelitäis andmeid koos tulpade nimedega.
Sellist päringut kasutatakse vaid erandolukordades ning väga väikeste tabelite juures. Reaalses
tööolukorras tuleks kindlasti loetleda ülesse kõik väljad, mida soovite vaadata ning seada
piirangud ridade arvule!
Järjestamiseks piisab lisaklauslist ORDER BY, millele järgneb tulba nimi. Kui soovime kõik
tabelis olevad andmed trükkida sorteerituna nimede järgi võime kirjutada:
Ja tulevadki andmed nimede järgi sordituna. Et esimeses tulbas olevad id-d näevad juhuslikult
segi paisatutena välja, see on täiesti loomulik. Kui sorditakse nime järgi, siis tõstetakse read
niimoodi ümber, et eesnimed lähevad tähestikulisse järjekorda. Iga rea andmed aga kuuluvad
endiselt kokku. Nii nagu Siiri oli algul 153 sentimeetrit pikk ja sündinud aastal 1996, nii on ta
seda ka pärast järjestamist. Ja samuti tema id-number jääb neljaks.
Tahtes sorteerimisjärjekorra muuta vastupidiseks, tuleb tulba nimele lisada tähed DESC
(sõnast descending). Ja ongi Siiri esimene ja Juku viimane.
Järjestust määravaid tulpi võib olla mitu. Sellisel juhul tuleb ORDER BY järgi loetleda väljad
tähtsuse järgi. Nt võttes sorteerimise aluseks sünniaasta ning seejärel nime saame, et
sõnniaastad on sorteeritud kasvavasse järjekorda ning kui samal aastal on sündinud mitu last
on nad sorteeritud nimede järgi:
Sugugi alati pole andmete juures vaja kõiki tulpasid näha. Kui soovin vaadata vaid nime ja
pikkust võin selle info panna ka SELECT lausesse, loetledes kõik vajalikud väljad SELECTi
järel.
Samuti saab seada piirangu ridade näitamise suhtes. Siin vaid lapsed, kelle sünnilinn on Tallinn
e. linna kood on 1
Või vaatame, millised lapsed on nooremad kui 12:
Kui tulemusse tekivad korduvad read saame nendest vabaneda kasutades DISTINCT
märksõna. Näiteks soovime välja selgitada milliste sünniaastatega lapsed meil tabelis on? Kui
kirjutame SELECTi ilma DISCTINCT märksõnata saame loetelu, kui kõigi laste sünniaastad,
kui lisame DISTINCTI saame kõik erinevad sünniaastad:
Võime piiranguid seada ka numbrivahemike järgi. Selleks on kaks võimalust:
* Kasutada BETWEEN operaatorit
* Kombineerida kaks võrratust AND operaatoriga
Sama tulemuse saaksime ka järgmise SQL lausega:
SELECT Nimi, Synniaasta
FROM dbo.Laps_tbl
WHERE Synniaasta >= 1995 AND Synniaasta <= 1997
ORDER BY Nimi
BETWEEN operaatori eeliseks on lihtsus ja ülevaatlikus. Loogikaavaldise kasuks räägib aga
paindlikkus - nimelt võime mõnest osapoolest võrduse ära võtta jättes täpsed väärtused välja.
Näiteks otsime lapsi, kes on sündinud alates aastast 1995, kuid enne aastat 1997:
Agregaatfunktsioonid
Lihtsamad kokkuarvutamised saab samuti SQL-keele abil ära teha, nende jaoks pole vaja
täiendavate programmide abi vaja otsida. Alljärgnevalt toodud funktsioonide töö tulemuseks
on siinsetel juhtudel terve tabeli kohta vaid üks arv. Aga eks see summa, suurima, vähima,
koguse või keskmise leidmisel nii olegi. Tahtes päringust välja tulnud ridu kokku lugeda, aitab
funktsioon COUNT(*). Saame teada, et praegu nimekirjas olevaid lapsi on 6.
Nagu näeme puudub saadud tulemusel pealkiri. Kui soovime SELECTis loetletud välju ümber
nimetada või neile nimesid anda tuleb kasutada AS märksõna mille järgi saate kirjutada sobiva
nime.
Suurima sünniaasta saab kätte käsuga MAX. Sarnaselt töötab ka MIN.
Või leiame tabelis olevate laste keskmise vanuse kasutades AVG funktsiooni (sõnast average)
Nagu näeme on tulemus täpselt sama täpne kui lähteandmed. Kui soovime andmetüüpi
arvutuste käigus muuta tuleb lähteandmed teisendada sobivale kujule ning sooritada tehte
pärast teisendust. Näiteks kui teisendame esmalt kõik vanused reaalarvudeks ning arvutame
seejärel keskmise saame hoopis põnevama tulemuse:
Funktsioon CAST ütleb SQLile, et käsitle seda andmevälja nii nagu ta oleks seda teist tüüpi.
Keerukamate arvutuste jaoks tuleb kasutada CONVERT funktsiooni.
Järgnevas tabelis on näidatud, millised teisendused andmete vahel on lubatud.
Kui on põhjust väärtused kokku liita, aitab funktsioon SUM. Praegusel kujul võib ette
kujutada, et kui kõik lapsed heidaksid üksteise järele pikali nii, et ühe pea puudutab järgmise
taldu, siis kokku oleks nende pikkus 9,67 meetrit.
Ette rutates märkus, et need funktsioonid saavad hakkama ka oludes, kus mõni väärtus on
tulpa sisestamata – näiteks pole paari lapse pikkust teada. Sellisel juhul puuduvaid ehk
tühiväärtusi lihtsalt ei arvestata. Vaid COUNT(*) loeb kõik read kokku. Kui tahetaks saada
vaid olemasolevate pikkustega ridu, siis aitaks COUNT(pikkus).
Muutmine
Muutmiste juures tuleb meeles pidada mitmeid olulisi asjaolusid:
1. Kõik muudatused tehakse transaktsioonis e. kas kõik või mitte midagi. St kui soovite
muuta tabelist kümmet rida ning ühe rea muutmine ei õnnestu, ei muudeta ka
ülejäänud üheksat.
2. Server eeldab, et käske jagab programm või intelligentne inimene ning käsu andja ei
eksi st kõik käsud täidetakse vastu vaidlemata ning mingit tagasiteed ei ole. Kui
andmebaas on osavalt seadistatud on võimalik andmebaasist andmeid taastada sekundi
või isegi transaktsiooni täpsusega, kuid kõik mis juhtus peale seda läheb kaduma. See
tähendab seda, et kui kogemata muudate midagi ära, avastate, et muudatus oli vale
ning soovite, et administraator taastaks selle andmebaasi teie tegevusele eelnenud
seisus ning selle otsuse vastuvõtmiseks kulus 5 minutit siis sisuliselt kustuvad kõik
viimase 5 minuti muudatused!
3. Sel ajal kui teie muudate ei saa teised samu andmeid kasutada (kui administraator pole
korraldanud teisiti ;)
Olge muutmiste juures äärmiselt ettevaatlikud!
Püüame muuta Mati (kood 3) pikkust. Uueks pikkuseks on 171.
Andmete muutmiseks on UPDATE lause, mille järgi tuleb kirjutada muudetav tabel, seejärel
peale SET märksõna loetleda ülesse kõik muudetavad väljad ning nende uued väärtused. Ärge
unustage piiranguid ridadele! Kui ridade arvu pole piiratud tehakse muudatus terves tabelis!
UPDATE dbo.Laps_tbl
SET Pikkus = 171
WHERE LapsID = 3
Selles lauses on näha ka kui oluline on unikaalne kood. Kui koode ei oleks ning me püüaksime
muuta nime baasile e. Nimi = ’Mati’ siis muudetaks kõigi Matide pikkused!
Enne:
Pärast:
UPDATE-käskluse puhul peab alati ettevaatlik olema, et WHERE-osa puudu ei jääks või
vigaseks ei osutuks. Puuduva osa puhul muudetakse ära kõikide ridade tulbad etteantud
väärtuseks. Edasi võib ainult varukoopia peale loota. Vigase WHERE puhul satub muutmise
alla lihtsalt vale rida.
Sellest tulenevalt oleks kasulik enne muutmist kontrollida, milliseid ridu muutma asute.
Näiteks käisid mõõtmisel kõik Tallinna lapsed ning selgus, et kõik on 5cm pikemaks kasvanud.
Sellisel juhul otsime esmalt ülesse kõik Tallinna lapsed:
Seega kui asume muutma kasutades sama WHERE tingimust peavad muutuma 3 lapse (Juku,
Siiri ja Madis) pikkused.
UPDATE dbo.Laps_tbl
SET Pikkus = Pikkus + 5
WHERE Synnilinn = 1
Enne:
Pärast:
Ühe UPDATE-lausega saab muuta ka mitme tulba väärtusi korraga. Selleks tuleb omistamised
eraldada komadega. Ehk siis näiteks kui Juku kasvab pikemaks ning saab Juhaniks siis saame
selle vormistada järgmise SQL lausega:
UPDATE dbo.Laps_tbl
SET Pikkus = 185, Nimi = 'Juhan'
WHERE LapsID = 1
Enne
Pärast
Kustutamine
Kustutamiseks saame kasutada DELETE lauset, mille järgi kirjutame tabeli, millest soovime
ridu kustutada. Kindlasti tuleks lisada ka tingimus selle kohta, milliseid ridu kustutame! Kui
tingimus ära jätta e. kirjutada nt:
DELETE dbo.Laps_tbl
Siis kustutatakse laste tabelist kõik read kuid tabel jääb alles!
Kui soovime laste hulgast eemaldada Madise (kood 5) saame kirjutada järgmise SQL lause:
DELETE dbo.Laps_tbl
WHERE LapsID = 5
Enne
Pärast
Tulemusena paistab, et rida id-numbriga 5 andmete hulgas puudub.
DELETE kohta kehtivad samad hoiatused, mis UPDATE juures. Kui WHERE-tingimus jääb
määramata, siis on ühekorraga kõik andmed kadunud. DELETE juures pole vaja tulpade
nimesid määrata, sest alati kustutatakse terve tabel korraga. UPDATE puhul tuleb aga iga
tulba puhul öelda, millise nimega tulpa muudetakse ja milline on sealne uus väärtus.
Kui kustutamise teel on üks rida tabelist eemaldatud, siis sama id-d samasse tulpa ei anta enam
kunagi välja. Isegi siis kui kustutasite kõige suurema IDga rea! Tuleb juurde uus laps, siis tema
järjekorranumber suureneb endiselt – sõltumata asjaolust, et loetelus juba vaba koht leidub.
INSERT INTO dbo.Laps_tbl (nimi, pikkus, synniaasta)
VALUES ('Ats', 165, 1996)
Alati uus number on vajalik segaduste vältimiseks. Kui näiteks juhtuks, et ennist kuuendal
kohal paiknenud Madisel oli trenniraha maksmata. ning nüüd uustulnukana saabunud Marile
antaks välja number kuus, siis oleks oht, et Madise maksmata arved jõuaksid Marile – sellist
muret aga ei pea üks infosüsteem tekitama. Kui igaühel oma järjekorranumber, siis jäävad
sellised segadused olemata.
Harjutus (lihtsad päringud)
Loo autode tabel, kus igaühe kohta on kirjas mark, registrinumber ja tootmisaasta ning
registripiirkond
Sisesta järgmised autod:
Mark
RegNr
Aasta
RegPiirk
Audi
123 ABC
2000
1
Ford
777 AAA
1988
2
Ford
FIN 772
2002
1
Nissan
111 CCC
2006
1
Toyota
128 HGF
2003
1
VAZ
544 CCH
1960
2
Järjesta autod tootmisaasta järgi kahanevasse järjekorda
Väljasta kõik erinevad margid
Väljasta enne 1993. aastat toodetud autode registrinumbrid
Väljasta enne 1993. aastat toodetud autode registrinumbrid tähestiku järjekorras
Väljasta autode kõige varasem väljalaskeaasta (MIN)
Muuda registrinumbrit autol, mille id on 3 (uus number 333 KKK)
Kustuta auto id-ga 4
Lisa uus masin nimekirja. Vaata tabeli sisu.
Mark
RegNr
Aasta
RegPiirk
Nissan
555 NNN
2007
2
Pikemad päringud
LIKE
Tekstidest otsimise juures aitab sobivat vastet leida võrdlus LIKE. Kui sinna anda ette lihtsalt
tekst, siis käitub LIKE võrdusmärgina:
Käskluse põhivõlu seisneb aga võimaluses otsida metamärkide järgi. Alljoon _ tähistab ühte
suvalist sümbolit ning protsendimärk % suvalist arvu suvalisi sümboleid. Näiteks S% vastab
kõigile tekstidele, mis algavad S-iga.
Ning %s% vastab kõigile tekstidele, mis sisaldavad s-i.
Alljoon, nagu öeldud, vastab vaid ühele sümbolile.
Nii et kui nimeks olnuks Pets, siis sinna '_ts' ei laiene.
Ette saab anda ka tähtede loetelu – selleks vajalikud kandilised sulud. '%[tr]i' tähendab, et
alguses võivad olla suvalised sümbolid, siis peab tulema t või r ning lõppu i. Nagu näha –
selliseid nimesid on meie loetelus päris mitu.
LIKE võtab arvutilt küllalt palju mõtlemisaega – seetõttu ei soovitata suuremate tabelite juures
vastavat käsklust ilmaasjata pruukida. Samas aga on see küllalt mugav vahend sobivate
nimede ja koodide leidmiseks, nii et mõõduka kasutamise juures on ta täiesti omal kohal.
Pikemate, nö täistekstide juures, on SQL-serveril omad käsud CONTAINS ning FREETEXT,
mille abil võimalik omale sobivaid andmeid kätte saada.
Kuna täisteksti otsinguid ei tee mitte SQL Server vaid Windowsi Indekseerimise ja otsimise
teenus siis vajab nende funktsioonide kasutamine administraatori poolset andmete
ettevalmistust ning hoolitust.
Tingimuste kombineerimine
Olemasolevaid tingimusi saab alati omavahel kombineerida. AND nõuab, et mõlemad
tingimuse pooled oleksid täidetud, OR seevastu piirdub nõudega, et vähemalt üks
tingimustest oleks tõene. Kõigepealt siis nimed, mis lõppevad i-ga ning sünnilinn on Tartu
(kood 2)
Edasi kõik i-lõpulised nimed pluss veel lisaks kõik, kes sündinud Tartus. Nimesid korduvalt
siiski ei näidata. Kuigi Siiri ja Mari vastavad mõlemale tingimusele, on nad nimekirjas siiski
ainult ühe korra.
IN
Tahtes ette anda lubatud väärtuste hulka, millele otsitav peab vastama, aitab käsklus IN.
Järgnevalt siis tegelased, kelle sünniaastaks on kas 1995 või 1997.
Sama tulemuse saab ka kombineerides OR operaatoriga otsesed võrdused:
SELECT LapsID, Nimi, Synniaasta
FROM dbo.Laps_tbl
WHERE synniaasta = 1995 OR synniaasta = 1997
Kuigi tulemus on sama ja SQL Server käsitleb neid päringuid ühtemoodi on IN operaatori
kasutamine ülevaatlikum.
NOT
NOT pöörab tulemuse ümber. Ehk siis kõik need lapsed, kes ei ole sündinud aastal 1996.
Võimaluse korral soovitatakse NOTi mitte pruukida, sest enamasti peab sel juhul
andmebaasimootor vaatama läbi tabeli kõik read, mis on suurte andmemahtude juures küllalt
suur töö. Aga kui muidu läbi ei saa, eks siis peab ikka selle sõna kirjutama.
TOP, päringu algusosa
Lihtsalt andmetest ülevaate saamiseks ei ole vaja sageli kõike näha. Samuti, kui soovime viite
kiiremat jooksjat või viite vanemat autot, siis on mugav, kui päring kohe annabki meile soovitu
kätte, mitte ei pea hakkama ise pead vaevama, kuidas soovitud kohast andmeid võtma hakata.
Laste tabelist tähestiku järjekorras kolm esimest nime näiteks saab kätte nii.
Tahtes saada neli vanemat last, tuleb andmed sorteerida sünniaasta järgi.
Tekib aga probleem: kuna andmed on salvestatud aasta täpsusega, siis võetakse 1996ndal
aastal sündinutest lihtsalt juhuslik komplekt ning ülejäänud jäävad näitamata. Mõnikord pole
sellest hullu – saadi juhuslikud tegelased kokku ja sobib küll. Teinekord aga võivad samade
tunnustega osalejad porisema hakata, kui üks neist kaasa võeti ja teine mitte. Et saaks kõik
ausalt kaasa, kes teistega võrdsed, selleks saab TOP käsklusele lisada WITH TIES. Nii
võetakse siin näites vähemalt kolm. Ning kui jagub järjestatava tunnuse alusel viimasega
võrdseid mahajääjaid, võetakse ka nemad kaasa.
Lisaks fikseeritud ridade arvule võime määrata ridade arvu ka proportsionaalselt kogu ridade
hulgast e. kasutada protsenti. Näiteks võime välja tuua neli protsenti kõige vanemaid lapsi:
4 protsenti kuuest lapsest on küll suhteliselt tilluke arv. Aga et näidates ümardatakse arve
ülespoole, siis näeme ikkagi vähemasti ühe lapse andmeid.
SQL Server 2005 täiendab TOP käsu süntaksid ühe väikese, kuid äärmiselt olulise
võimalusega: nimelt on võimalik ridu piirava konstandi asemel kasutada ka muutujat või isegi
alampäringut! See annab juurde väga palju uusi võimalusi päringute ja ka salvestatud
protseduuride loomisel. Nii saab luua rakenduse, kus kasutaja ise ütleb, mitut rida ta soovib
näha. Näiteks loome protseduuri, mis tagastab soovitud hulga vanimaid lapsi:
CREATE PROC VanimadLapsed_proc
@LasteArv int
AS
IF (@LasteArv >0)
SELECT TOP (@LasteArv) eesnimi, synniaasta
FROM lapsed
ORDER BY synniaasta
ELSE
PRINT ’Lähteandmed päringu tegemiseks on vigased!’
Enne SQL 2005 puudus ka võimalus vahepealt valimiseks e. kui soovite tuua alates 3ndast
kuni 5nda reani. SQL 2005 on tekitada tulemusse reanumbrid ning nende järgi ka filtreerida.
Selleks saab kasutada ROW_NUMBER() funktsiooni.
Süntaks on siis järgmine: ROW_NUMBER() OVER (partitsioon) st OVER märksõna järgi
sulgudes tuleb öelda, mis moodi on read nummerdatud. Antud näites nummerdatakse
sünniaastate järgi kasvavasse järjekorda.
Grupeerimine
Eelnevalt uurisime agregaatfunktsioone suurima, vähima, keskmise, summa ja koguse
leidmiseks. Nad on kogu tabeli kohta head abilised. WHERE-tingimuse abil saab filtreerida
sobiva tunnuse väärtuse alusel read välja ning siis nende põhjal kokkuvõtteid teha. Näiteks
leida kõikide nende laste keskmise pikkuse, kes sündinud aastal 1996. Selgub aga, et käsklus
lubab veelgi peenema statistika ette võtta.
Seik autori oma kogemusest. Kord oli vaja ühele firmale teha veebipõhine rakendus
komandeeringuaruannete sisestamiseks ning kokkuvõtete vaatamiseks. Iseenesest pealtnäha
lihtne ülesanne: igaüks annab teada, kus ta käis, mida tegi ning kui palju raha kulus ja pärast
loetakse nädalate, kuude, aastate ja isikute lõikes kõikvõimalikud andmed kokku. Muuhulgas
oli vaja teada, mitu korda konkreetsel aastal millist linna on komandeeringu raames külastatud.
SQL oli tuttav ligikaudu samapalju, kuivõrd lugeja kirjutises siiamaani jõudes. Et ka paarilt
tuttavalt nõu küsimine ei aidanud edasi, tuli ise vastav programmike kirjutada. Pool päeva
tööd, paar lehekülge koodi ning tulemus oli valmis ja sobis tööandjale. Suur oli aga üllatus, kui
paar päeva hiljem SQLi manuaale uurides leidus võimalus seesama töö ühe suhteliselt lihtsa
lausega kirja panna.
Nüüd siis mõned näited ja seletused, et siinse kirjutise lugejad ei peaks sama pikka ja okkalist
teed läbi käima. Algul meeldetuletuseks laste andmed, et oleks näha, mida ja kuidas
grupeeritakse.
Tahtes iga aasta kohta teada, mitu last meie nimekirjast vastaval aastal sündinud on, aitab
järgnev lause. COUNT(*) loeb kokku plokis olevad read. Et päringu lõpus on GROUP BY
synniaasta, siis loetakse iga erinev sünniaasta omaette plokiks. Tahtes sünniaastat ka ennast
näha, tuleb ka see SELECTi järele tulpade loetellu kirjutada. Grupeerimisfunktsioonide puhul
tohibki vastusesse küsida väärtusi vaid nendest tulpadest, mille järgi grupeeritakse. Muidu
tekiks ju segadus, sest kui tahaks võtta väljundisse ka pikkust, aga iga sünniaasta juurde võib
kuuluda lapsi ja seega ka pikkusi mitu, siis ei tuleks vastus tabeli kujuline ning seetõttu ei
sobiks relatsioonilise ehk tabelitel põhineva andmebaasi juurde. Kui aga sünniaasta järele
grupeeritakse ja viimane ka ilusti näha on – siis püsib kõik korras. Pigem tunduks imelik, kui
näidataks küll loendamise tulemusi 1, 3 ja 3, aga poleks näha, millise aasta juurde milline arv
käib.
Sarnaselt nagu võib ridu kokku lugeda, saab ka teisi grupeerimisfunktsioone kasutada. Siin
leitakse iga sünniaasta kohta sealsete laste keskmine pikkus.
Kui rakendame päringule piiranguid WHERE abil siis esmalt filtreeritakse lähteandmed ning
alles peale seda hakatakse gruppe looma:
Kui soovime juba grupeeritud tulemusele piiranguid seada siis saame kasutada HAVING
lauseosa. Näiteks soovime moodustada korvpallimeeskonda ning tahame teada, milliste
vanusegruppide keskmine pikkus on üle 165 cm
ROLLUP, gruppide koondinfo
Grupeerimise juures on vahel võimalik ja vajalik päris mitmesuguseid andmeid koguda. Ja
mõnikord on mugav, kui ei pea iga väärtuse jaoks omaette päringut tegema, vaid võib kõik
andmed tulemusplokis ette võtta ja nendega toimetama asuda. Lihtsama näite puhul
loendatakse lapsi aastate kaupa ning lõpuks võetakse kokku, palju neid üldse nimekirjas oli.
Nagu alt näha – 6. Koguhulga juures pannakse sünniaasta kohale NULL, sest see ei käi enam
mitte ühe konkreetse sünniaasta kohta, vaid kõigi peale kokku. Sellise lisarea annab käskluse
osa WITH ROLLUP.
Kui grupeeritavaid tulpasid on rohkem, siis saab ka sellist lisastatistikat rohkem välja lugeda.
Järgnevas näites grupeeriti lapsed sünnilinna ja sünniaasta järgi. See tähendab, et ühte gruppi
sattunuksid nad vaid juhul, kui nad sündinuksid samal aastal ja oleksid ühepikkused. Iga muu
kombinatsioon annab uue grupi. Nii see loend siis ka tuleb, kui algusest lugema hakata.
Kõigepealt esimeses reas teatatakse kõigi laste keskmine pikkus 167. Seejärel järgmises reas on
esimese linna e. Tallinna laste keskmine pikkus 171. Seejärel tulevad keskmised pikkused
Tallinna lastel, kes sündinud aastatel 1996 ja 1997. Viiendas reas on Tartu laste keskmine
pikkus ning see järel aastatel 1995 ja 1997 sündinud Tartu laste keskmised pikkused.
Nagu näha tähistatakse üldkokkuvõtted määramata e. NULL väärtusega kokku võetud väljal.
Võib tekkida olukord kus kokkuvõetav väli ise võib sisaldada määramata väärtuseid. Näiteks
meie tabelis on ühel lapsel sünnilinn teadmata. Kui nüüd leida grupid saame tulemuseks kaks
väga sarnast rida:
Tekib küsimus kus on kõigi laste keskmine ning kus on teadmata sünnilinnaga laste keskmine
pikkus. Selle probleemi lahendamiseks saame probleemsele väljale rakendada GROUPING
funktsiooni:
GROUPING funktsioon tekitab meile veeru, kus on 1 juhul kui on tegemist üldkokkuvõttega
ning vastasel juhul on väärtus 0. Seega saame teada, et lastel kelle sünnilinna me ei tea on
keskmine pikkus 165, kõigi laste keskmine pikkus on 166 ning lastel, kelle sünnilinna me ei tea
ja sünniaasta on 1996 on keskmine pikkus 165.
CUBE, täiendatud koondinfo
Kui eelmises päringus olnud WITH ROLLUP asendada reaga WITH CUBE, siis tehakse
grupeerimist kaks korda vastupidistes suundades ning näidatakse tulemuseks neid kahte
tulemust ühendatuna. Seega saame lisaks teada, et 1995 sündinute keskmine pikkus on 171,
1996 sündinutel 161 ning 1997 sündinutel 168.
Et ühe sentimeetri kaupa grupeering on nii väikese inimeste arvu puhul ilmselt liiast, võib võtta
inimeste jaotuse mõnevõrra suurema piirkonna ehk detsimeetri järgi. Avaldis pikkus/10 annab
täisarvude puhul jagatise täisosa. Ehk siis 157/10 annab tulemuseks 15 ja 163/10 tuleb 16.
Selliselt saab lapsed 10 sentimeetri kaupa gruppidesse jagada ning grupi andmetel on juba
mõnevõrra mõistlikum sisu. Et väljatrükil poleks näha mitte 15 ja 16, vaid 150 ja 160, selleks
korrutati SELECT real täisarvuks muutunud jagatis uuesti kümnega. Saadud tulemustest võib
välja lugeda, et 1996ndal sündinute hulgas on kaks last 150ndates ning üks 160ndates. Ning
kõigi aastate peale kokku on 4 inimest 150 ja 160 vahel ning 3 inimest 160 ja 170 vahel.
Harjutused (pikemad päringud)
Loo autode tabel, kus on iga masina kohta kirjas mark, registreerimisnumber ja
väljalaskeaasta
Trüki välja kõik autod, mille registreerimismärk sisaldab A-d
Väljasta iga margi kohta, mitu eksemplari seda on.
Väljasta iga margi ja väljalaskeaasta komplekti kohta, mitu seda on.
Väljasta iga margi kohta keskmine väljalaskeaasta
Väljasta iga margi kohta suurima ja vähima väljalaskeaasta vahe.
Katseta ROLLUP ja CUBE lisainfo võimalusi eelmiste päringute juures
Mitu tabelit
Ühes andmebaasitabelis hoitakse üldjuhul ainult ühte liiki andmeid, mille kohta annab
soovitavalt selge seletuse ka tabeli pealkiri. Kui on tunda, et lisanduvad andmed ei taha enam
selle pealkirja või olemasolevate väljade peale ära mahtuda, siis enamasti on targem uus tabel
teha. Tabelite rohkust ei pea pelgama. Pigem on kasulik teha mitu tabelit, kui ühte tabelisse
suruda kokku mitmesuguseid väärtusi, mis sinna ei taha passida.
Samuti on mõistlik tabeleid lisada, kui paistab, et samu andmeid tuleks muidu lisada
korduvalt. Ühelt poolt tekitab samade andmete korduv sisestamine ohu, et kusagil tehakse
sisestamisel viga ning selle tulemusena näidatakse tulevikus kord õigeid, kord valesid
andmeid. Teiseks ühekordse sisestuse eeliseks on, et andmete muutumisel piisab muutusest
vaid ühes kohas.
Enamasti on andmebaasides tabelid omavahel ühendatud. Siin näites koostame
lemmikloomade tabeli, kus iga looma juures võib lisaks nimele olla pikkus, mass, sünniaeg.
Samas igal lemmikloomal on peremees, kel on enesel nimi, isikukood ja muud inimesele
omased tunnused. Kui püütaks kõik andmed ühte tabelisse kokku toppida, siis tuleks iga uue
lemmiklooma puhul kirjutada uuesti ka tema peremehe andmed – muidu jääksid vastavad
lahtrid lihtsalt tühjaks ja poleks kindel, kelle juurde loom kuulub. Et aga lemmiklooma
peremeheks sobivad inimesed on eraldi tabelis juba kirjas, piisab, kui lisada iga looma juurde
tema peremehe id-number ning ongi üheselt looma peremees määratud.
Järgnevalt siis lemmikloomade tabeli loomiskäsk. Igale tabelile iseloomulikult id-tulp
isesuureneva primaarvõtmena, et oleks kindel järjekorranumber, mille kaudu loomale viidata.
Looma nimi – tekst pikkusega kuni 50 sümbolit. Arv peremehe id-numbri meelespidamiseks.
Ning lõpuks teade baasile
FOREIGN KEY (peremehe_id) REFERENCES lapsed(id)
ehk siis võõrvõti (väärtus lemmikloomade väljast peremehe_id) näitab tabeli lapsed tulbale id.
Selle lause järgi oskab SQL Server kontrollida, et tabelisse lubatakse lisada vaid lemmikloomi,
kelle peremehe_id näitab tabelis olemasolevale lapsele.
CREATE TABLE lemmikloomad(
id INT identity PRIMARY KEY,
loomanimi VARCHAR(50),
peremehe_id INT,
FOREIGN KEY (peremehe_id) REFERENCES lapsed(id)
)
Andmete lisamine INSERT lause abil nagu igal pool mujalgi. id-tulba väärtuse määrab
programm ise, loomanimi ja peremehe identifikaator antakse ette lausega. Kui vastava
järjekorranumbriga peremees on tabelis olemas, siis õnnestub kõik ilusti.
INSERT INTO lemmikloomad (loomanimi, peremehe_id) VALUES
('Miisu', 5);
INSERT INTO lemmikloomad (loomanimi, peremehe_id) VALUES
('Pauka', 7);
Madis ehk tegelane number kuus sai aga eespool tabelist kustutatud. Kui nüüd püütakse Muri
kirja panna Madise koerana, siis annab arvuti vastu veateate.
INSERT INTO lemmikloomad (loomanimi, peremehe_id) VALUES
('Muri', 6);
Msg 547, Level 16, State 0, Line 1
The INSERT statement conflicted with the FOREIGN KEY constraint
"FK__lemmikloo__perem__117F9D94". The conflict occurred in database
"baas1", table "dbo.lapsed", column 'id'.
The statement has been terminated.
Öeldakse, et sisestatud võõrvõti ei sobi tabeli lapsed veeru id väärtusega. Ning Muri jääb
sisestamata. Selle üle võib veenduda ka lemmikloomade tabelist andmeid küsides:
SELECT * FROM lemmikloomad
id
loomanimi
peremehe_id
1
Miisu
5
2
Pauka
7
Ehk siis said kirja Miisu ja Pauka, aga mitte Muri. Sest Muri puhul polnud võimalik üles
märkida tabelis kirjas olevat peremeest.
Seoseid on võimalik luua ka juba valmis tabelite vahele. Selleks tuleb tabelit muuta ALTER
TABLE käsuga. Näiteks kui soovime luua seose tabelite Laps_tbl ja Linn_tbl vahele võiksime
kasutada järgmist konstruktsiooni:
ALTER TABLE dbo.Laps_tbl
ADD CONSTRAINT FK_Laps_Linn
FOREIGN KEY ( SynniLinn )
REFERENCES dbo.Linn_tbl (LinnID)
ON UPDATE NO ACTION
ON DELETE NO ACTION
Nagu näha saab välisvõtmele lisada juurde ka käitumisreeglid juhuks kui peatabelis võti
muutub või kustub. Valikuid tegevusteks on neli:
* NO ACTION – ei tehta midagi st kui Laps tabelis on mõnel lapsel linn, mida üritatakse
kustutada siis kustutamine katkestatakse ning antakse veateade
* CASCADE – antakse edasi e. kui kustutad/muuta linna kustuvad/muutuvad
automaatselt (ILMA HOIATUSTETA) ka kõik selle linna lapsed
* SET NULL – kui kustub/muutub linn pannakse kõigi selle linna laste sünnilinnaks
NULL e. määramata. See eeldab, et NULL väärtused on lubatud-
* SET DEFAULT – kui linn kustub/muutub siis taastatakse lastel vaikimisi määratud e
DEFAULT linnad. Kui DEFAULT on määramata üritatakse panna NULL väärtust.
Kui ka see ei õnnestu siis tegevus katkestatakse.
Tabelite ühendamine päringutes
Praegu on meil olemas kaks eraldi tabelit. Laste loetelu ning lemmikloomade loetelu, iga
lemmiklooma juures on kirjas lapse id, kelle ülesandeks on vastava looma eest hoolt kanda.
Soovides teada, kelle oma on Miisu, tuleb "käsitsi" uurides minna kõigepealt lemmikloomade
tabelisse, otsida sealt üles Miisu peremehe_id ning siis minna selle arvu järgi laste tabelist
peremehe nime ja muid andmeid otsima. Et selline tabelite ühendamine aga on andmebaaside
juures sage ja hädavajalik, siis on ühendamise jaoks loodud omad käsklused.
Järgnevalt ühendatakse laste andmetabeli külge lemmikloomade andmetabel, kusjuures ridade
kõrvutamise tingimuseks on, et lapse id-number ning lemmiklooma peremehe_id-number
oleksid võrdsed. Tärn SELECTi järel teatab, et näidataks kõiki võimalikke veerge. Ridadest
on praeguse päringu puhul nähtavad ainult need lapsed, kel lemmikloom olemas. Ja kui mõnel
lapsel oleks mitu lemmiklooma, siis näidataks ka selle lapse andmed mitmekordselt.
Mitmekordsest näitamisest hoolimata talletatakse aga selle lapse andmeid baasis ikkagi
ühekordselt. Nii et kui kellegi pikkus peaks muutuma, siis piisab selle märkimisest ühes kohas.
SELECT * FROM lapsed
INNER JOIN lemmikloomad
ON lemmikloomad.peremehe_id=lapsed.id
id
eesnimi
pikkus
synniaasta
id
loomanimi
peremehe_id
5
Siiri
153
1996
1
Miisu
5
7
Siim
163
1997
2
Pauka
7
INNER JOINi nimeline süntaks on levinud SQL Serveris. Sama toimingu kirjapanekuks aga
on ka teine viis, mis töötab nii siin kui teiste SQL standardit arvestavate andmebaaside peal.
FROM-sõna järgi kirjutatakse kõikide osalevate tabelite loetelu ning WHERE-tingimusega
seatakse, millised read peavad omavahel võrdsed olema. Nagu näha, on tulemus eelmise
päringuga võrreldes samasugune.
SELECT * FROM lapsed, lemmikloomad
WHERE lemmikloomad.peremehe_id=lapsed.id
id
eesnimi
pikkus
synniaasta
id
loomanimi
peremehe_id
5
Siiri
153
1996
1
Miisu
5
7
Siim
163
1997
2
Pauka
7
Tabeleid ühendades saab lihtsalt mitmest tabelist kokku ühe. Muud tingimused ja
järjestamised käivad ikka samamoodi. Ehk siis, kui tahta panna andmed peremeeste nimede
järgi tähestikulisse järjekorda, siis aitab endiselt ORDER BY eesnimi.
SELECT * FROM lapsed, lemmikloomad
WHERE lemmikloomad.peremehe_id=lapsed.id
ORDER BY eesnimi
id
eesnimi
pikkus
synniaasta
id
loomanimi
peremehe_id
7
Siim
163
1997
2
Pauka
7
5
Siiri
153
1996
1
Miisu
5
Tabelite järjekord päringus määrab ka nende järjekorra trükitavas vastuses. Kui andmete poole
pöördutakse tulba nime järgi, pole sel erilist vahet. Kui aga kasutatakse tulba järjekorraumbrit,
siis peab teadma, mitmendana milline tulp kus asetseb. Siin siis lemmikloomad ees ja lapsed
järgi.
SELECT * FROM lemmikloomad
INNER JOIN lapsed
ON lemmikloomad.peremehe_id=lapsed.id
id
loomanimi
peremehe_id
id
eesnimi
pikkus
synniaasta
1
Miisu
5
5
Siiri
153
1996
2
Pauka
7
7
Siim
163
1997
Tahtes näha vaid osa tulpasid kõigi asemel, tuleb nende tulpade nimed ette lugeda. Ikka
sarnaselt nagu ühestki tabelist tehtavate päringute korral.
SELECT eesnimi, loomanimi FROM lemmikloomad
INNER JOIN lapsed
ON lemmikloomad.peremehe_id=lapsed.id
Siiri Miisu
Siim Pauka
Et INNER JOIN on SQL Serveri jaoks vaikimisi ühendusviis, võib sõna INNER ära jätta –
päring töötab ikka samamoodi.
SELECT eesnimi, loomanimi FROM lemmikloomad
JOIN lapsed
ON lemmikloomad.peremehe_id=lapsed.id
Siiri Miisu
Siim Pauka
LEFT ja RIGHT JOIN
Kõige tavalisema ühendamise puhul saime kahest tabelist kätte need read, mis mõlemas
olemas olid. Ehk siis loetelus olid vaid lemmikloomaga lapsed ning samuti igas loetelus olnud
lemmikloomal oli kõrval peremees. Et praeguses näites ei lubata peremeheta lemmikloomi
tabelisse lisada, siis jääb ära ka võimalus ülejäänud lemmikloomade näitamiseks. Küll aga võib
mõnikord olla soov näha ka neid lapsi, kel pole oma koera või kassi. Ning samas
loomaomanikele panna kõrvale ka loomade andmed. Sellise tööga saab hakkama LEFT JOIN.
Loetelus esimesena olnud tabelist ehk vasakust näidatakse välja kõik read. Paremast aga vaid
need, kus seos vasaku tabeliga olemas. Kel looma pole, sel tuleb loomanime kohale tühiväärtus
NULL.
SELECT eesnimi, loomanimi FROM lapsed
LEFT JOIN lemmikloomad
ON lemmikloomad.peremehe_id=lapsed.id
eesnimi
loomanimi
Juku
NULL
Kati
NULL
Mati
NULL
Ats
NULL
Siiri
Miisu
Siim
Pauka
Mari
NULL
Sarnaselt töötab RIGHT JOIN. Ainult selle vahega, et näidatakse välja kõik parempoolses
tabelis olevad andmed. Kui mõnele reale ei vasta kirjet vasakpoolses tabelis, siis näidatakse
selle koha peal vasakpoolse tabeli väljade kohal NULL. Et siin aga on igal loomal peremees,
siis tühiväärtusi ei teki.
SELECT eesnimi, loomanimi FROM lapsed
RIGHT JOIN lemmikloomad
ON lemmikloomad.peremehe_id=lapsed.id
Siiri Miisu
Siim Pauka
LEFT JOINi ja RIGHT JOINi pikem kuju on LEFT OUTER JOIN ning RIGHT OUTER
JOIN. Aga nagu näha, tulemus jääb samaks.
SELECT eesnimi, loomanimi FROM lapsed
RIGHT OUTER JOIN lemmikloomad
ON lemmikloomad.peremehe_id=lapsed.id
Siiri Miisu
Siim Pauka
Nende ühendamiste puhul peab kindlasti silmas pidama tabelite järjekorda. Kui panna
lemmikloomad vasakuks tabeliks ja lapsed parempoolseks tabeliks ning ühendamisel kasutada
RIGHT JOINi ning tulbad nime järgi välja kutsuda, siis on tulemus sama, kui oleks kasutanud
tabeleid teises järjekorras ning ühendamiseks LEFT JOINi.
SELECT eesnimi, loomanimi FROM lemmikloomad
RIGHT JOIN lapsed
ON lemmikloomad.peremehe_id=lapsed.id
eesnimi
loomanimi
Juku
NULL
Kati
NULL
Mati
NULL
Ats
NULL
Siiri
Miisu
Siim
Pauka
Mari
NULL
CROSS JOIN
Kõikide võimalike kombinatsioonide väljatrükiks sobib CROSS JOIN. Sel juhul võtmeid
tabelite ühendamiseks ei kasutata, vaid trükitakse välja kõik võimalikud kombinatsioonid,
kuidas esimese tabeli read saavad olla ühendatud teise tabeli ridadega. Ehk siis siin näites
pakutakse välja kõik kombinatsioonid, milline laps saab millise lemmikloomaga koos olla.
SELECT eesnimi, loomanimi FROM lemmikloomad
CROSS JOIN lapsed
Juku Miisu
Kati Miisu
Mati Miisu
Ats Miisu
Siiri Miisu
Siim Miisu
Mari Miisu
Juku Pauka
Kati Pauka
Mati Pauka
Ats Pauka
Siiri Pauka
Siim Pauka
Mari Pauka
Eks sellist segapudru läheb suhteliselt harvem vaja, aga ilus on vaadata, kes võib kellega koos
olla. Samuti sobib CROSS JOIN olukordade jaoks, kui tahetakse kõikide võimalike variantide
hulgast sobivat välja otsida. Näiteks soovitakse otsida kombinatsioonid, kus lapse ja looma
nimed algavad sama tähega, või siis on nad sündinud samas kuus. Siinse näite puhul on
tingimused pastakast välja imetud, aga mõne tutvumisõhtu puhul või laborikatsete juures
võivad sellised valikud täiesti omal kohal olla.
CROSS JOINiga sama tulemuse annab, kui päringusse kirjutada lihtsalt tabelite nimed ilma
täiendavaid tingimusi seadmata.
SELECT eesnimi, loomanimi FROM lemmikloomad, lapsed
Juku Miisu
Kati Miisu
Mati Miisu
Ats Miisu
Siiri Miisu
Siim Miisu
Mari Miisu
Juku Pauka
Kati Pauka
Mati Pauka
Ats Pauka
Siiri Pauka
Siim Pauka
Mari Pauka
Seos sama tabeliga
Esimese hooga võib tunduda imelik, miks peaks olema vaja siduda tabelit iseenesega. Aga
rakendusi kirjutades tekib selliseid seostamiskohti üllatavalt palju. Näiteks kui kataloogid on
kataloogipuus, siis seda struktuuri saab tabelisse salvestada nii, et iga kataloogi puhul
kirjutatakse eraldi tulpa tema ülemkataloogi ID. Ning juurkataloogi puhul see arv näitab
iseenesele või ei näita kuhugi. Samuti foorumi kirjade puhul, kui tahetakse meeles pidada,
milline kiri millisele vastab. Siin aga vaatame, kuidas seos sama tabeliga toimub sünniaastate
kaudu. Esialgu koostatakse päring, kus näidatakse kõikide laste paarid nendega samal aastal
sündinud lastega. Et saaks tabelit iseenesega seostada, tuleb tabelist teha päringu ajaks kaks
koopiat. Nii nagu sai päringus tulpasid ümber nimetada, nii saab ümber nimetada ka tabeleid.
SELECT * FROM lapsed as tabel1, lapsed as tabel2
ütleb, et võta tabel lapsed kõigepealt märksõna all tabel1 ning seejärel tabel lapsed ka
märksõna all tabel2. Edasi juba võib need tabelid tingimus(t)e abil kokku siduda, sest muidu
näidataks kõikide ridade omavahelised võimalikud kombinatsioonid. Et siin aga soovime
paare vaid sünniaastate kaupa, siis nõuame, et eri tabeli ridade kõrvuti panekuks peavad nende
laste sünniaastad kattuma.
SELECT * FROM lapsed as tabel1, lapsed as tabel2
WHERE tabel1.synniaasta=tabel2.synniaasta
id
eesnimi
pikkus
synniaasta
id
eesnimi
pikkus
synniaasta
1
Juku
155
1997
1
Juku
155
1997
2
Kati
158
1997
1
Juku
155
1997
7
Siim
163
1997
1
Juku
155
1997
1
Juku
155
1997
2
Kati
158
1997
2
Kati
158
1997
2
Kati
158
1997
7
Siim
163
1997
2
Kati
158
1997
3
Mati
164
1995
3
Mati
164
1995
4
Ats
165
1996
4
Ats
165
1996
5
Siiri
153
1996
4
Ats
165
1996
8
Mari
158
1996
4
Ats
165
1996
4
Ats
165
1996
5
Siiri
153
1996
5
Siiri
153
1996
5
Siiri
153
1996
8
Mari
158
1996
5
Siiri
153
1996
1
Juku
155
1997
7
Siim
163
1997
2
Kati
158
1997
7
Siim
163
1997
7
Siim
163
1997
7
Siim
163
1997
4
Ats
165
1996
8
Mari
158
1996
5
Siiri
153
1996
8
Mari
158
1996
8
Mari
158
1996
8
Mari
158
1996
Üllatusena avastame, et Juku paariliseks on pandud ka Juku ise. Ning paarina on olemas nii
Juku Katiga kui Kati Jukuga. Selliseid anomaaliaid saab tingimuste täpsustamisega vähendada
või kaotada.
Kui soovida, et sama isik ei oleks iseenesega kõrvuti, siis aitab tingimus, et kõrvuti seatud
tabelikoopiate id-numbrite väärtused ei oleks võrdsed. Kui soovida, et sama paari korduvalt ei
näidataks, siis võib seada näiteks tingimuse, et teisest tabelist tuleva inimese id-number oleks
suurem kui esimesest tabelist tulev id-number. Sellisel juhul jääb igast paarist alles vaid üks
väljatrükk – selline, mis vastab tingimustele.
Tahame ainult ühe konkreetse isiku eakaaslasi kätte saada, võib tema eraldi ära määrata.
Kindlam oleks küll id kaudu, sest mitme Juku puhul võivad tekkida segadused. Meil aga on
vaid üks Juku, seetõttu on loota, et vastus sobib ning tema eakaaslasteks on siin tabelis vaid
Kati ja Siim.
SELECT tabel2.eesnimi from lapsed as tabel1, lapsed as tabel2
WHERE tabel1.synniaasta=tabel2.synniaasta
AND tabel1.id<>tabel2.id and tabel1.eesnimi='Juku'
Kati
Siim
Päringutulemuste ühendamine
Vahel on mugav, kui päringuga saab soovitud tulemuse võimalikult täpselt ette anda – et
hiljem poleks muretsemist, kuidas üksikutest lõikudest tervet vastust kokku panna. Üheks
mooduseks on päringutulemuste ühend – UNION. Tahtes teha nimekaarte kõigile lastele ja
lemmikloomadele, võib nende nimed kokku liita.
SELECT eesnimi FROM lapsed
UNION
SELECT loomanimi FROM lemmikloomad
Vastuses oleva tulba nimi võetakse esimese päringu järgi. Ja ongi kõik erinevad nimed siin.
eesnimi
Ats
Juku
Kati
Mari
Mati
Miisu
Pauka
Siim
Siiri
Samuti võib mingil põhjusel olla soov välja tuua kõik tabelis leiduvad arvud. Kaks päringut
ühtejärge, UNION vahele ning loetelu ongi käes.
SELECT pikkus FROM lapsed
UNION
SELECT synniaasta FROM lapsed
pikkus
153
155
158
163
164
165
1995
1996
1997
Nagu näidete põhjal aimata sai, lähevad UNION käsu põhjal korduvad väärtused kaduma.
Tegemist matemaatilises mõttes hulgatehtega, mille tulemusena jäetakse alles vaid erinevad
väärtused. Tahtes, et ka kõik korduvad arvud või sõnad näha oleksid, selleks sobib kahe
päringu vahele panna UNION ALL. Nii näeme mõlema päringu tulemuste summat tervikuna.
SELECT pikkus as arvud FROM lapsed
UNION ALL
SELECT synniaasta FROM lapsed
ORDER BY arvud
arvud
153
155
158
158
163
164
165
1995
1996
1996
1996
1997
1997
1997
Harjutused (tabelite ühendamine)
Ühenda UNION ALL abil laste sünniaastad ja autode väljalaskeaastad
Lisa seos Linna_tbl ning Auto_tbl vahele
Väljasta kõik autod koos Linna nimetustega, kus nad registreeritud
Väljasta autode arv grupeerituna Linnade kaupa
Lisa Linnade tabelisse uus linn
Väljasta LEFT JOINi abil kõik linnad koos neis registreeritud autodega. Registreeritud
autodeta maakonnast väljasta vaid nimi.
Kingi igale lapsele auto, mille margiks on FORD, väljalaske aasta on lapse sünniaasta
ning numbrimärk moodustub lapse pikkus + esimesed 3 tähte lapse nimest.
Alampäringud
Kui üksik päring kipub liialt keerukaks minema või ei paista mõnd tulemust olema lootustki
tavalise päringuga välja arvutada, siis võib aidata alampäring. Nii nagu avaldiste kirjutamisel
võib igasugu väärtused asendada funktsioonidega, nii saab SQL-päringute puhul
olemasolevad kohad asendada alampäringutega. Kusjuures tasub eristada kolme võimalust.
Ühel juhul antakse alampäringu vastuseks terve tabel (nt. SELECT * FROM lapsed). Sel juhul
saab alampäringu panna kohale, kus muidu oli tabeli nimi.
Teisel juhul väljastab alampäring ühe veerutäie andmeid. Sel juhul võib kontrollida, kas uuritav
rida vastab vähemasti ühele päringus väljastatud väärtustest. Kontrollimiseks käsklus IN,
millest edaspidi.
Kolmas ja loodetavasti kõige lihtsam võimalus on, kus alampäring väljastab vaid ühe arvu. Sel
juhul saab päringu panna selle väärtuse kohale. Alampäring kirjutatakse alati sulgudesse.
Tabeli asendaja
Siin tehti võimalikult lihtne näide, kus päringu tulemusena loodud tabelist küsitakse eraldi
väärtused välja. SELECT * FROM lapsed annab tulpadeks id, eesnimi, pikkus ja synniaasta.
Täiend "as tabel1" ütleb, et selle alampäringu tulemust saab edaspidises päringus kasutada
nime all tabel1. Ning praegu lihtsalt küsitaksegi sealt soovitud tulbad välja.
SELECT tabel1.eesnimi, tabel1.pikkus FROM
(SELECT * FROM lapsed) as tabel1
Juku 155
Kati 158
Mati 164
Ats 165
Siiri 153
Siim 163
Mari 158
Selline vahetabelist edasi küsimine võib aga toimuda ka tunduvalt keerulisemate päringute
puhul, kus ühe päringu tulemusena saadakse tabelikujuline vastus kokku ning seda asutakse
järgmise päringuga edasi töötlema.
Väärtuse asendaja
Agregaatfunktsiooni või ka ühe konkreetse rea lahtri küsimise peale saab SQL-päringu panna
väljastama vaid üht väärtust. Seda üksikut väärtust võib taas edaspidises päringus kasutada.
Siin leitakse kõigepealt alampäringuga laste keskmine pikkus. Edasi väljastatakse kõikide laste
andmed, kelle pikkus ületab keskmist.
SELECT eesnimi, pikkus FROM lapsed
WHERE pikkus>(SELECT AVG(pikkus) FROM lapsed)
Mati 164
Ats 165
Siim 163
Kontrolliks saab alampäringu väärtust ka eraldi vaadata. Näeme, et laste keskmine pikkus
tabelis on 159 sentimeetrit.
SELECT AVG(pikkus) FROM lapsed
159
Väljaarvutatud väärtust võib ka avaldise sees tarvitada. Siin leitakse iga lapse pikkuse erinevus
keskmisest pikkusest.
SELECT eesnimi, pikkus,
pikkus - (SELECT AVG(pikkus) FROM lapsed) as erinevus
FROM lapsed
eesnimi
pikkus
erinevus
Juku
155
-4
Kati
158
-1
Mati
164
5
Ats
165
6
Siiri
153
-6
Siim
163
4
Mari
158
-1
Sarnase pikkusega lapsed
Järgnevalt mõned näited, kuidas sama ülesannet saab alampäringute abil mitmel moel
rakendada. Lisatud näited on tehtud ühe tabeli andmete põhjal. Vähegi suuremas andmebaasis
aga käiakse väärtusi sageli küllalt kaugelt küsimas.
Läbimängitava ülesandena otsitakse tabelist iga lapse kohta, kui palju on temaga sarnase
pikkusega teisi lapsi. See tähendab arvuti keeles, et otsitakse iga lapse puhul, mitu on neid
lapsi, kelle pikkus erineb temast mitte rohkem kui ühe sentimeetri võrra. Üheks võimaluseks on
teha lihtsalt eraldi tulp. Selles tulbas väärtuse leidmiseks tuleb andmebaasimootoril igal korral
vastav päring uuesti käivitada. Iga kord, kui välimises päringus võetakse ette uus inimene,
loetakse kolmanda tulba ehk sarnase pikkusega laste leidmiseks uuesti kokku kõik lapsed, kelle
pikkuse erinevust just sellest konkreetsest trükitavast lapsest on 1 sentimeeter või vähem. ABS
tähendab absoluutväärtust. Miinus üks tulbaavaldise lõpus on vajalik, kuna trükitav laps
loetakse alampäringu abil ka ise iseendaga ühepikkuste laste hulka. Et igaüks on enesega sama
pikk, saabki lahutamistehte abil väärtuse õigeks.
SELECT eesnimi, pikkus,
(
SELECT COUNT(*) FROM lapsed as tabel2
WHERE ABS(tabel2.pikkus-tabel1.pikkus)<=1
)-1 as sarnaseid
FROM lapsed as tabel1
eesnimi
pikkus
sarnaseid
Juku
155
0
Kati
158
1
Mati
164
2
Ats
165
1
Siiri
153
0
Siim
163
1
Mari
158
1
Tollest miinus ühest saab vabaneda, kui eraldi tingimusse lisada, et trükitavat last ennast
samapikkade laste kokku lugemisel ei arvestata. Ehk siis trükitava lapse id (tabel1.id) ning
loetava lapse id (tabel2.id) ei tohi kattuda.
SELECT eesnimi, pikkus,
(
SELECT COUNT(*) FROM lapsed as tabel2
WHERE ABS(tabel2.pikkus-tabel1.pikkus)<=1
AND tabel1.id<>tabel2.id
) as sarnaseid
FROM lapsed as tabel1
Ehkki kood läks veidi pikemaks, võib see hiljem paremini loetav olla. Sest salapärane -1 võib
võõrale lugedes päris palju peavalu valmistada. Kui aga ilusti tingimuse abil kontrollitakse, et
trükitav tegelane ei oleks kokkuloetavate hulgas – see on loodetavasti kergemini mõistetav.
Tulemused on samad nagu eelmise päringu korral.
eesnimi
pikkus
sarnaseid
Juku
155
0
Kati
158
1
Mati
164
2
Ats
165
1
Siiri
153
0
Siim
163
1
Mari
158
1
Järgnevalt kasutame pikkuskaimude leidmiseks EXISTS-lauset päringu tingimusosas. Kui
ennist loeti kokku, mitu sobiva pikkusega kaaslast leiti, siis siin küsitakse iga uuritava lapse
puhul soovitud tingimusele vastavad kaaslased. Pikkuskaimu leidumise korral on EXISTS-
kontrolli tingimus tõene ning vastava eesnime ja sünniaasta võib välja kirjutada.
SELECT eesnimi, pikkus
FROM lapsed as tabel1
WHERE EXISTS (
SELECT * FROM lapsed as tabel2
WHERE ABS(tabel2.pikkus-tabel1.pikkus)<=1
AND tabel1.id<>tabel2.id
)
Kati 158
Mati 164
Ats 165
Siim 163
Mari 158
Sama ülesande võib lahendada veel kolme SELECTi abil. Algus on sarnane nagu esimeses
näites, kus trükkimise kolmandas tulbas arvutati välja, mitu pikkuskaimu iga lapse puhul on.
Kuid kui nüüd me ei taha saada mitte arvu, vaid loetelu nendest, kel kaimud olemas, siis saab
väljastatud tabelile lihtsalt veel ühe päringu ümber panna. Eesnimi ja pikkus väljastatakse ka
välimises päringus. Kolmanda tulba ehk "sarnaseid" väärtust aga kasutatakse otsustamiseks,
kas vastavat rida näidata või mitte.
SELECT eesnimi, pikkus FROM
(SELECT eesnimi, pikkus,
(
SELECT COUNT(*) FROM lapsed as tabel2
WHERE ABS(tabel2.pikkus-tabel1.pikkus)<=1
AND tabel1.id<>tabel2.id
) as sarnaseid
FROM lapsed as tabel1
)as tabel3
WHERE tabel3.sarnaseid>0
Kati 158
Mati 164
Ats 165
Siim 163
Mari 158
Veeru asendaja
Kontroll IN võimaldab tingimuses uurida, kas otsitav väärtus kattub mõnega teises päringus
väljastatud väärtustest. Siinsel juhul siis sisemises päringus leitakse kõik 1997. aastal sündinud
laste pikkused. Edasi välimises päringus väljastatakse kõikide laste andmed, kelle pikkus
kattub kasvõi ühega eelpoolleitud pikkustest.
SELECT eesnimi, synniaasta FROM lapsed
WHERE pikkus IN (
SELECT pikkus FROM lapsed
WHERE synniaasta=1997
)
Nagu tulemustest näha, on tulemusridade hulgas lisaks 1997. sündinutele ka üks 1996. aastal
sündinu, kel pikkust samapalju kui mõnel aasta nooremal. Tõepoolest – Mari ja Kati on
ühepikkused. Ehkki esimene neist sündinud 1996. ning teine 1997. aastal.
Juku 1997
Kati 1997
Siim 1997
Mari 1996
Tekkinud tabelite ühendamine
Ka alampäringus tekkinud tabeleid saab teistega ühendada sarnaselt tavalistele võimalustele.
Siin leitakse tagumises päringus iga sünniaasta kohta suurim pikkus. Ning tabelite ühendamise
kaudu (ehkki praegu ühendatakse laste tabelist saadud tulemus algse tabeli enesega) leitakse
iga suurima pikkuse kohta inimese nimi ja sünniaasta, kellel selline pikkus on. Juhul, kui
juhtuks aastakäigu suurima pikkusega olema võrdselt mitu inimest, siis trükitaks nad kõik nagu
tabelite ühendamise puhul kombeks.
SELECT eesnimi, synniaasta, pikkus FROM lapsed as tabel1
INNER JOIN
(SELECT MAX(pikkus) as suurim FROM lapsed
GROUP BY synniaasta)as tabel2
ON tabel1.pikkus=tabel2.suurim
Mati 1995 164
Ats 1996 165
Siim 1997 163
Harjutused (Alampäringud)
Teata alampäringu abil kõik keskmisest vanemad autod
Teata alampäringu abil markide kaupa kõik selle margi keskmisest vanemad autod
Teata iga linna kohta selles linnas liikuva kõige vanema sõiduki mark/margid
Lisavõimalused
Andmetüüpide loomine
Andmetüüpide loomiseks on vaja andmebaasi programmeerimisobjektide alt ülesse otsida
Types\Use-defined Data Types ning lisada sinna uus:
Andmetüübi loomisel saate ära määrata uue andmetüübi nime, süsteemse andmetüübi, millele
uus andmetüüp baseerub, kas vaikimisi lubatakse määramata väärtusi või mitte ning milliseid
reegleid rakendatakse. Reeglitest räägime pisut hiljem.
Kogu selle töö saab ära teha ka lihtsa SQL Lausega:
CREATE TYPE [dbo].[Aasta] FROM [smallint] NOT NULL
Uue tabeli loomisel saame oma tehtud andmetüüpi kasutada nagu süsteemsetki. Kui soovime
nüüd tagantjärele olemasolevates tabelites asenduse teha siis on olukord keerulisem kuna SQL
Server selliseid asendusi ei luba. Selle asenduse saame aga teha väikese skriptiga, mis esmalt
loob uue välja, seejärel kopeerib kõik andmed vanalt väljalt uuele ning kustutab vana välja.
ALTER TABLE dbo.Auto_tbl
ADD tmp_Aasta dbo.Aasta
GO
UPDATE dbo.Auto_tbl
SET tmp_Aasta = Aasta
ALTER TABLE dbo.Auto_tbl
DROP COLUMN Aasta
GO
EXECUTE sp_rename N'dbo.Auto_tbl.tmp_Aasta', N'Aasta', 'COLUMN'
GO
Isetehtud andmetüübid võimaldavad lihtsama vaevaga ühtlustada/hoida ühtsena sarnaste
tunnustega andmete salvestamise.
Andmete ühtsuse tagamine
Andmete ühtsuse tagamiseks on SQL serveris mitmeid võimalusi:
1. andmetüübid
2. konstraandid e. piirangud
3. indeksid
4. võtmed
5. triggerid e. päästikud
6. programsed vahendid
Piirangud
Kuigi enamikes olukordades on andmetüübist tulenev piirang täiesti piisav, on palju olukordi,
kus oleks soov täpsemalt määratleda andmete iseloom.
Andmetele piirangute seadmisel tulevad appi konstraandid e. piirangud.
Piiranguid on võimalik seada nii ühele väljale kui ka kirjele.
Ühele väljale e. veerule seatavad piirangud on enamasti kitsendused andmetüübile. Näiteks
oleme loonud aastaarvude hoidmiseks välja ning soovime, et meie aastad oleksid vahemikus
1900 kuni jooksev aasta.
ALTER TABLE dbo.Auto_tbl
ADD CONSTRAINT CK_Auto_Aasta
CHECK (Aasta BETWEEN 1900 AND YEAR(GETDATE()))
Kirje piirangutega kirjeldatakse seoseid samal real asuvate väljade vahel. Näiteks kui me
soovime hoida andmebaasis laste juures õpingute alguse ja lõpu aega, saaksime seada piirangu,
mis ütleks, et algus peab olema enne lõppu.
Konstraantide rakendamine on väga kiire ning neist on abi nii andmete sisestamisel
andmeühtsuse tagamisel kui ka päringute tegemisel.
Konstraante on võimalik salvestada andmebaasi ka eraldiseisvate objektide e. reeglitena.
CREATE RULE Aasta1900KuniTana
AS @Aasta BETWEEN 1900 AND YEAR(GETDATE())
Sellisel kujul reegleid on võimalik rakendada tabeli väljadele otseselt kui ka läbi oma
andmetüüpide.
Reeglite kleepimiseks andmetüübi külge tuleb kasutada salvestatud protseduuri sp_bindrule:
EXEC sys.sp_bindrule
@rulename=N'[dbo].[Aasta1900KuniTana]'
, @objname=N'[dbo].[Aasta]'
Indeksid
Esmajärjekorras on indeksid mõeldud andmete otsingu kiirendamiseks. Indekseerimist tasub
kaaluda kõigil väljadel mida kasutatakse WHERE ja GROUP BY lauseosas ning millel
rakendatakse agregaatfunktsioone.
Teiseks saab indeksitega peale suruda andmete unikaalsust kas ühel väljal või väljade
kombinatsioonis. Sisuliselt on unikaalne indeks ka juba varem kasutatud primaarvõti.
Indekseid saab luua CREATE INDEX lausega. Näiteks loome unikaalse indeksi auto
registrinumbrite tarbeks:
CREATE UNIQUE NONCLUSTERED INDEX IX_Auto_RegNr
ON dbo.Auto_tbl ( RegNr )
Või indekseerime sorteerimise lihtsustamiseks laste nimed
CREATE NONCLUSTERED INDEX IX_Laps_Nimi
ON dbo.Laps_tbl ( Nimi )
Miinuspoolelt võib välja tuua, et igasugused muudatused indekseeritud väljadel toovad
endaga kaasa muudatused indeksites ning seega muutuvad muutmistegevused (lisamine,
muutmine, kustutamine) aeglasemaks. Samas aitavad indeksid kaasa muudetavate kirjete
leidmisel.
Alates SQL Server 2000 on võimalik tekitada indekseid ka vaadetele!
Päästikprotsess
Andmebaasides saab toiminguid ka automaatselt tööle panna. Näiteks kui soovitakse
mõningates tabelites toimunud muutused soovitud kohtadesse kokku logisse lugeda.
CREATE TABLE logi(
id INT IDENTITY PRIMARY KEY,
aeg DATETIME,
toiming VARCHAR(20),
andmed VARCHAR(20),
)
SQL 2005 võimaldab luua päästikuid ka DDL käskudele e. CREATE, ALTER ja DROP
käskudele!
Loomine
Järgnev päästik kannab iga linnade tabelis toimunud muutuse kohta teate logitabelisse.
Salapärane nimi "inserted" tähendab päästiku sees kasutatavat ajutist tabelit, mille kaudu saab
kätte lisatud rea. Selle ajutise inserted-tabeli tulpade nimed ja tüübid on samad kui tegelikul
tabelil, millega muutus toimus – siinjuhul tabel nimega linnad. Siin küsitakse linnanimi
kõigepealt eraldi muutujasse, et seda oleks mugavam päästiku raames toimuva lisamise juures
kasutada.
CREATE TRIGGER linnamuutus
ON linnad
FOR INSERT
AS
INSERT INTO logi (aeg, toiming, andmed)
SELECT GETDATE(), 'lisati', linnanimi
FROM inserted
Käivitus
Kui juhtutakse linnade tabelisse andmeid lisama, siis näen kahel korral teateid, et "ühele reale
mõjus muutus". Üks teade on siis algse lisamise kohta ning teine teade päästiku abil toimunud
logikande kohta.
INSERT INTO linnad(linnanimi, rahvaarv)
VALUES ('Valga', 25000)
(1 row(s) affected)
(1 row(s) affected)
Nõnda iga muutuse korral.
INSERT INTO linnad(linnanimi, rahvaarv)
VALUES ('Jõgeva', 17000)
Ja kui edasi minna ja vaadata, mis logisse kirjutatud, siis on teated ilusti näha. Mitmes
sündmus, millal, mida tehti ja millise linnaga on tegemist.
SELECT * FROM logi
1 2006-08-03 08:39:01.693 lisati Valga
2 2006-08-03 08:39:18.387 lisati Jõgeva
Ülesandeid
* Koosta logitabel autoregistris toimuvate muudatuste logimiseks. Tulbad
(id, auto_id, toiming, aeg, mark, aasta, maakonna_id).
* Loo päästikud lisamiste, kustutamiste, muutmiste logimiseks. Mõtle, mida
ja millistesse tulpadesse on kindlasti vajalik kirjutada, et logi järgi
oleks võimalik vana seis taastada.
* Loo salvestatud protseduur etteantud id-ga autoga tehtud toimingute
väljatrükiks inimkeelsete lausetena ajas tagant ette.
* Loo salvestatud protseduur etteantud id-ga auto seisundi näitamiseks
etteantud ajal.
Ajutiste tabelite kasutamine
Kirjutades keerukamaid programme on aeg-ajalt kasulik mingid vahetulemused meelde jätta.
Üks võimalus selleks on ajutiste tabelite kasutamine.
Ajutised tabelid on tabelid, mida püsivalt pole vaja, mis on vajalikud vaid ühe konkreetse
skripti täitmiseks. Selliseid tabeleid ei hoita enamasti samas kohas päris andmetega. SQL
Serveril on sedasorti andmete tarbeks eraldi andmebaas tempdb. Selleks, et tabel läheks
ajutisse andmebaasi tuleb tabeli nime ette panna trellid #.
Kui on soov salvestada nt autode loetelu ajutisse tabelisse saame seda teha lihtsa SELECT
lausega, millele lisame INTO võtmesõna:
SELECT *
INTO #Autod
FROM dbo.Auto_tbl
SELECT *
FROM #Autod
Sellega oleme tekitanud ajutisse andmebaasi koopia autode tabelist. Ühe trelliga tähistatud
ajutised tabelid on sessioonipõhised e. kui katkeb serveriga ühendus, mille kaudu see tabel
tehti, kustutatakse tabel automaatselt.
On võimalik luua ka globaalseid ajutisi tabeleid. Selleks tuleb nime ette panna kahed trellid ##.
Sellised tabelid kustuvad, kui katkeb viimane seda tabelit kasutav ühendus.
Ajutised tabelid on kasulikud keerukate arvutustulemuste hoidmiseks või keerulistest JOIN
lausetest saadud tulemuste hoidmiseks edasiseks töötlemiseks.
Samas tuleb arvestada, et ajutistel tabelitel pole enam mingit seost läheandmetega e. sealt
otsimiseks ei saa kasutada indekseid
Tsükkel, valik
Transact-SQLil on kasutada mitmedki programmeerimiskeelele omased tunnused, kaasa
arvatud muutujad, tsükkel ja valik. Nende tutvustamiseks väike koodilõik. Koodilõiku saab
eraldi käivitades proovida. Pärast, kui on veendutud, et lõik töötab, saab selle protseduuriks
vormistada, kirjutades ette CREATE PROCEDURE protseduurinimi AS.
Siinses näites luuakse kõigepealt kaks muutujat ning määratakse nende tüübid. Nagu näha, on
muutujatel @-märk ees.
DECLARE @i INT, @s as VARCHAR(max)
Edasi saavad muutujad enesele väärtused
SET @i=1
SET @s=''
Tsükkel toimib sarnaselt nagu mõnes teiseski keeles. Tsükli keha piiratakse BEGIN ja ENDiga.
WHILE(@i<=10) BEGIN
Sama kehtib valiku kohta. Nagu näha juhul, kui pole tegemist esimese läbimiskorraga,
lisatakse olemasoleva teksti lõppu koma. IF-ile saab soovi korral lisada ka ELSE-osa.
IF (@i>1) BEGIN
SET @s=@s+','
END
Edasi lisandub teksti lõppu tsükli läbimiskorra number
SET @s=@s+str(@i)
Ning et tsükkel lõputult kordama ei jääks, tuleb järgmise sammuna hoolitseda, et loenduri
väärtus suureneks.
SET @i=@i+1
END
Lõpuks võib SELECT-käskluse abil saadud tulemuse päringu käivitajale nähtavaks teha. Edasi
saab sellega käituda juba nagu tavalise päringu vastusega.
SELECT @s as tulemus
Edasi kood tervikuna vaatamiseks
CREATE PROCEDURE arvujada AS
DECLARE @i INT, @s as VARCHAR(max)
SET @i=1
SET @s=''
WHILE(@i<=10) BEGIN
IF (@i>1) BEGIN
SET @s=@s+','
END
SET @s=@s+str(@i)
SET @i=@i+1
END
SELECT @s as tulemus
ja väljastatud tulemus:
EXEC arvujada
tulemus
1,2,3,4,5,6,7,8,9,10
Muutujasse lugemine
Tahtes üht väärtust päringust kätte saada, saab selle omistada otse SELECT-lause juures.
DECLARE @suurimpikkus AS INT
SELECT @suurimpikkus=MAX(pikkus) FROM lapsed
PRINT @suurimpikkus
annab tulemuseks
165
Samuti võib muutujaid olla rohkem. Lihtsalt iga SELECTiga küsitud tulba ette tuleb vastav
muutuja omistamiseks panna.
Kursor
Tabelist pärinevate rohkematel ridadel paiknevate andmetega töötamiseks kasutatakse Cursor-
tüüpi muutujat.
DECLARE @crs CURSOR
Kursori kaudu andmete kätte saamiseks tuleb määrata andmete allikaks olev päring.
SET @CRS = CURSOR FOR
SELECT eesnimi FROM lapsed
Sealt omakorda andmete pruukimiseks tuleb kursor avada
OPEN @crs
Ning iga FETCH-käsklus võtab kursorist välja ühe rea.
FETCH NEXT FROM @crs
Kursori all oleva mälu vabastamiseks tuleb kõigepealt kursor sulgeda ning seejärel päris
vabastada.
CLOSE @crs
DEALLOCATE @crs
Ning koodilõik tervikuna
DECLARE @crs CURSOR
SET @CRS = CURSOR FOR
SELECT eesnimi FROM lapsed
OPEN @crs
FETCH NEXT FROM @crs
CLOSE @crs
DEALLOCATE @crs
Väljastatud tulemuseks praegusel juhul lihtsalt tabeli esimene nimi.
eesnimi
Juku
Tahtes läbi käia kogu kursoriga ligipääsetava andmeploki, tuleb kontrollida, kas on veel
võimalik edasi liikuda. Seda näitab süsteemne muutuja nimega @@FETCH_STATUS.
DECLARE @crs CURSOR
DECLARE @eesnimi VARCHAR(20)
SET @CRS = CURSOR FOR
SELECT eesnimi FROM lapsed
OPEN @crs
FETCH NEXT FROM @crs INTO @eesnimi
WHILE @@FETCH_STATUS<>-1 BEGIN
PRINT @eesnimi
FETCH NEXT FROM @crs INTO @eesnimi
END
CLOSE @crs
DEALLOCATE @crs
Juku
Kati
Mati
Ats
Siiri
Siim
Mari
Kui eraldi lubada, et kursor on keritav (SCROLL), siis saab päringu vastuste hulgas ka
tagurpidi, algusesse, lõppu või soovitud järjekorranumbriga reale hüpata.
Ülesandeid
* Loo tabel myyk, kus toimingutena kirjas aeg (automaatselt sisestatav), myyjaID,
tooteID ning kogus
* Trüki kursori abil välja laused kujul „Kell * müüja nr * müüs toodet * * tükki”
* Loo päringu jaoks ajutine tabel, kuhu soovitud toote kohta kirjutatakse kellaajad ning
kogus, mitu selle id-ga toodet selleks ajaks müüdud oli. Väljasta tulemused, kustuta
tabel.
* Võrreldes eelmisega lisa tulp, kus on loetelu müüjatest, kes selleks ajaks olid vastavat
toodet müünud.
Schema
SQL Server 2005 on iga objekti poole võimalik pöörduda objekti täispika nime kaudu.
Täispikk nimi koosneb neljast osast ning on kujul server.andmebaas.schema.nimi. Täispikast
nimest on võimalik ära jätta kõik peale objekti nime. Schemad e nimeruumid võimaldavad
andmebaasi objekte loogiliselt grupeerida ning selle abil on lihtsam hallata ligipääsuõiguseid.
Ligipääsu on seega võimalik seadistada serveri tasemel, andmebaasi tasemel, schema tasemel
ning loomulikult ka objekti tasemel.
Schema loomiseks on CREATE SCHEMA lause:
CREATE SCHEMA yldasjad
Tabelite lisamiseks konkreetsesse nimeruumi tuleb tabeli loomisel näidata ära, millisesse
nimeruumi tabel läheb. Vaikimisi on igas andmebaasis kasutusel vähemalt üks nimeruum dbo.
Lisame nimeruumi yldasjad tabeli proovikas:
CREATE TABLE yldasjad.proovikas
(
ID INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
MingiInf VARCHAR(100) NOT NULL
)
Igale kasutajale on võimalik määrata default schema:
CREATE USER [tegijad]
FOR LOGIN [BUILTIN\Users]
WITH DEFAULT_SCHEMA=[yldasjad]
Kui kasutaja ei täpsusta objekti loomisel/kasutamisel nimeruumi pöördutakse tema default
nimeruumi poole. Kui sealt objekti ei leita otsitakse objekti dbo nimeruumist.
Lisaks sellele on võimalik igale kasutajale või kasutajate grupile e. rollile määrata, milliseid
tegevusi ta nimeruumis võib teha:
Taas on võimalik kõike korraldada ka kasutades SQL Lauseid:
GRANT ALTER ON SCHEMA::[yldasjad] TO [public]
GRANT SELECT ON SCHEMA::[yldasjad] TO [public]
DENY TAKE OWNERSHIP ON SCHEMA::[yldasjad] TO [public]
Common Table Expression
Common Table Expression CTE on väga sarnane ajutisele vaatele, mida saab kasutada päringu
FROM lauseosas.
Üldine süntaks näeb välja järgmine:
WITH ()
AS
(
)
SELECT * FROM
CTE abil on võimalik vältida ajutiste tabelite ning alampäringute kasutamist. See omakorda
muudab päringu ülevaatlikumaks ning lihtsamaks.
Näiteks soovime tegelda ainult Tallinnas (kood 1) sündinud lastega. Sellisel juhul saame
endale kirjeldada CTE, milles sisalduvad vaid Tallinna lapsed ning kasutada seda oma
päringus nagu iga teist tabelit või vaadet:
WITH TallinnaLapsed
AS
(
SELECT *
FROM dbo.Laps_tbl
WHERE SynniLinn = 1
)
SELECT *
FROM TallinnaLapsed
ORDER BY Nimi
CTE tõeline jõud tuleb aga ilmsiks kui läbi rekursiivsete päringute. Rekursiivsed SQL
päringud olid ka põhiline idee CTE loomise taga.
Rekursiivse päringu loomiseks tuleb CTE tekitada UNION päringu abil, milles on kaks liiget
ankur (alguspunkt päringule) ning rekursiivne liig (iseendale viitav päring).
Et seda katsetada teeme mõned täiendused oma Laste tabelisse ning lisame sinna uue välja
Rühmajuht ning määrame igale lapsele sobiva juhi:
-- Lisame tabelisse uue välja
ALTER TABLE dbo.Laps_tbl
ADD Ryhmajuht INT NULL REFERENCES dbo.Laps_tbl (LapsID)
GO
-- paneme rühmajuhtideks igas linnas esimese kõige vanemate linnakodanike
hulgast
WITH VanimSynniA AS
( -- Iga linna varaseim sünniaasta
SELECT SynniLinn, MIN(SYNNIAASTA) AS Aasta
FROM dbo.Laps_tbl AS L1
GROUP BY SynniLinn
),
Pealikud AS
( -- esimene väikseima sünniaastaga laps
SELECT L2.SynniLinn, MIN(L2.LapsID) AS LapsID
FROM dbo.Laps_tbl AS L2
INNER JOIN VanimSynniA as VA ON L2.SynniLinn = VA.SynniLinn AND
L2.SynniAasta = VA.Aasta
GROUP BY L2.SynniLinn
)
UPDATE dbo.Laps_tbl
SET Ryhmajuht = P.LapsID
FROM dbo.Laps_tbl AS L3
INNER JOIN Pealikud AS P ON L3.SynniLinn = P.SynniLinn
WHERE L3.LapsID <> P.LapsID
GO
-- Määrame ka peetri kahe lapse rühmajuhiks
UPDATE dbo.Laps_tbl
SET RyhmaJuht = 12
WHERE LapsID IN (2,10)
-- lisame uue Lapse, kellest saab suurib pealik
DECLARE @UusLapsID INT
INSERT INTO dbo.Laps_tbl (Nimi, Synniaasta)
VALUES ('Roobert', 1988)
SET @UusLapsID = @@identity
UPDATE dbo.Laps_tbl
SET Ryhmajuht = @UusLapsID
WHERE Ryhmajuht IS NULL AND LapsID <> @UusLapsID
Kui kõik tehtud on meie tabel järgmine:
Alustame hästi lihtsast rekursiivsest päringust püüdes välja selgitada, kes alluvad Matile:
WITH Alluvad
AS
(
SELECT LapsID, Nimi, Ryhmajuht
FROM dbo.Laps_tbl
WHERE LapsID = 3
UNION ALL
SELECT L.LapsID, L.Nimi, L.Ryhmajuht
FROM dbo.Laps_tbl AS L
INNER JOIN Alluvad AS A ON L.RyhmaJuht = A.LapsID
)
SELECT A.LapsID, A.Nimi, L.Nimi AS Ryhmajuht
FROM Alluvad AS A
INNER JOIN dbo.Laps_tbl AS L ON A.RyhmaJuht = L.LapsID
Järgmisena alustame uuringut kõige suurematest pealikest e. kellel Ryhmajuht puudub ning
püüame välja selgitada mitmenda taseme alluvaga on tegemist:
WITH Alluvad
AS
(
SELECT LapsID, Nimi, Ryhmajuht, 0 AS Tase
FROM dbo.Laps_tbl
WHERE RyhmaJuht IS NULL
UNION ALL
SELECT L.LapsID, L.Nimi, L.Ryhmajuht, A.Tase + 1
FROM dbo.Laps_tbl AS L
INNER JOIN Alluvad AS A ON L.RyhmaJuht = A.LapsID
)
SELECT A.Tase, A.LapsID, A.Nimi, L.Nimi AS Ryhmajuht
FROM Alluvad AS A
LEFT OUTER JOIN dbo.Laps_tbl AS L ON A.RyhmaJuht = L.LapsID
ORDER BY A.Tase
Ülesandeid
* Koosta tabel isikud tulpadega id, eesnimi, isaID. Lisa mõned andmed. Juhul, kui isa on
tabelis olemas, siis isaID näitab isiku id-le, kes on vastava rea inimese isaks.
* Trüki välja kõikide isikute nimed, kellel tabelis isa puudub
* WITH-lausega loo vahetabel isikutest, kel tabelis isa olemas, all päringus näita nende
inimeste andmed välja.
* Lisaks eelmisega ühenda JOINi abil isiku nime juurde ka tema isa nimi
* Trüki välja etteantud inimesest alates pärinev sugupuu – alati kirjas, kes on kelle isa.
* Lisa juurde arv, mitmes põlv ta tabelis näidatud esiisast on
* Näita iga inimese kohta, mitu järeltulijat tal tabeli kaudu leitavas sugupuus on.
PIVOT JA UNPIVOT
Pivot tekitab tavalisest tabelist kahemõõtmelise risttabeli. Unpivot teeb täpselt vastupidist.
Püüame tekitada risttabeli, milles oleks ridades laste sünnilinn ning veergudes sünniaastad
ning andmetena laste keskmised pikkused:
SELECT *
FROM
( SELECT SynniAasta, SynniLinn, Pikkus
FROM dbo.Laps_tbl ) AS data
PIVOT (
AVG (Pikkus)
FOR Synniaasta IN ([1995], [1996], [1997])
) AS piv
Vastupidise teisenduse saame korraldada UNPIVOT käsuga. Selleks salvestan eelnevalt
eelmise pärinu tulemuse ajutisse tabelisse kasutades SELECT ... INTO #ristt …
konstruktsiooni, ehk siis
SELECT * INTO #ristt
FROM
( SELECT SynniAasta, SynniLinn, Pikkus
FROM dbo.Laps_tbl ) AS data
PIVOT (
AVG (Pikkus)
FOR Synniaasta IN ([1995], [1996], [1997])
) AS piv
Iseenesest õnnestus ka varem moodustada analoogseid konstruktsioone, kasutades CASE
valikuid SELECT loetelus. PIVOT on muutnud selle protsessi oluliselt lihtsamaks kuid
risttabelisse minevad väljanimed (veerud) tuleb endiselt sisse trükkida ning neid pole võimalik
tekitada dünaamiliselt.
Lahenduseks on sellele dünaamiline SQLi koostamine ning käivitamine EXEC abil.
Selleks tuleb luua skript, mis tekitab komadega eraldatud loetelu kõigist risttabelisse
minevatest väljadest ning kleepida see SQLis õige koha peale.
Kuna kasutame SQL 2005 siis teeksin selle tegevuse kasutades CTE abi:
DECLARE @aastad AS VARCHAR(MAX);
WITH AastaCTE
AS
(
SELECT DISTINCT SynniAasta AS Aasta FROM dbo.Laps_tbl
)
SELECT @aastad = ISNULL(@aastad + ',[', '[') +
CAST(Aasta AS CHAR(4)) + ']'
FROM AastaCTE
ORDER BY Aasta
DECLARE @sql NVARCHAR(max)
SET @sql = N'SELECT *
FROM ( SELECT Pikkus, SynniAasta, SynniLinn
FROM dbo.Laps_tbl ) AS Lapsed
PIVOT ( AVG(Pikkus) FOR SynniAasta IN(' + @aastad + N')) AS Piv'
EXEC (@sql)
Ülesandeid
* Näita laste tabelis iga nime ja etteantud kohta, mitu last on sel aastal sündinud (nt.
mitu Jukut aastal 1998)
* Kasuta SQL-lause genereerimist, et saaks loetellu kõik olemasolevad sünniaastad.
* Salvesta tulemus abitabelisse
* Eralda andmed taas UNPIVOT käsu abil.
APPLY
APPLY on uus operaator FROM lauseosas, mis võimaldab Teil teha väljakutseid tabelit
tagastavatele funktsioonile iga rea kohta peapäringus.
Näiteks huvitavad, meid iga linna kõige pikemad lapsed. Selleks loome funktsiooni, mis
loetleb meile ülesse konkreetse linna kõige pikemad lapsed:
CREATE FUNCTION dbo.fn_Pikimad (@LinnID int, @Top int)
RETURNS TABLE
AS
RETURN SELECT TOP (@Top) *
FROM dbo.Laps_tbl
WHERE SynniLinn = @LinnID
ORDER BY Pikkus DESC
Selleks, et vaadata seda infot konkreetsete linnade kaupa saame kirjutada päringu:
SELECT *
FROM dbo.Linn_tbl
CROSS APPLY dbo.fn_Pikimad(LinnID, 2)
Nummerdamised
ROW_NUMBER() – nummerdab kõik read tulemuses
RANK() – ütleb, mitmes on loetelus antud väärtus
DENSE_RANK() – analoogne RANK funktsiooniga, kuid ei jäta numbreid vahele
NTILE(n) – jagab tulemuse N grupiks
Kõiki nummerdamise funktsioone on võimalik kasutada ka üle gruppide jagades andmed
sobiva tunnuse alusel partitsioonideks:
Vaade
Sagedamini vajaminevad päringud on mõistlik vormistada vaatena. Nõnda optimeerib server
päringu ühe korra ära ning edaspidi saab tema tulemusi kiiremini vaadata. Samuti pole põhjust
keerukamaid ridu hakata taas uuesti kokku panema. Ka aitavad vaated olukordades, kus
päringud kipuvad muidu liialt keerukaks minema. Nii nagu alampäring tekitab vajadusel edasi
töödeldavad andmed, nii saab ka vaateid edasi kasutada sarnaselt, nagu oleksid need täiesti
tavalised tabelid.
Määratud õigustega andmebaasis saab vaadete järgi kergemini ja täpsemalt sättida, millised
kasutajad millistele andmetele ligi pääsevad. Muul juhul võib tükk tegemist olla, et kasutaja
näeks vaid üksikutest veergudest kindlatele tunnustele vastavaid ridu. Kui aga kasutajal
lubatakse vaid vaadetest andmeid näha ning vaate juures on sobivad piirangud õigesti seatud,
siis toimib kõik nõnda nagu liidese looja vajalikuks peab.
Vaated aitavad ka pidada andmete hoidmist ja kasutamist paindlikumana. Kui kasutaja või
kasutav programm küsib andmeid vaid vaadete kaudu, siis saab vajadusel tabelistruktuuri
enese julgesti ära muuta. Jääb vaid hoolitseda, et pärast muutust vaade jälle õigetest kohtadest
omale andmed kätte saaks.
Vaate kirjutamine näeb üldjuhul välja nagu täiesti tavalise SELECT-päringu kirjutamine.
Lihtsalt algusritta tuleb määrata vaate nimi. Pikemate laste leidmise päring siis järgmiselt:
CREATE VIEW pikadlapsed AS
SELECT eesnimi, pikkus FROM lapsed
WHERE pikkus > 160
Õnnestunud loomise peale teatatakse lihtsalt, et
Command(s) completed successfully.
Vaatest andmete küsimine näeb välja sarnaselt tabelile.
SELECT * FROM pikadlapsed
annab välja piisavalt pikkade laste loetelu nagu vaate loomisel sai ette nähtud.
Mati 164
Ats 165
Siim 163
Kui mõnel põhjusel soovitakse loodud vaatest loobuda või vaade uuega asendada, siis lahti
saab sellest käsuga DROP. Näiteks
DROP VIEW pikadlapsed
Ülesandeid
* Loo maakondade tabel (id, maakonnanimi)
* Loo autode tabel (id, mark, aasta, maakonna_id)
* Loo vaade, kus näha iga auto mark, väljalaskeaasta ning maakonna nimi.
* Loo vaade, kus näha registreeritud autode arv maakondade kaupa.
* Veendu vaadete toimimist andmete muutmisel.
* Loo vaade, mis kasutaks eelnevalt loodud vaadet ning näitaks vaid neid
maakondi, kus autosid rohkem kui 1.
Salvestatud protseduur
Levinud tegevuste tarbeks on võimalik andmebaasis salvestada protseduur. Nii saab
andmebaasis teha valmis keerukad toimingud, mida hiljem vajaduse korral vaid ühe käsuga
võib baasi haldusliidese või omakoostatud programmi kaudu välja kutsuda.
Loomine
Kõigepealt loomise näide:
CREATE PROCEDURE kysiLapsed(@algaasta decimal)
AS
SELECT eesnimi, synniaasta FROM lapsed
WHERE synniaasta>=@algaasta
Kui selline käsujada tipitakse SQL-serveri haldusliidesesse või sisestatakse muul moel
käsujadana, siis salvestatakse andmebaasi protseduur nimega kysiLapsed.
Tulemusena tekib baasi haldusliidesesse alajaotisesse Programmability -> Stored Procedures
vastav salvestatud protseduur, mille kohta saab soovi korral ka uurida, millist tüüpi andmeid ta
ette tahab ning millisel kujul tulemuse väljastab.
Protseduur eeldab, et baasis leidub tabel "lapsed", millel tulpadeks on vähemasti eesnimi ja
synniaasta. Protseduur väljastab laste andmed, kes on sündinud etteantud aastal või hiljem.
Halduskeskkonnas sobib käivitamiseks käsklus kujul
EXEC kysiLapsed 1997
Väljundiks loetelu nagu soovitud:
Juku 1997
Kati 1997
Siim 1997
Ülesandeid
* Loo salvestatud protseduur näitamaks, mitu autot on registris varasemad kui etteantud
väljalaskeaasta.
* Loo salvestatud protseduur auto lisamiseks registrisse. Parameetriteks
mark, väljalaskeaasta ja maakonna id.
* Võrreldes eelmisega sisestatakse maakonna nimi.
* Sobiva maakonnanime puudumisel antakse veateade.
* Väljalaskeaasta määramata jätmisel pannakse selleks käesolev aasta.
* Loo salvestatud protseduur, mis koostaks ja väljastaks tabeli, kus ühes
tulbas on maakondade nimed ning kõrval teises tulbas komadega eraldatult
sinna maakonda registreeritud erinevate sõidukite margid.
Transaktsioonid
Transaktsioonid on tegevused, kus kõik tegevused kas õnnestuvad edukalt või ei õnnestu
üldse. SQL Server tunnistab kahte tüüpi transaktsioone:
* Automaatsed transaktsioonid – Server tekitab need ise kõigi muutmistegevustega st
UPDATE, INSERT ja DELETE lausetega. Sisuliselt tähendab see seda, et kui nt
üritate muuta 10 kirje väärtust ja ühe kirje väärtuse muutmine ei õnnestu, siis ei
muudeta neist ühtegi.
* Käsitsi tehtud transaktsioonid – Programmeerija loodud tegevuste jadad, mis peavad
kõik õnnestuma.
Transaktsiooni tekitamiseks on käsk BEGIN TRAN. Selle järele tulevad kõik soovitud
tegevused. Kui transaktsioon lõpeb edukalt, siis saab selle lõpetada käsuga COMMIT TRAN.
Kui midagi läheb valesti, saab selle tagasi kerida käsuga ROLLBACK TRAN.
Vaatame näiteks, kuidas võiks luua protseduuri pangaülekandeks. Kuna SQL Server 2005
tunnistab uut TRY CATCH konstruktsiooni vigade haldamiseks, siis vaatleme seda näidet nii
SQL Server 2000 kui ka SQL Server 2005 baasil.
SQL Server 2000
SQL Server 2005
CREATE PROC ylekanne
@kellelt int,
@kellele int,
@summa money
AS
BEGIN TRAN
UPDATE konto
SET jaak = jaak - @summa
WHERE omanik = @kellelt
IF @@error <> 0 BEGIN
ROLLBACK TRAN
RETURN
END
UPDATE konto
SET jaak = jaak + @summa
WHERE omanik = @kellele
IF @@error <> 0 BEGIN
ROLLBACK TRAN
RETURN
END
COMMIT TRAN
GO
CREATE PROC ylekanne2
@kellelt int,
@kellele int,
@summa money
AS
BEGIN TRAN
BEGIN TRY
UPDATE konto
SET jaak = jaak - @summa
WHERE omanik = @kellelt
UPDATE konto
SET jaak = jaak + @summa
WHERE omanik = @kellele
END TRY
BEGIN CATCH
IF @@trancount > 0
ROLLBACK TRAN
END CATCH
IF @@TRANCOUNT > 0
COMMIT TRANSACTION;
GO
Samast raha ülekandmisest nüüd pikem näide. Pigem jäägu ülekanne teostamata, kui et
toimingu sees osa raha ära kaob või juurde tekib. Et kontodega toimetada, sai loodud
võimalikult lihtne tabel – vaid konto number ning seal olev saldo.
create table kontod(
id int identity not null primary key,
saldo money
)
Edasi luuakse salvestatud protseduur raha ülekandeks. Kust kontolt võtta, kuhu panna ning
kui suur on summa. Esimesed kaks arvu tähendavad siis vastavate kontode numbreid.
create procedure ylekanne
(@kust int, @kuhu int, @summa money)
as
Abimuutujas hoitakse meeles, kas kõik õnnestus hästi.
declare @korras as int
Esialgu probleeme pole, nii et @korras saab väärtuseks 1.
set @korras=1
Kogu järgnev toiming pannakse transaktsiooni sisse. See tähendab, et sealsed muutused kas
toimivad tervikuna või jäävad sootuks ära.
begin transaction
Kõigepealt kontrollitakse, kas esimeselt kontolt on võimalik vastav summa maha võtta. Kui
saab, siis võetakse, muul juhul väljastatakse veateade ja muutujasse @korras antakse teada, et
kord läks kaduma.
if (select saldo from kontod where id=@kust)>=@summa begin
update kontod set saldo=saldo-@summa where id=@kust
end else begin
set @korras=0
raiserror('raha otsas', 1, 1)
end
Järgmise sammuga kontrollitakse, et ka see konto ikka olemas on, kuhu raha kanda soovitakse.
Hariliku UPDATE-lause puhul ei antaks isegi veateadet juhul, kui saajakontot olemas poleks.
Siinse kontrolliga aga tehakse olemasolu kindlaks ja vaid sel juhul suurendatakse sealset
summat. Muul juhul jäetakse jälle meelde, et asjad pole korras.
if exists(select saldo from kontod where id=@kuhu) begin
update kontod set saldo=saldo+@summa where id=@kuhu
end else begin
set @korras=0
end
Edasi jääb üle vaid muutuja järgi otsustada, kas toiming kinnitada või tagasi lükata.
if @korras=1
commit transaction
else begin
rollback transaction
print 'probleem'
end
Ja et protseduuri käivitamise tulemusena oleks ka kontode operatsioonijärgset seisu näha,
selleks lõppu üks SELECT-lause.
select * from kontod where id in (@kust, @kuhu)
Ning kood tervikuna.
create procedure ylekanne
(@kust int, @kuhu int, @summa money)
as
declare @korras as int
set @korras=1
begin transaction
if (select saldo from kontod where id=@kust)>=@summa begin
update kontod set saldo=saldo-@summa where id=@kust
end else begin
set @korras=0
raiserror('raha otsas', 1, 1)
end
if exists(select saldo from kontod where id=@kuhu) begin
update kontod set saldo=saldo+@summa where id=@kuhu
end else begin
set @korras=0
end
if @korras=1
commit transaction
else begin
rollback transaction
print 'probleem'
end
select * from kontod where id in (@kust, @kuhu)
Tahtes loodud protseduur käivitada, tuleb siis ette anda andjakonto, saajakonto ja ülekantav
summa. Ning juhul, kui ülekanne on võimalik, see ka tehakse.
EXEC ylekanne 1, 4, 100
Ülesandeid
* Loo salvestatud protseduur sõiduki ühest maakonnast teise ümber registreerimiseks.
Parameetriteks sõiduki id ning maakonna id. Kõigepealt võetakse sõiduk algsest maakonnast
maha. Kui edasi selgub, et uuele id-le ei vasta maakonda, kuhu registreerida, siis taastatakse
algseisund ROLLBACKi abil. Kontrolli toimimist.
XML andmete kasutamine
XML andmete tugi on olnud juba alates SQL 2000st. SQL 2005 on seda tuge oluliselt
parandatud ning lisandunud on isegi spetsiaalne andmetüüp. XML andmeväli erineb tavalisest
tekstist selle poolest, et seal hoitakse andmeid programselt lihtsalt kasutatava objektina ning
sellelt väljalt on lihtsam teha XML päringuid ning sinna saab tekitada ka XML indekseid.
Selle välja juures tuleb aga meeles pidada, et sinna salvestuvad küll kõik andmed, kuid kuna
andmeid hoitakse puu kujul, kaob esialgsetest andmetest ära tühiruum. Seega kui Teile on
oluline, et andmed säiliksid täielikult sellisel kujul nagu need sisestati siis tuleb xml
andmetüübi asemel kasutada tavalist tekstivälja.
FOR XML
Kõige lihtsam moodus XML andmete genereerimiseks on lisada SELECT lause lõppu FOR
XML märksõna ning andmed teisenevad XML kujule. Teisenduse protsessi kontrollimiseks on
kasutada neli erinevat metoodikat.
Kõige lihtsam neist on RAW. RAW tekitab kõigist tulemuses olevates ridadest XML
elemendid nimega „row“, kus kõik SELECT loetelus olevad väljad on atribuutideks. Seega
peavad kõikide väljade nimed olema erinevad!
SELECT nimi, pikkus
FROM dbo.Laps_tbl
FOR XML RAW
Nagu näeme ei ole loodud XMLil juurelement e. tegemist ei ole well formed XMLiga. Selle
vea saame lihtsalt parandada lisades juurde ROOT märksõna:
SELECT nimi, pikkus
FROM dbo.Laps_tbl
FOR XML RAW, ROOT
Loomulikult võime nii juurkale kui ka elementidele anda ka mingid teised nimed, näidates
uued nime vastava märksõna järel sulgudes:
SELECT nimi, pikkus
FROM dbo.Laps_tbl
FOR XML RAW('laps'), ROOT('lapsed')
Saame teha ka nii, et atribuutide asemel genereeruvad elemendid. Selleks lisame juurde
ELEMENTS märksõna:
SELECT nimi, pikkus
FROM dbo.Laps_tbl
FOR XML RAW('laps'), ROOT('lapsed'), ELEMENTS
Juhan
185
Kati
158
Mati
171
Siiri
158
Siim
163
Ats
165
Järgmine võimalus on genereerida XMLi automaatselt:
SELECT nimi, pikkus
FROM dbo.Laps_tbl AS Laps
FOR XML AUTO, ROOT('lapsed')
Nagu näeme valitakse sellisel juhul elementide nimed vastavalt kasutatud tabelitele.
Automaatse genereerimise juures on veel tore see, et see automaatika oskab järgida JOIN
lauseid ning tekitab hierarhilise XMLi.
SELECT Linn.Nimi, Laps.Nimi, Laps.Pikkus
FROM dbo.Linn_tbl as Linn
INNER JOIN dbo.Laps_tbl AS Laps ON Linn.LinnID = Laps.Synnilinn
ORDER BY Linn.Nimi
FOR XML AUTO, ROOT('lapsed')
Eelpool toodud kahest variandist keerukamate XML struktuuride tekitamiseks on EXPLICIT
ning PATH märksõnad.
Kui soovite, et päringu või alampäringu tulemuks oleks XML andmetüübis tuleb lisada FOR
XML lauseosasse TYPE märksõna. Sellisel juhul on võimalik genereerida XMLi XMLi sisse.
SELECT Linn.Nimi,
( SELECT Laps.Nimi, Laps.Pikkus
FROM dbo.Laps_tbl AS Laps
WHERE Laps.SynniLInn = Linn.LinnID
FOR XML AUTO, TYPE)
FROM dbo.Linn_tbl as Linn
FOR XML AUTO
SELECT loetelus kasutatav alampäring tagastab mitu kirjet ning tavaolukorras ei oleks selline
asi lubatud, kuid teisendades andmed XML kujule on kõik lubatud ;)
Lisaks sellele võime väliselt päringult FOR XML lauseosa ära jätta ning tekib tabel e.
recordset, kus on kombineeritud relatsioonilised andmed ja XML andmed:
OPENXML
FOR XML võimaldab meil XMLi genereerida edasi saatmiseks. OPENXML on vastupidine
protsess, mis võimaldab meil saabunud XMLi töödelda.
XMLi töötlemine OPENXML käib viie sammulise protsessina:
1. Saabub XML dokument
2. sp_xml_preparedocument salvestatud protseduuri abil tekitame mällu XML puu
3. kasutades OPENXMLi teisendame puust sobivad tükid relatsioonilisele e. SQL
Serverile kodusele kujule
4. Teeme andmetega vajalikud toimingud nt salvestame info tabelitesse
5. kasutades sp_xml_removedocument kustutame XMl puu ning vabastame sellega
seotud mälu
sp_xml_preparedocument kasutamiseks on meil vaja INT tüüpi muutujat, millesse salvestada
viide XML puule mälus. Saadud viidet vajavad nii OPENXML kui ka
sp_xml_removedocument. Seega peaks protsess nägema välja u. sarnane:.
DECLARE @doc xml
-- kusgilt saabub mingi XML dokument
DECLARE @hdoc INT
EXEC sp_xml_preparedocument @hdoc OUTPUT, @doc
-- teeme vajalikud tehingud
EXEC sp_xml_removedocument @hdoc
OPENXML süntaks on järgmine:
OPENXML(idoc, rowpattern [, flags]) WITH (SchemaDeclaration | TableName)
* rowpattern – Xpath päring, mis kirjeldab tagastatavad XML oksad
* idoc – viide mälus asuvale XML puule
* flags . kas tuua:
0. atribuudid (default)
1. atribuudid
2. elemendid
3. nii atribuudid kui elemendid
* SchemaDeclaration – tagastatava tabeli kirjeldus
* TableName – olemasolev tabel, mille Schemat kasutada
DECLARE @doc xml
SET @doc =
'
'
DECLARE @hdoc INT
EXEC sp_xml_preparedocument @hdoc OUTPUT, @doc
SELECT *
FROM OPENXML(@hdoc, '/lapsed/Linn/Laps')
WITH (Nimi VARCHAR(40), Pikkus SMALLINT)
EXEC sp_xml_removedocument @hdoc
Kui soovime jätta alles ka linnade nimed tuleks teha väike parandus schemasse:
DECLARE @doc xml
SET @doc =
'
'
DECLARE @hdoc INT
EXEC sp_xml_preparedocument @hdoc OUTPUT, @doc
SELECT *
FROM OPENXML(@hdoc, '/lapsed/Linn/Laps')
WITH ( Nimi VARCHAR(40)
, Pikkus SMALLINT
, Linn VARCHAR(20) '../@Nimi')
EXEC sp_xml_removedocument @hdoc
PS! Olge ettevaatlikud. XML on tõusutundlik e. @nimi ei ole sama, mis @Nimi!
Saadud tabelitega võime teha, mida iganes soovime. Näiteks leiame linna nimede asemele
nende koodid:
SELECT L.LinnID, uus.Nimi, uus.Pikkus
FROM OPENXML(@hdoc, '/lapsed/Linn/Laps')
WITH ( Nimi VARCHAR(40)
, Pikkus SMALLINT
, Linn VARCHAR(20) '../@Nimi') AS uus
INNER JOIN dbo.Linn_tbl as L on uus.Linn = L.Nimi
Või lisame saadud andmed Laps_tbl tabelisse:
Ülesandeid
* Väljasta kõikide tabelis olevate sõidukite andmed XMLina
* Katseta XML RAW ja AUTO võimalusi. Samuti juurelemendi määramist ning
ELEMENTS täiendit
* Ühenda autode ja maakondade tabel ning väljasta sealsed tulemused XMLina
maakondade kaupa
* Paiguta alampäringuga iga maakonna sisse selles maakonnas sõitvate kõige vanemate
sõidukite margid XMLina
* Sisendiks autode loetelu XMLina. Väljasta OPENXMLi abil andmed tabelina.
* Sisendiks XML, kus autod maakondade kaupa. Väljundiks autod koos maakondade
nimedega.
* Lisa etteantud XMList autod olemasolevasse tabelisse.
Varukoopia
Masinatega juhtub ikka õnnetusi. Mõni läheb rikki, teine varastatakse ära. Ja vahel piisab ainult
vigaselt kirjutatud DELETE või UPDATE lausest, et paljust tähtsast ilma jääda. Kui aga
andmetest kindlas kohas koopia(d) olemas, saab töö pärast taastamistoimetusi jätkuda.
Põhjalikum koopiate loomine ja haldamine on terve teadus: millal teha täielik koopia ning
millal salvestada vaid erinevused. Ning kuidas taastada olukord kindlal kellaajal ja kuidas
hilisematest muutustest vaid õiged välja valida. Kogu see uurimine jääb siinsest kirjutisest
välja. Aga lihtsalt teadmiseks kõrvale, et vajaduse korral saab varukoopiamajanduse küllalt
peenelt paika sättida. Siin ainult paar lihtsat käsku, millega saab baasist tervikuna koopia teha
ning siis jälle tervikuna taastada, sest ka ilma seadistusteta tehtud varukoopia on tunduvalt
parem kui andmete täielik häving.
Loomine
SQL-serveril on sobiv käsk olemas. Tuleb vaid määrata, millise nimega baas kuhu faili
salvestatakse. Ja edasi võib juba edasi failiga toimetada.
BACKUP DATABASE baas1
TO DISK='d:\kodu\baas1.bak'
Taastamine
Kui varukoopiafail ilusti hoitud, siis endise seisu saab tagasi, lugedes varukoopia algse baasi
kohale. Salvestushetkele järgnenud muutused lähevad küll kaduma, kuid vähemalt tallel
hoitud põhiosa on võimalik ilusti tagasi saada.
RESTORE DATABASE baas1
FROM DISK='d:\kodu\baas1.bak'
Binaarandmete asemel on võimalik baasi ja tabelite koopiaid ka SQL-lausetena luua ja hoida.
Selleks sobib näiteks SQL Server Management Studio pakutav võimalus vastavad skriptid
genereerida. Andmete taastamiseks tuleb nad lihtsalt käima panna ja jällegi võib taastatud
andmetest rahulolu tunda.
Kui soovitakse varukoopia põhjal täiesti uus baas importida, sel juhul käib toiming kahe
sammuga. Kõigepealt faililoetelu loomine
RESTORE FILELISTONLY
FROM DISK='c:\jaagup\esimene.bak'
ning edasi luuakse baas ise, loetakse andmed sisse ning vanale baasinimele (siin juhul baas1)
määratakse andmete hoidmise asukohaks uued failid.
RESTORE DATABASE proov2
FROM DISK = 'c:\jaagup\esimene.bak'
WITH MOVE 'baas1' TO 'c:\jaagup\proov2db.mdf',
MOVE 'baas1_log' TO 'c:\jaagup\proov2db.ldf'
Nõnda võib loodetavasti julgesti oma andmetega toimetada nii, et ka suuremate tehniliste
viperuste korral jäävad tähtsamad andmed alles ja kasutatavaks.
Ülesandeid
* Loo andmebaasist varukoopia.
* Vaheta koopia naabriga.
* Taasta naabri baas oma arvutisse. Kontrolli päringute ja käskluste
toimimist.
Kokkuvõte
Nagu näha, võib SQL-lausete abil suhteliselt lühidalt küllalt palju tarvilikku kirja panna ning
alampäringute kaudu saab õige mitmekesiseid lauseid kokku. Enne, kui asuda usinasti omi
andmetöötlusprogramme kirjutama, tasub uurida, kas sama tulemust mitte SQLi kaudu
tunduvalt lihtsamalt kätte ei saa. See päringute koostamine on mõnes mõttes nagu
matemaatiliste avaldiste lihtsustamine või malemäng, kus reeglid teada. Kuid hea
lõpptulemuseni jõudmiseks tuleb vahel osata mitu käiku ette näha. Ning veel raskem on
kindlaks teha, et vastavat ülesannet polegi võimalik olemasolevate vahenditega lahendada.
Aga – kes püüab kõigest väest, saab üle igast mäest.
Andmetele ligipääs
Andmehoidla on meetod infoühikute hoidmiseks, mis kokku moodustavad informatsiooni.
Üksikud infoühikud on seejuures suhteliselt kasutud, nad omandavad väärtuse kui nad panna
konteksti teiste infoühikute juurde.
Andmete hoidmiseks on viis meetodit:
Meetod
Kirjeldus
Näide
Struktureerimata
Andmetel ei ole mingit
loogilist järjekorda
Lihtsad märkmed ja
kommentaarid
Struktureeritud, kuid mitte
hierarhiline
Andmed on grupeeritud
üksusteks, kuid üksused on
organiseeritud vaid järjekorra
alusel
Exceli tabelid, CSV failid jne
Hierarhiline
Andmed on organiseeritud
puu kujule.
XML dokumendid
Relatsiooniline andmebaas
Andmed on organiseeritud
tabelitesse, kus veergudes on
konkreetset tüüpi andmed ja
iga rida sisaldab kirjet.
Tabelid on seotavad teiste
tabelitega, kus leidub
sarnaste andmetega veerge.
SQL Serveri andmebaasid,
Accessi andmebaasid, Oracle
andmebaasid
Objektorienteeritud
andmebaas
Andmed on organiseeritud
objektidena
ADO.NET võimaldab andmeid hoida kõigil kirjeldatud viisidel ning ühendada kõikvõimalike
väliste andmehoidlate/andmebaaside külge.
Andmete kasutamise keskkond võib olla nii ühendatud kui ka ühenduseta. ADO.NET
võimaldab kasutada neid mõlemaid.
Andmebaasi kasutamine käib enamasti viiesammulise protsessina:
1. Tuleb luua ühendus kasutades ühendusteksti (ConnectionString)
2. Tuleb luua objekt, mis sisaldab andmebaasi käske
3. Avada ühendus
4. Käivitada käsk
5. Sulgeda ühendus
Üsna pikalt oli ainukeseks andmete kasutamise võimaluseks ühendatud keskkond. Ühendatud
keskkond on keskkond, kus kasutaja või programm on pidevalt ühendatud andmeallika külge.
Ühendatud keskkonnal on mitmeid eeliseid:
* Turvalist keskkonda on lihtsam hallata
* Konkurentsi on lihtsam kontrollida
* Andmed on värsked
Samas on ühendatud keskkonnal ka mõned puudused:
* On vaja pidevat võrguühendust
* Laiendatavus on raskendatud
Ühendatud keskkonna kasutamiseks on
XxxDataReader klass. Programmi loogika oleks
siis järgmine
1. Ava ühendus
2. Käivita käsk
3. Töötle lugeja poolt tagastatavad kirjed
4. Sulge lugeja
5. Sulge ühendus
Koos Interneti arenguga on hakanud tavaliseks muutuma ühenduseta keskkonnad.
Ühenduseta keskkond on keskkond, kus kasutaja või programm ei ole pidevalt ühendatud
andmeallikaga. Kõige tüüpilisemaks näiteks on mobiilsete seadmete (nt sülearvuti) kasutajad,
kes võtavad mingi hulga andmeid endaga kaasa ning kui taas levisse satuvad, siis
sünkroniseerivad andmeid.
Ühenduseta keskkonnal on mitmeid eelised:
* on võimalik töötada andmetega siis, kui see kõige paremini sobib ning on võimalik
ühendada andmeallika külge siis, kui see on võimalik.
* Sel ajal kui ei kasuta ise andmeallikat, võivad seda teha teised kasutajad
* Oluliselt parem laiendatavus ja tööjõudlus
Samas on ka mõned puudused:
* Andmed ei ole alati kõige värskemad
* Muudatused võivad tekitada konflikte ja need tuleb lahendada
Ühenduseta keskkonna kasutamiseks on
XxxDataAdapeter klass ning DataSet’id.
Programmi loogika oleks järgmine:
1. Ava ühendus
2. Täida DataSet
3. Sulge ühendus
4. Töötle andmeid DataSet’is
5. Ava ühendus
6. Värskenda andmeid
7. Sulge ühendus
ADO.NET on hulk klasse, mis on mõeldud andmetega töötamiseks. Andmetega seotud
nimeruumid on:
System.Data
ADO.NET põhilised osad, mis võimaldavad kasutada ühenduseta
keskkonda.
System.Data.Common
Lisavahendid ja näod, mis on .NET raamistikus realiseeritud
System.Data.SqlClient
SQL Serveri andmeallikas
System.Data.OleDb
OLE DB andmeallikas
System.Data.SqlTypes
Klassid ja struktuurid SQL andmetüüpide kirjeldamiseks
System.Xml
Klassid ja näod XML kujul andmete töötlemiseks
Andmeallika külge ühendumine
Ennem, kui saate hakata tegelema andmetega, peate fikseerima andmeallika. Andmeallikad on
üks ADO.NET põhikomponentidest, mis võimaldavad programmil suhelda andmeid hoidvate
süsteemidega. .NET raamistikuga on kaasas SQL Server .NET Data Provider (Optimeeritud
SQL Serverite kasutamiseks alates SQL Server 7.0st) ja OLE DB .NET Data Provider
(Võimaldab kasutada kõiki andmeallikaid). Lisaks on saadaval ka spetsiaalseid allikaid ODBC
ja Oracle andmete kasutamiseks.
Iga andmeallikas pakub järgmiseid klasse:
* XxxConnection nt SqlConnection SQL Serveriga suhtlemiseks. Suhtluse
kontrollimiseks on sealjuures veel XxxTransaction, XxxExeption ja XxxError klassid.
* XxxCommand nt SqlCommand käivitab andmeallikas käsu. Käsu parameetreid saab
kontrollida läbi XxxParameter klassi.
* XxxDataReader nt SqlDataReader avab ainult loetava andmevoo, tavaliselt mingi
SELECT lause tulemus
* XxxDataAdapter nt SqlDataAdapter kasutab SqlCommand objekte DataSeti
täitmiseks ning haldab ka DataSetis toimuvaid muudatusi
* XxxPermission – Õigustega seonduv
Andmeallika leidmiseks tuleb ühenduse loomisel ära määrata ühendustekst. Ainukene erinevus
OLE DB ja Sql andmeallika vahel seisneb selles, et OLE DB puhul tuleb täpsustada, mis sorti
andmeallikaga on tegemist. Järgnevalt mõned näited:
Andmebaas
Ühendustekst
SQL Server 6.5
Provider=SQLOLEDB;Data Source=London;Initial Catalog=pubs;User
ID=sa;Password=2389;
Oracle Server
Provider=MSDAORA;Data Source=ORACLE8I7;User ID=OLEDB;Password=OLEDB;
Accessi
andmebaas
Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:\bin\LocalAccess40.mdb;
Ühendusteksti luues on vaja määrata:
* Provider (kasutades OLE DB andmeallikat) – mis tüüpi andmebaasiga on tegemist
* Data Source – Millise andmebaasiserveri poole pöördutakse
* Initial Catalog – Millist andmebaasi serverist soovitakse
* User ID (uid) ja Password (pwd) – millise kasutaja õigustes andmebaasiga suheldakse
ning tema parool.
* Integrated Security - kui kasutajanime ja parooli ei taha sisse kirjutada, võib kasutada
Windowsi autentimist. Kuid see vajab pisut ettevalmistusi ka Windowsi seadistuse
poole pealt.
* Presist Security – Kui on False, siis tundliku infot nagu nt kasutajanimi avatud
ühenduses ei vahetata.
Lisaks neile parameetritele võib lisada veel määranguid selle kohta, kui suurte tükkidena
andmeid vahetatakse, kaua see ühendus võib avatuks jääda jne.
Hostingu keskkonnas tuleb kasutada ühendusteksti järgmisel kujul:
packet size=4096;uid=KASUTAJANIMI;pwd=PAROOL;data source=dotnet;persist security
info=False;initial catalog=KASUTAJANIMI
Ühendusteksti saab määrata XxxConnenction objekti luues (konstruktoris)
SqlConnection conn = new SqlConnection(”uid=KASUTAJANIMI;pwd=PAROOL;data
source=dotnet;persist security info=False;initial catalog=KASUTAJANIMI”);
või tagantjärele, kasutades ConnectionString omadust. Tagantjärgi saab ConnectionString
omadust muuta võid juhul, kui ühendus on suletud.
SqlConnection conn = new SqlConnection();
conn.ConnectionString = ”uid=KASUTAJANIMI;pwd=PAROOL;data
source=dotnet;persist security info=False;initial catalog=KASUTAJANIMI”;
Kui ühendus on algväärtustatud, saate Open ja Close meetodite abil seda ühendust avada ja
kinni panna.
Ühenduste sulgemine on oluline, kuna neid ei sulgeta automaatselt kui ühendust hoidev
muutuja väljub vaateväljast! Seega peaks andmetega töötamine käima järgnevalt:
// Algväärtustame ühenduse
SqlConnection conn = new SqlConnection();
conn.ConnectionString = ”uid=KASUTAJANIMI;pwd=PAROOL;data
source=dotnet;persist security info=False;initial catalog=KASUTAJANIMI”;
// avame ühenduse
conn.Open();
// mingid tegevused selles ühenduses
// sulgeme ühenduse e. peatame andmevahetuse selles ühenduses
// kui ühendus pärines basseinist siis pannakse ühendus basseini tagasi
conn.Close();
// Eemaldame ühenduse basseinist
conn.Dispose();
// hävitame ühenduse objekti ja lubame prügiautol mälu vabastada
conn = null
Kui soovite reageerida ühenduse staatuse muutmisele, võite kinni püüda StateChange
sündmuse. Staatuse kontrollimiseks on ühenduse küljes State omadus.
Kuna ühenduse loomisel või andmete toomisel võib ette tulla mitmeid probleeme, tuleks kogu
protseduuri teha kindlasti Try...Catch konstruktsiooni sees ning kinni tuleks püüda kõik
probleemid, millele teie programm oskab viisakad lahendused pakkuda.
Lisaks probleemidele, mida tuvastab ADO.NET, võivad probleeme tekitada ka serverisse
saadetud käsud. Sqli serveri poolt tekitatud veateateid saab hallata püüdes kinni
SqlException’i. SqlExceptionist saab kätte SqlError klassi, mille omadustest on võimalik välja
lugeda, millega tegemist:
Class
Vea kriitilisus
LineNumber
Rida, mis põhjustas vea
Message
Vea tekst
Number
Vea numbriline kood
Vigade kirjeldusi ja võimalikke lahendusi on võimalik otsida SQL Serveri spikrist
BooksOnline.
Töötamine andmebaasiga ühendatud keskkonnas
Kuigi ühenduseta keskkonnal on mitmeid häid omadusi, on siiski olukordi, kus on mõttekam
jätta vahele DataSet’idest tulenev keerukus ja käivitada käsud otse andmebaasis.
Järgnevalt mõned olukorrad, kus on enamasti otstarbekam kasutada ühendatud keskkonda:
* Päringute tegemine andmetele, mis on rakenduses ainult loetavad. Sh otsingud
andmebaasist.
* ASP.NET rakenduste disain, kus andmeid edastatakse vaid üks kord nt otsingu
tulemuse näitamine
* Päringute käivitamine, mis tagastavad vaid ühe väärtuse või ei tagasta üldse midagi.
* Andmebaasi struktuuri muutmine
Kui soovite käivitada käske, mis ei tagasta andmetabelit nt tabelite loomine või
muutmistegevused, siis ei ole võimalik DataSet’i kasutada ning tuleb kasutada ühendatud
keskkonnas käivitatavaid käske.
Klass
Kirjeldus
XxxConnection
Tekitab ühenduse soovitud andmeallikaga. Nt SqlConnection loob
ühenduse Microsoft SQL Serveriga
XxxCommand
Käivitab käsu olemasolevas ühenduses. Nt SqlCommand käivitab
käsu Microsoft SQL Serveris
XxxDataReader
Ainult edasiloetav andmejada andmeallikast. Nt SqlDataReader
klass loeb andmeid Microsoft SQL Serverist. Lugeja saab tekitada
käsu XxxCommand (SqlCommand) meetodiga ExecuteReader.
Enamasti on tegemist SELECT lausete tulemustega
XxxXMLReader
Pakub kiiret, ainult edasi lugevat ligipääsu XML andmetele
XxxCommand
Käsu objekt annab ühendatud keskkonnas otsese ligipääsu andmetele. Käsu objekti saab
kasutada järgmiste tegevuste tarbeks:
* SELECT lause käivitamiseks, kui tulemuseks on 1 väärtus
* SELECT lause käivitamiseks, mis tagastab rea andmeid
* DDL (Data Definition Language – Andmebaasi kirjeldamislaused) lausete käivitamine
nt tabelite loomine, muutmine, protseduuride loomine jne
* DCL (Data Control Language – Andmete ligipääsu kontrollimise laused) lausete
käivitamiseks nt andmete lugemise keelamine või lubamine
* XML formaadis andmete lugemine
Käsu objekti omadused sisaldavad käsu käivitamiseks kogu vajalikku informatsiooni:
* Connection – Viide ühendusele, kus käsk käivitatakse
* CommandType – Käsu tüüp: Text, StoredProcedure, TableDirect
* CommandText – Käsk ise st SQL lause või StoredProcedure nimi
* Parameters – Kollektsioon parameetritest. Vastavalt vajadusele võivad parameetrid
puududa ning neid võib olla ka mitmeid.
Kui käsu omadused on määratud, tuleb käsk käivitada, kutsudes välja mõne meetodi käsu
objekti küljest.
Meetod
Kirjeldus
ExecuteScalar
Käivitab käsu, mis tagastab ühe väärtuse
ExecuteReader
Käivitab käsu, mis tagastab mingi hulga ridasid
ExecuteNonQuery
Käivitab käsu, mis otseselt midagi ei tagasta. Nt tabelis
ridade kustutamine või muutmine. Tulemusena
tagastatakse ridade arv, mida käsk mõjutas
ExecuteXmlReader
(ainult SqlCommand’i puhul)
Käivitab käsu, mis tagastab XML andmed
Käsu objekti loomise võib korraldada nt järgmise koodireaga:
SqlCommand cmd = new SqlCommand();
Sellise tühja käsuga pole loomulikult palju peale hakata, seega järgnevalt tuleks väärtustada
kõik olulisemad omadused: käsu tüüp, käsk ja ühendus.
cmd.CommandType = CommandType.Text;
cmd.CommandText = "UPDATE Toode SET Hind = Hind * 1.1";
cmd.Connection = conn;
Käsu käivitamiseks tuleb välja kutsuda sobiv meetod. Kuna hetkel on tegemist
muutmislausega, mis tulemust ei tagasta, siis oleks kõige sobivamaks ExecuteNonQuery
meetod. Enne käsu käivitamist tuleb avada ka andmebaasi ühendus.
cmd.Open();
int read = cmd.ExecuteNonQuery();
cmd.Close();
Muutujasse read salvestatakse muudetud ridade arv ning seda saab kasutada edaspidises
koodis. Kui sellist informatsiooni vaja ei ole, siis võib selle muutuja omistamise ära jätta ning
käivitada käsu järgmiselt:
cmd.ExecuteNonQuery();
Parameetrite kasutamine
SQL laused ja protseduurid võivad kasutada nii sisend- kui ka väljundparameetreid. Nende
parameetrite kasutamiseks on käsu tüübile vastav XxxParameter klass. SqlCommand’i puhul
on selleks SqlParameter.
Enne käsu käivitamist tuleb omistada väärtused kõigile sisendparameetritele.
Peale käsu käivitamist õnnestub lugeda väärtusi väljundparameetritest.
Järgnevalt täiendame eelpool moodustatud käsku nii, et muudetud saaks vaid ühe toote hind.
Selleks tuleb esmalt teha muudatus käsus, kus ütleme, et soovime vaid muuta selle toote
hinda, mille määrame parameetriga @TooteKood.
cmd.CommandText =
"UPDATE Toode SET Hind = Hind * 1.1 WHERE ToodeID = @TooteKood";
Seejärel tuleb meil tekitada sobivate omadustega parameeter:
SqlParameter p = new SqlParameter("@TooteKood",SqlDbType.Int);
p.Direction = ParameterDirection.Input;
p.Value = 5;
Ning kleepida see parameeter käsu külge:
cmd.Parameters.Add(p);
Kui käsk vajab rohkem parameetreid, siis tuleb seda protseduuri korrata iga parameetriga.
Kui kõik parameetrid lisatud, siis võib käsu käivitada nii nagu varemgi.
Ridade lugemine väljundist (DataReader)
DataReader on kiire ainult edasi lugev tsükkel, mis loeb läbi andmeallikast tulevad read.
Kui käsu nt SqlCommand’i tulemuseks on tabel, siis kasutades SqlDataReader’it, saab
vaadata, mis neis ridades kirjas.
DataReaderi tekitamiseks on käsu küljes ExecuteReader meetod. Käsuks võib olla nii
SELECT lause kui ka StoredProtcedure.
Lisaks andmetele annab DataReader andmete kohta ka metainfot e. mis tüüpi andmetega on
tegemist ning mis on väljade nimed.
Sql lause tulemuseks olevate ridade läbi käimiseks on DataReader’il meetod Read. Read
meetod liigub tulemuses järgmisele reale. Kui Read meetod tagastab false, siis on kõik read
läbi vaadatud. Sel hetkel oleks ka sobiv kutsuda välja Close meetod, mis sulgeb DataReaderi
ja vabastab ühenduse.
Aktiivselt realt andmete lugemiseks on mitmeid võimalusi:
* Kasutada Item omadust. Item on kahtepidi indekseeritud massiiv e. te võite küsida
väärtusi nii välja nime järgi lugeja[”TooteID”] kui ka positsiooni järgi lugeja[3].
* Väärtusi saab lugeda ka GetDateTime, GetDouble, GetGuid, GetInt32 jne
meetoditega, mis tagastavad teile vastava välja väärtuse teisendatuna konkreetsesse
andmetüüpi.
* Viimase võimalusena on võimalik kasutada GetValues meetodit, mis tagastab
objektide massiivi kõigist rea väljadest.
Üks salapärane väärtus andmebaasis on määramata väärtus NULL. Selleks, et kindlaks teha,
kas mingil väljal on väärtus puudu, saate kasutada IsDbNull meetodit. Kui selle meetodi
tulemuseks on true, siis on väljal väärtus puudu.
Järgnevalt üks näide, kuidas lugeda andmebaasist 10ne kõige kallima toote andmed ning
trükkida need konsooli aknasse.
SqlCommand cmd = new SqlCommand();
cmd.CommandType = CommandType.Text;
cmd.CommandText = "SELECT TOP 10 ToodeID, Nimi, Hind " +
" FROM Toode ORDER BY Hind DESC";
cmd.Connection = conn;
conn.Open();
SqlDataReader rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
while (rdr.Read()) {
Console.WriteLine("{0}: {1} ({2})",
rdr.GetInt32(0),rdr.GetString(1),rdr.GetDecimal(2));
}
rdr.Close();
Transaktsioonid
Keerukate muutmistegevuste juures tuleb tihtipeale kasutada transaktsioone. Transaktsioon on
tegevuste jada, kus kõik tegevused kas lõpevad edukalt või neid ei sooritata üldse.
Üheks lihtsamaks näiteks on pangaülekanne. Pangaülekanne koosneb kahest tegevusest:
esmalt tuleb ühelt kontolt sobiv summa maha võtta ja seejärel tuleb teisele kontole sama
summa juurde liita. Need tegevused peavad õnnestuma mõlemad, muidu läheb kusagilt raha
kaduma või tekib lubamatult juurde.
Selliste tegevuste haldamiseks on 2 varianti. Esimene on kasutada SQL lauseid (vaata
eestpoolt), teine võimalus on korraldada transaktsioonid ADO.NET tasemel.
conn.Open();
SqlTransaction tran = conn.BeginTransaction();
SqlCommand cmd = new SqlCommand();
cmd.Connection = conn;
cmd.Transaction = tran;
cmd.CommandType = CommandType.Text;
try {
cmd.Command.Text =
” UPDATE konto SET jaak = jaak – 100 WHERE omanik = 1”
cmd.ExecuteNonQuery();
cmd.Command.Text =
” UPDATE konto SET jaak = jaak + 100 WHERE omanik = 2”
cmd.ExecuteNonQuery();
tran.Commit();
}
catch {
tran.Rollback();
}
finally {
conn.Close();
}
Töötamine ühenduseta keskkonnas (DataSets)
Ühenduseta keskkonnas on võimalik töötada ilma püsiva andmebaasi ühenduseta ning
kasutada paralleelselt mitmetest erinevatest andmebaasidest pärit andmeid.
DataSeti võib võtta kui arvuti mällu
salvestatud andmebaasi, mis töötab
analoogselt relatsioonilise andmebaasiga.
Samas tuleb DataSeti loomisel olla ülimalt
ettevaatlik, sest teie programme jooksutavatel
arvutitel on mälu oluliselt vähem kui
andmebaase hoidvatel serveritel kõvaketta
ruumi.
Seega ei maksa DataSettidesse koguda mitte tervet andmebaasi, vaid ainult kõige olulisemad
andmed, mida vajate oma programmis.
DataSeti tegemiseks tuleb kutsuda välja DataSet klassi konstruktor:
DataSet ds = new DataSet();
Tabeli lisamiseks DataSeti tuleb tekitada tabel ning lisada ta sobivasse DataSeti. Kuna ühes
DataSetis võib tabeleid olla mitu, on kasulik anda igale tabelile oma nimi.
DataTable toode = new DataTable(”toode”);
ds.Tables.Add(toode);
Loomulikult on tegemist veel abstraktse tabeli objektiga, millel puuduvad struktuur ja
andmed. Struktuuri tekitamiseks tuleb sellesse tabelisse lisada veerud. Lisame näiteks
unikaalse koodivälja, toote nimetuse ja hinna.
DataColumn kood = toode.Columns.Add(”ToodeID”, typeof(Int32));
kood.AllowDBNull = false;
kood.Unique = true;
DataColumn nimi = toode.Columns.Add(”Nimi”, typeof(String));
nimi.AllowDBNull = false;
DataColumn hind = toode.Columns.Add(”Hind”, typeof(Decimal));
hind.AllowDBNull = false;
Kuigi koodi näol on tegemist igati sobiva väljaga tabeli primaarvõtmeks, ei käsitle DataSet
seda kui primaarvõtit enne, kui oleme seda näidanud läbi tabeli PrimaryKey omaduse. Kuna
primaarvõti võib olla ka kombinatsioon mitmest väljast, siis tuleb meie veerg paigutada
massiivi.
toode.PrimaryKey = new DataColumn[] {kood};
Analoogselt võiks lisada ka piiranguid (Constraint) ja viiteid (Reference).
Andmetabelitesse saab lisaks reaalseid andmeid sisaldavatele väljadele lisada ka arvutatud
välju. Nt kui teame, et hind on meil Eesti kroonides, siis võib tekkida vajadus esitleda hinda
ka eurodes. Selleks võime lisada oma tabelisse arvutatud välja:
DataColumn hindEur = toode.Columns.Add(”HindEUR”, typeof(Decimal));
hind.Expression = ”Hind * 15,56”;
Loomulikult pole sellise tühja andmebaasiga midagi peale hakata. Selleks, et midagi huvitavat
korda saata, tuleb DataSetis olevatesse tabelitesse ka andmeid lisada. Andmete lisamiseks on
kaks moodust: me võime andmed ise tekitada ja rea kaupa tabelisse kirjutada või loeme
andmed kusagilt andmebaasist.
Alustame käsitsi rea lisamisest. Selleks tuleb esmalt tekitada sobiva struktuuriga andmerea
objekt.
DataRow rida = toode.NewRow();
Seejärel tuleb värskelt loodud ritta sisestada sobivad andmed. Väljade poole saab pöörduda
nii välja nime kui ka positsiooni järgi.
rida[0] = 1;
rida[”Nimi”]=”Kapsas”;
rida[”Hind”]=3.3;
Kui rida sobivate väärtustega täidetud, lisame ta sobivasse tabelisse.
toode.Rows.Add(rida);
Ridasid on võimalik lisada ka läbi objektide massiivi nagu näha allolevas koodireas.
toode.Rows.Add(new Object[] {2, ”Porgand”, 5.0});
Tekkinud andmetabeli võime kuvada konsooliaknas nt järgmise for-lausega:
foreach (DataRow dr in toode.Rows) {
foreach (object o in dr.ItemArray)
Console.Write("{0}\t",o);
Console.WriteLine();
}
Loomulikult on olemas ka DataSetis olevate andmete kuvamiseks ning töötlemiseks
mugavamaid vahendeid. Enne kui neid saame vaadata, tuleb aga üle vaadata graafilise
kasutajaliidese (nt ASP.NET) põhitõed.
Ülesandeid
Koosta DataSet tabeliga autode andmete hoidmiseks. Mass tonnides arvutatakse
kilogrammides olevate andmete põhjal automaatselt. Lisa mõned andmed. Trüki andmed
ekraanile.
Olemasolevate andmete põhjal DataSeti loomine
Lisaks käsitsi andmete tekitamisele on võimalik DataSetis kasutada ka juba olemasolevaid
andmeid. Olemasolevate andmete kasutamiseks on kaks võimalust:
1. kasutada sama metoodikat, mis käsitsi uute DataSettide loomisel ning andmed lisada
rea kaupa kasutades XxxDataReader’it
2. kasutada DataAdapter objekti abi
DataSet on oma
olemuselt andmebaasis
olevate andmete
koopia. Tegemist on
väga kasuliku
objektiga andmete
töötlemisel. Samas
pole ilma reaalseid
andmebaasis olevaid
andmeid
värskendamata sellest
võimsast vahendist
eriti kasu.
Andmetabelis olevate
andmete
sünkroniseerimiseks
andmebaasiga on DataAdapter objekt. DataAdapter klass on kogumik andmebaasi käske ja
ühendusi, mida saab kasutada tabelite täitmiseks e. lugemiseks andmebaasist (Fill) ja
muudatuste ülekandmiseks andmebaasi (Update). Iga DataAdapter tegeleb vaid ühe tabeli
andmetega. Kuna DataAdapter tegeleb otseselt andmebaasiga, tuleb valida DataAdapter
vastavalt andmebaasile. .NET raamistikuga on kaasas kaks DataAdapteri klassi
* OleDbDataAdapter – igasuguste andmeallikatega suhtlemiseks
* SqlDataAdapter – Suhtlemiseks SQL Serveritega alates versioonist 7.0
XxxDataAdapteri kaudu
on võimalik sooritada
kõiki DML (Data
Manipulation
Language -
andmetöötlus) tegevusi
(andmete valimine,
muutmine, lisamine,
kustutamine).
DataAdapteri küljes on väga palju erinevaid omadusi ja meetodeid. Olulisemad neist on:
* SelectCommand – Käsk, millega tuuakse andmed andmebaasist andmetabelisse
* InsertCommand – Käsk, mis sisestab lisatud read andmebaasi
* UpdateCommand – Käsk, mis viib andmetabelis tehtud muudatused andmebaasi
* DeleteCommand - Käsk, mis kustutab andmetabelist kustutatud read ka andmebaasist
* Fill meetod – täidab andmetabeli so SELECT lause või StoredProtseduuri tulemus
* Update meetod – Kutsub vastavalt vajadusele välja vajalikud INSERT, UPDATE ja
DELETE käsud
DataAdapteri kasutamiseks tuleb luua DataAdapteri objekt ning määrata vajalikud omadused.
SqlDataAdapter daTooted = new SqlDataAdapter();
SqlConnection yhendus = new SqlConnection(
”uid=KASUTAJANIMI;pwd=PAROOL;data source=dotnet;” +
”persist security info=False;initial catalog=KASUTAJANIMI”);
SqlCommand cmSelect = new SqlCommand(
"SELECT TOP 20 ToodeID, Nimi, Hind FROM Toode", yhendus);
daTooted.SelectCommand = cmSelect;
Selleks, et värskelt loodud adapteri abil täita andmetabel, tuleb välja kutsuda Fill meetod. Fill
meetod on mitu korda üle kirjutatud/maskeeritud ning oskab täita nii DataSeti, DataTable’t
kui ka mõnda konkreetset tabelit DataSetist.
Varem loodud DataSetis oleva tabeli Toode võime täita nt järgmise koodireaga.
int read = daTooted.Fill(ds, ”toode”);
DataAdapter on ise niipalju intelligente, et oskab enne andmete lugemist avada ühenduse
andmebaasiga ning sulgeb selle, kui kõik andmed on käes.
Igal real DataTable’s on RowState omadus. Tegemist on ainult loetava omadusega, mis näitab,
kas see rida on peale viimast andmete värskendamist muudetud, lisatud või kustutatud.
RowState võib omada järgnevaid väärtuseid:
RowState väärtus
Selgitus
DataRowState.Added
Uus rida
DataRowState.Deleted
Kustutatud rida
DataRowState.Modified
Real olevaid andmeid on muudetud
DataRowState.UnChanged
Andmeid ei ole muudetud
DataSet hoiab iga rea kohta kahte andmete komplekti: hetkel kehtivad andmed ja esialgsed
andmed. DataSeti kasutades pääsete ligi mõlemale andmete komplektile.
DataRowVersion omaduse väärtuseks saab vastavalt vajadusele panna, kas
DataRowVersion.Current või DataRowVersion.Original.
Järgnev näide vaatab läbi DataSet’is oleva tabeli kõik read ning näitab rea staatust. Kui rida on
uus või muutmata, näidatakse andmete hetkel kehtivat versiooni. Kui rida on kustutatud,
näidatakse esialgseid andmeid e. andmed, mis kustutati. Kui andmed on muudetud,
näidatakse nii esialgseid kui ka uusi andmeid.
foreach (DataRow row in ds.Tables["toode"].Rows)
{
Console.WriteLine("Rea staatus: " + row.RowState);
switch (row.RowState){
case DataRowState.Added:
case DataRowState.Unchanged:
Console.WriteLine("Hetkel kehtivad andmed\n" +
row["ToodeID", DataRowVersion.Current] + ", " +
row["Nimi", DataRowVersion.Current]+ ", " +
row["Hind", DataRowVersion.Current] + "\n\n");
break;
case DataRowState.Deleted:
Console.WriteLine("Esialgsed andmed\n" +
row["ToodeID", DataRowVersion.Original] + ", " +
row["Nimi", DataRowVersion.Original]+ ", " +
row["Hind", DataRowVersion.Original] + "\n\n");
break;
case DataRowState.Modified:
Console.WriteLine("Esialgsed andmed\n" +
row["ToodeID", DataRowVersion.Original] + ", " +
row["Nimi", DataRowVersion.Original]+ ", " +
row["Hind", DataRowVersion.Original] );
Console.WriteLine("Hetkel kehtivad andmed\n" +
row["ToodeID", DataRowVersion.Current] + ", " +
row["Nimi", DataRowVersion.Current]+ ", " +
row["Hind", DataRowVersion.Current] + "\n\n");
break;
}
}
Selleks, et muudatused jõuaksid ka andmebaasi, tuleb DataAdapteri küljes ära määrata
vajalike tegevuste käsud InsertCommand, UpdateCommand, DeleteCommand.
SqlCommand cmInsert = new SqlCommand(
"INSERT Toode (Nimi, Hind) VALUES (@Nimi, @Hind)", yhendus);
cmInsert.Parameters.Add(new SqlParameter("@Nimi",
SqlDbType.NVarChar, 100, ParameterDirection.Input, false,
0, 0, "Nimi", DataRowVersion.Current, null));
cmInsert.Parameters.Add(new SqlParameter("@Hind",
SqlDbType.Money, 8, ParameterDirection.Input, false,
0, 0, "Hind", DataRowVersion.Current, null));
daTooted.InsertCommand = cmInsert;
SqlCommand cmUpdate = new SqlCommand(
"UPDATE Toode SET Nimi = @Nimi, " +
"Hind = @Hind WHERE (ToodeID = @ToodeID)", yhendus);
cmUpdate.Parameters.Add(new SqlParameter("@ToodeID",
SqlDbType.Int, 4, ParameterDirection.Input, false,
0, 0, "ToodeID", DataRowVersion.Original, null));
cmUpdate.Parameters.Add(new SqlParameter("@Nimi",
SqlDbType.NVarChar, 100, ParameterDirection.Input, false,
0, 0, "Nimi", DataRowVersion.Current, null));
cmUpdate.Parameters.Add(new SqlParameter("@Hind",
SqlDbType.Money, 8, ParameterDirection.Input, false,
0, 0, "CustomerID", DataRowVersion.Current, null));
daTooted.UpdateCommand = cmUpdate;
SqlCommand cmDelete = new SqlCommand(
"DELETE Toode WHERE ToodeID = @ToodeID", yhendus);
cmDelete.Parameters.Add(new SqlParameter("@ToodeID",
SqlDbType.Int, 4, ParameterDirection.Input, false,
0, 0, "ToodeID", DataRowVersion.Original, null));
daTooted.DeleteCommand = cmDelete;
DataSetis olevate muudatuste salvestamine andmebaasi peaks käima 4 sammuga.
1. Kutsuda välja GetChanges meetod, mis tekitab uue DataSeti, millesse koondatakse
vaid need kirjed, mis on muutunud. Muutunud kirjeid on võimalik leida vastavalt
muudatuse tüübile. GetChanges meetodist on kasu juhul, kui soovitakse kontrollida,
mis järjekorras muudatused andmebaasi salvestada.
2. Kutsuda välja iga vajaliku DataAdapteri Update meetodi, et muudatused jõustuksid
andmebaasis
3. Kutsuda välja Merge meetodi mis ühendab kaks DataSeti taas üheks. Vajalik, kui
GetChanges abil eraldatud DataSetist mingi osa.
4. Kasutada AcceptChanges meetodit, et kinnitada muudatused DataSetis
Koodis näeb see protseduur välja järgnev:
if (ds.HasChanges()) {
DataSet dsTemp = ds.GetChanges();
daTooted.Update(dsTemp,"toode");
ds.Merge(dsTemp);
ds.AcceptChanges();
}
Loomulikult on võimalik küsida muudatusi iga tabeli kohta ning sealt tehtud muudatuse tüübi
kohta eraldi. Samuti on võimalik muudatused kinnitada lisaks DataSet tasemele ka tabeli või
tabeli rea tasemel.
Kui töötate andmetega ühenduseta keskkonnas, võivad tekkida andmete muutmisel
konfliktid. ADO.NET kasutab ühenduseta keskkonnas optimistlikku lukustamist st. lukk
eemaldatakse kohe, kui andmed on loetud, st andmete lugemise ja rakenduse poolt tehtud
muudatuste salvestamise vahel võib keegi neid samu andmeid muuta.
Vigadele reageerimiseks pakuvad DataSet, DataTable ja DataRow klassid omadust HasErrors.
Läbi selle omaduse on võimalik tuvastada, millised andmed on sattunud konflikti. DataRow
klassil on lisaks olemas veel GetColumnsInError meetod, mis tagastab konkreetsed väljad, mis
antud real on konfliktis.
Konfliktide tekkimisel oleks mõistlik uuendusi mitte peale suruda, vaid paluda kasutajal antud
konfliktid lahendada. Samuti on võimalik konfliktsed muudatused tagasi kerida, selleks võib
kutsuda välja RejectChanges meetodi konfliktis oleva DataSeti’i, DataTable või DataRow
küljest.
Kui soovitakse kasutaja poolt tehtud muudatused tühistada ja lugeda andmebaasist asemele
värsked andmed, tuleks DataSet või DataTable puhastada kasutades Clear meetodit ning
seejärel täita uuesti DataAdapteri Fill meetodi abil.
Järgnevas näites tühistatakse kõik kasutaja poolt tehtud konfliktsed muudatused. Selleks
ütleme adapterile, et ta jätkaks muudatustega ka peale vea tekkimist.
daTooted.ContinueUpdateOnError = true;
daTooted.Update(ds);
Kui Update käsk lõpetab, siis on kõik mitte konfliktsed kirjed ära muudetud ning konfliktsed
jäänud muutmata. Et ka kasutajal oleks mingi ülevaade toimunust, loeme ette, millistes
tabelites, millistel ridadel ja väljadel probleem tekkis ning tühistame tehtud muudatused.
if(ds.HasErrors){
foreach(DataTable table in ds.Tables){
if(table.HasErrors){
Console.WriteLine("Probleem tabelis ’{0}’.", table.Name);
foreach(DataRow row in table.Rows){
if(row.HasErrors){
Console.WriteLine("Probleem tootega nr {0}.", row["ToodeID"]);
Console.WriteLine("Vea kirjeldus: {0}", row.RowError);
foreach(DataColumn col in row.GetColumnsInError()){
Console.WriteLine(col.ColumnName, "Probleem selle väljaga");
}
Console.WriteLine("Muudatus tühistati!\n”);
row.ClearErrors();
row.RejectChanges();
}
}
}
}
}
Kui probleemid lahendatud, lubame DataSetil muudatused salvestada:
daToode.AcceptChanges();
Kuigi selline automaatne muudatuste tagasi lükkamine tagab programmi töö ka konfliktide
tekkimise korral, on enamasti mõistlik lasta kasutajal otsustada, kas peale jäävad tema tehtud
muudatused, või need andmed, mis on andmebaasis.
Lisaks eelpool mainitule on selle värskendamise protseduuri juures veel teine konks: nimelt kui
andmebaasis on vahepeal midagi muudetud, siis neid muudatusi me oma DataSeti ei loe.
Muudatuste lugemiseks on kaks meetodit: kas leiate mingi kavala SQLi abil read, mis on
vahepeal muutunud ja vahetate need välja/lisate juurde või teete DataSeti tühjaks ja täidate
uuesti. Viimast on oluliselt lihtsam rakendada. Selleks tuleb AcceptChanges asendada
järgmiste meetodi väljakutsetega:
ds.Clear();
daToode.Fill(ds, "toode");
Kuigi DataSet on mõeldud peamiselt andmete hoidmiseks ühenduseta keskkonnas, oskavad
mitmed Windowsi ja ASP.NET graafilise liidese komponendid (nt DataGrid jt) neid andmeid
kuvada ja pakuvad juurde ka sorteerimise, muutmise jt võimalusi. Sellest aga lähemalt juba
ASP.NET juures leheküljel 248.
Ülesandeid
* Täida DataSet autode tabelist tulevate andmetega
* Muuda andmeid DataSetis
* Kirjuta muutused baasis olevasse andmetabelisse
* Katseta, mis juhtub, kui baasis olevaid andmeid on vahepeal muudetud. Püüa muutusi
hallata, lasta kasutajal valida mida teha.
XML
Extensible Markup Language ehk XML on lihtne ja väga paindlik tekstiformaat, mis baseerub
SGML’il (Standard Generalized Markup Language). XML töötati välja XML-töögrupi
(algselt tuntud kui SGMLi redaktsiooniliselt läbivaatava nõukogu – SGML Editorial Review
Board) poolt, mis loodi World Wide Web Konsortsiumi patronaaži all 1996. aastal. Töögruppi
juhtis Jon Bosak, Sun Microsystemsist aktiivses koostöös W3C poolt organiseeritud XML-
erihuvigrupiga (varem tuntud SGML-töögrupina).
XML’i loomise eesmärgid:
* XML peab olema kasutatav üle Interneti.
* XML peab olema loetav nii inimesele kui arvutile.
* XML rakenduste ampluaa peab olema võimalikult lai: sisestamine, lehitsemine,
otsing, infotöötlus, sisutöötlus.
* XML ühildub SGMLga
* XML dokumentide töötlemise programme peab olema lihtne kirjutada
* XML disain peab olema formaalne ja kompaktne
* XML dokumentide genereerimine peab olema lihtne. Kuigi XML dokumentide
koostamiseks kasutatakse enamasti spetsiaalseid redaktoreid, peab nende tekitamine
olema võimalik mistahes redaktoriga.
* XML märgistuse napisõnalisuse nõue pole tähtis.
Aastal 1998 tuldi välja XML’i versiooniga 1.0, millele lisati väiksed täiendused aastal 2000
ning see versioon on ka hetkel ainukene.
XML failide loomiseks ei ole vaja spetsiaalseid programme, piisab vaid programmist, milles on
võimalik tööd salvestada tekstifailina.
Kui RTF ja HTML failid on orienteeritud välisilmele ehk sellele, kuidas dokumente inimesele
näidata, siis XML on orienteeritud andmete paremale kirjeldamisele e. kuidas andmeid
võimalikult hästi programmidele arusaadavaks teha. Sellest tulenevalt on nendel
failiformaatidel ka mõned olulised erinevused. RTF ja HTML failis paiknevad andmed ja
nende kujundus läbisegi, XML’is moodustatakse eraldi failid nii andmetele, nende
kujundusele ja struktuurile. See muudab XML andmete programse kasutamise oluliselt
lihtsamaks, kui on seda RTF või HTML andmete töötlemine. Ühtlasi võimaldab anda
samadele andmetele erinevaid kujundusi lihtsamalt.
XML’i kirjutamise reeglid
XML’i fail koosneb kahest osast: faili alguses on deklaratsioonid selle faili töötlemiseks ning
sellele järgnevad andmed.
Reeglid
* XML-dokumendi alguses peab olema näidatud, mis versiooni kasutatakse (töö kirjutamise
hetkel on ainukeseks versiooniks 1.0)
* XML-dokument peab omama ainult üht juurelementi, mis sisaldab kõiki teisi elemente
dokumendis
...
* Igal elemendil (ka tühjal) peab olema algusmärgis ja lõppmärgis
Algusmärgis
Sisu
Lõppmärgis
Ants
* XML’is kasutatavad atribuudid peavad alati olema jutumärkide või ülakomade vahel. Ei
ole vahet, kumba kasutada, kuid oluline on see, et alustav ja lõpetav märk oleksid samad.
Seega võib juhtuda, et ühe elemendi üks atribuut on kirjutatud ülakomade, teine
jutumärkide vahele.
* XML’is tehakse vahet suur-ja väiketähtedel.
Näiteks ja on erinevad elemendid
* Alamelement peab lõppema enne peaelementi. Kui HTMLi vanemates versioonides on
lubatud nt järgmine konstruktsioon , siis XMLis sellist asja olla ei tohi ehk
korrektne oleks
Kui XML fail vastab eelpool loetletud reeglitele, on tegemist trimmis (Well Formed)
XML’iga.
XML’i elemendid
XML-elementidele nimede panemisel kehtivad järgmised reeglid:
* element esitatakse tagidena. Tage kirjutatakse järgmiselt: sisu.
Esimest nimetatakse alustavaks tagiks, teist lõpetavaks.
* nimi võib sisaldada tähti, numbreid ja mõningaid sümboleid (sümbolit „:” ei tohi kasutada,
kuna see on kasutusel nimeruumidele viitamisel);
* nimi ei tohi alata numbri või kirjavahemärgiga;
* nimi ei tohi alata kombinatsiooniga xml (samuti ka XML või Xml jne);
* nimi ei tohi sisaldada tühikut;
* elementide nimedes tehakse vahet suurtel ja väikestel tähtedel
* teinekord võib elementidel sisu puududa. Siis kasutatakse nn tühje elemente
Näiteks element on
sama, mis element
Sisu poolest võib elemendid jagada kolmeks:
* tühjad elemendid
* lihtsad elemendid
* keerukad elemendid
Tühjad elemendid: tühjadeks nimetatakse elemente, millel sisu puudub ja nende esitamiseks on
kaks võimalust:
1. kasutatakse alustatavat ja lõpetavat tagi, nii et nende vahele ei jää midagi
2. kasutada spetsiaalset tagi, kus tagi lõpp sisaldub alustavas tagis
Lihtsad elemendid: elemendid, kus alustava ja lõpetava tagi vahel on mingi lihtne väärtus,
nt sõna, lause, kuupäev, number.
Mati
Keerukad elemendid: elemendid, mille sisuks on teised elemendid
AS Virumaa Mets
Metsa 7
0011223344
Loetavuse parandamiseks võib kasutada treppimist, sest XML’i parserid ignoreerivad
tühiruumi
Elementidele saab lisada ka atribuute.
Atribuudid
Atribuute on mõistlik kasutada andmete metadata jaoks (ehk atribuut on andmed andmete
kohta). Atribuudid kirjutatakse elemendi alustava tagi sisse. Ühe elemendi sees peavad olema
kõik atribuutide nimed erinevad. Atribuudid kirjutatakse kujul: atribuudinimi=väärtus.
Väärtused peavad olema kas jutumärkide või ülakomade vahel.
Näiteks:
Ants Aavik
Atribuudinimedele kehtivad samad reeglid, mis elementide nimedele.
Võib tekkida küsimus, milliseid andmeid esitada elementidena, milliseid atribuutidena? Sellele
küsimusele ühtset vastust ei ole, kõik sõltub vajadustest, kuid heaks tavaks on see, et
atribuutides ei ole mitte andmed, vaid lisainfo andmete kohta ehk metadata. Samuti pannakse
sinna vahel ka andmed, mis on lühikesed ning mille puhul on aimata, et neid pole põhjust
hiljem laiendada (osadeks jaotada).
XHTML
XHTML on XML’i reeglite järgi kirjutatud HTML. Täiendavalt on kokkulepe, et kõik tag’id
kirjutatakse väikeste tähtedega. Kui soovite kontrollida, kas teie poolt valmistatud HTML
dokument on ka XHTML dokument, siis võite seda teha W3 portaalis paikneva DTD
dokumendi abil:
Lehestiku kontrollimiseks sobib valideerimisteenus aadressil http://validator.w3.org/
Nimeruum
Mõnikord võib tekkida situatsioon, kus mitu osapoolt kasutavad XML-dokumentides ühte ja
sama elementi erinevas tähenduses. Seetõttu on vaja eristada, millisesse sõnavarasse üks või
teine märgis kuulub. Lahenduse antud probleemile toob XML-nimeruum (XML namespace).
W3C standardi järgi on XML-nimeruum nimede kollektsioon, mis on kasutusel XML-
dokumendis elementide ja atribuutidena. Üks XML-dokument võib sisaldada elemente ja
atribuute rohkem kui ühest nimeruumist.
Allolevas näites on kirjeldatud firma andmed nii, et firma nime ja registrinumbri kirjeldus on
kirjeldatud nõnda, nagu riigis kokkulepitud. Kõigi ülejäänud elementide kirjeldused on
väljamõeldud mingi firma enese poolt:
* iga element, millel pole eesliidet, kasutab nimeruumi
xmlns=”http:/www.mingifirma.ee/firma
* iga element, millel on eesliide ee: kasutab nimeruumi
xmlns:ee=”http://www.riik.ee/firma”
AS Virumaa Mets
Metsa 7
0011223344
XML’i valideerimine
Et XML andmeid vahetada või töödelda, on vähe kasu teadmisest, et XML on WellForm,
kuna XML’i moodustamise reeglid on väga lõdvad ja täpselt samu andmeid on võimalik
esitada mitmel erineval moel. Et fikseerida, mis kujul andmed peaksid XML failis olema, on
kasutusel XML’i valideerimine. Neid reegleid on võimalik seada kas kasutades DTD
(Document Type Defination) või XML skeeme (XML Schema). Kui XML fail vastab DTD’s
või XML skeemis kirjeldatud reeglitele, siis nimetatakse seda XML’i trimmis XML (valid
XML)
DTD kirjeldus võib paikneda XML andmetega samas failis või võib olla täiesti eraldi seisev
fail. Nende lausetega pannakse paika, millised elemendid on nõutavad ja milline on elementide
järjestuse kord.
DTD suurimaks puuduseks on see, et ei ole võimalik määrata, mis tüüpi peavad olema
elementides või atribuutides olevad andmed (ainukene andmetüüp on tekst). Seetõttu
kasutatakse keerukamate süsteemide puhul XML skeeme.
XML skeemid
XML skeemid on reeglite kogum, kus on kirjas, mis võib ja mis ei või olla XML-andmefaili
erinevates osades. Reeglitega on võimalik määrata, millised elemendid, millises järjekorras
peavad olema ning mis tüüpi andmeid võivad sisaldada atribuudid ja elemendid.
Andmetüüpidele on võimalik lisada piiranguid, nt vähim või suurim võimalik väärtus numbrite
puhul, tekstilistel andmetel teksti pikkus. Lisaks sellele on võimalik määrata konkreetset
väärtuste hulka.
Skeemides jagunevad elemendid vastavalt sisule. Lihtsaks elemendiks (simple type)
nimetatakse elemente, millel puuduvad atribuudid ja alamelemendid.
Näiteks: AS Virumaa Mets
XML skeemis võiks seda kirjeldada järgmiselt:
Antud XML failis peab esinema element Nimi vähemalt 1 korra, kuid võib esineda ka rohkem.
Nimi on tekstilist tüüpi (string).
Elemendid, millel on atribuudid ja/või alamelemendid, on keerulised elemendid (complex
type).
Näiteks:
AS Virumaa Mets
Metsa 7
0011223344
XML skeemis võiks seda kirjeldada järgmiselt:
Ülesandeid
* Koosta XML-fail autode loeteluga
* Koosta skeem ühe auto andmete hoidmise kirjelduseks
* Koosta skeem autode loetelu andmete kirjelduseks
XMLi kasutamine SqlServeris
Juba alates SQL Server 2000st on SQL Serveris olnud XMLi tugi. SQL Server 2005s on seda
oluliselt täiendatud. Juurde on tulnud isegi xml andmetüüp. Kuigi SQL päringuid tehes ei ole
XML kujul palju rakendust, on see funktsionaalsus äärmiselt vajalik rakendustes, kuna .NET
raamistik saab XML andmetega väga hästi hakkama.
XMLi genereerimine relatsioonilistest andmetest
Kõige lihtsam on SQL Serverist XMLi küsida SELECT lause abil, lisades lõppu FOR XML
märksõnad. Näiteks järmise SQL lause abil saame kolme toote nimed XML kujul
SELECT TOP 3 nimi, hind
FROM toode
ORDER BY nimi
FOR XML AUTO
Tulemus on:
Nagu näeme, on saadud XML ilma juurelemendita. See tähendab, et otseselt XML faili sellist
tulemust salvestada ei saa, erinevad programmid sh .NET võtavad XMLi vastu ka ilma
juurelemendita. Juurelemendi võite saadud XMLile lisada, kas käsitsi läbi programmi või siis
SQLis lisades FOR XML ... lause lõppu, ROOT(’juurikanimi’).
Lisaks andmetele ühest tabelist on võimalik sama meetodiga pärida ka seotud andmeid.
Tulemusena genereeritakse hierarhiline XML:
SELECT tellimus.klient, toode.nimi AS toode, toode.hind
FROM tellimus
INNER JOIN tellimustoode
ON tellimus.kood = tellimustoode.tellimus_kood
INNER JOIN toode ON tellimustoode.toode_kood = toode.kood
FOR XML AUTO
Tulemus on:
Loomulikult on võimalik kasutada ka teistsuguseid XML struktuure. Näiteks kui asendame
SELECT lause lõpus märksõna AUTO märksõnaga RAW, tekitatakse meile iga kirje kohta üks
XML element nimega row, kus kõik väljad on atribuutidena:
SELECT tellimus.klient, toode.nimi AS toode, toode.hind
FROM tellimus
INNER JOIN tellimustoode
ON tellimus.kood = tellimustoode.tellimus_kood
INNER JOIN toode ON tellimustoode.toode_kood = toode.kood
FOR XML RAW
Tulemus on:
Kui need kaks meetodit ei sobi, siis on loomulikult võimalik ka ise struktuur ette anda. Selleks
tuleb SELECT lause abil moodustada universaalne tabel, kus oleksid järgmised väljad:
1. Tag – elemendi tase XML struktuuris
2. Parent – peaelemendi tase XML struktuuris
3. Väljad ja atribuudid, igaüks eraldi veerus. Väljade nimed tuleb kirjutada kujul
Element!tase!atribuut!täpsustus
Üritame eelpool kasutatud tooted teisendada järgmisele XML kujule:
madis
jaan
anni
Selleks tuleb meil tekitada universaalne tabel, mis näeb välja järgmine:
Tag
Parent
Tellimus!
1!
TellimusID
Tellimus!
1!Klient!
element
Tellitud
Toode!2!
ToodeID
Tellitud
Toode!2!
Toode
Tellitud
Toode!2!
Hind
1
NULL
1
madis
NULL
NULL
NULL
2
1
1
NULL
1
kala
55,00
2
1
1
NULL
2
kurk
5,00
2
1
1
NULL
3
piim
7,00
1
NULL
2
Jaan
NULL
NULL
NULL
2
1
2
NULL
2
kurk
5,00
2
1
2
NULL
4
limonaad
5,00
1
NULL
3
Anni
NULL
NULL
NULL
2
1
3
NULL
5
kapsas
5,00
2
1
3
NULL
6
õun
10,00
Sellise tabeli tekitamiseks tuleb moodustada UNION päring, mis paneb taseme kaupa tabeli
kokku. Esmalt loeme sisse tellimused ja seejärel tooted. Kuna XMLi hakatakse genereerima
vastavalt ridade järjekorrale, siis peame ka selle eelnevalt fikseerima. Sorteerida tuleb esmalt
TellimusID järgi, kuna me soovime, et üks tellimus oleks ühes elemendis ning seejärel Parent
välja järgi, et esmalt oleks Tellimuse element moodustatud ning sinna järele tuleksid
TellitudTooted. Kui päring valmis, tuleb lisada lõppu FOR XML EXPLICIT ning ongi
valmis:
SELECT 1 AS Tag, null AS Parent,
kood AS [Tellimus!1!TellimusID],
klient AS [Tellimus!1!Klient!element],
null AS [TellitudToode!2!ToodeID],
null AS [TellitudToode!2!Toode],
null AS [TellitudToode!2!Hind]
FROM tellimus
UNION ALL
SELECT 2 AS Tag, 1 AS Parent, tellimustoode.tellimus_kood, null,
toode.kood, toode.nimi, toode.hind
FROM tellimustoode
INNER JOIN toode ON tellimustoode.toode_kood = toode.kood
ORDER BY 3, 2
FOR XML EXPLICIT
Ülesandeid
* Koosta XML EXPLICIT abil ühe auto andmetele vastav dokument, kus mark on
elemendina ning valmistamisaasta atribuudina märgitud
* Koosta eelnevat näidet arvestades XML EXPLICIT jaoks tabel ning sealtkaudu XML-
dokument, kus autode andmed on maakondade kaupa loetelus. Iga maakonna juures
on näha ka maakonnakeskus autoregistri asukohana.
XML andmetüübi kasutamine
XML dokumentide ja XML dokumendi osade hoidmiseks SQL Serveris saab kasutada xml
andmetüüpi. Ühele väljale saab panna kuni 2GB XML andmeid. Vajadusel saab xml välja
siduda ka schemaga, mille järgi väljale sisestatavat XMLi kontrollitakse.
Näiteks loome ühe lihtsa tabeli, mis koosneb võtmeväljast ning XML väljast:
CREATE TABLE xmlstuff(
kood int IDENTITY(1,1) PRIMARY KEY,
xmljutt xml NOT NULL,
)
Lisame sinna ka mõned read. Allolevas näites on XMLi lühendatud. Terve lisatav XML on ära
toodud eelmise peatüki näidetes ning ka järgmisel lehel olevas tabelis..
insert xmlstuff (xmljutt) VALUES(N'm... ')
Tulemuseks on meil 3 reaga XML tabel:
koo
d
Xmljutt
1
2
|
|
|
|
|
|
3
madisjaananni
Kuigi meetodeid XMLi haldamiseks SQL Serveri enda vahenditega on mitmeid, vaatleme
siinkohal vaid kaht:. xml.query ja xml.exist. Need on xml andmetüübi meetodid, mis
võimaldavad XML väljalt XPath päringute abil otsida sobivaid väärtuseid ning kontrollida,
kas sellised väärtused on olemas.
Näide 1: toome välja kogu XML välja sisu, kui selles XMLis on juurelemendiks tellimus:
select kood, xmljutt.query('/') AS xmljutt
from xmlstuff
where xmljutt.exist('/tellimus') = 1
Tulemus on järgmine:
koo
d
Xmljutt
1
3
madisjaananni
Näide 2: toome välja XMLis oleva elemendi Klient sisu:
select kood, xmljutt.query('//klient') AS xmljutt
from xmlstuff
where xmljutt.exist('//klient') = 1
Tulemus on järgmine:
koo
d
Xmljutt
3
madisjaananni
Näide 3: toome välja XMLis sisalduvad tooted
select kood, xmljutt.query('//toode') AS xmljutt
from xmlstuff
where xmljutt.exist('//toode') = 1
koo
d
Xmljutt
1
Näide 4: jätame toodetest nähtavale vaid need, mille hind on vähemalt 10:
select kood, xmljutt.query('//toode[@hind >= 10]') AS xmljutt
from xmlstuff
where xmljutt.exist('//toode') = 1
koo
d
Xmljutt
1
Ülesandeid
* Loo tabel tulbaga autode andmete hoidmiseks XMLina
* Küsi sealt välja talletatud toodete margid
* Näita vaid nende autode andmeid, mis on valmistatud enne 1995ndat aastat.
XML andmete kasutamine .NET raamistikus
XML on väga hea vahend erinevatest andmeallikatest pärit andmete kokku koondamiseks ja
haldamiseks. XMLi paremaks töötlemiseks on .NET raamistikus terve hulk erinevaid klasse,
mis jagunevad mitmete nimeruumide vahel vastavalt W3 standarditele.
Olulisemad klassid, mida XMLiga töötades vaja läheb on:
Abstraktne klass
Kasutusala
Päritud klassid
XmlReader
XML voog (strem) XML andmete lugemiseks.
Lugemise ajal on võimalik ka dokumentide
kontrollimine e. valideerimine
XmlTextReader
XmlNodeReader
XmlWriter
Tekitab XML voo kusagile edasi saatmiseks
(teine rakendus, andmekandja)
XmlTextWriter
XmlNavigator
Kasutatakse dokumendis liikumiseks, kui kogu
dokumendi mällu laadimine ei ole otstarbekas
XmlPathNavigator
XmlResolver
Väliste XML ressursside leidmine URI abil
XmlUrlResolver
XMLi parsimine
Parsimine tähendab andmete lugemist ning seejärel loetud andmetega mingite tegevuste
sooritamist. Parsimine võimaldab XML failist leida just seda infot, mida teil kõige rohkem
vaja läheb.
Püüame lugeda XML andmeid kasutades XmlReader klassi. Raamistiku poolt on tehtud kolm
erinevat klassi XML andmete lugemiseks. Kui nende funktsionaalsus ei ole piisav, siis võite ise
alati neid klasse juurde tekitada.
XMLi lugemiseks saab kasutada tavalisi System.IO klasse. Läbi IO klasside on võimalik
XMLi lugeda nii failist kui ka voost. Sisuliselt ei ole vahet, kumba meetodit kasutada aga, et
oleks lihtsam jälgida, siis võite ette kujutada nii, et Fail on andmekandjale salvestatud nimeline
baidijada, voog on aga kusagilt mujalt (võrk, andmebaas, jne) tulev baidijada.
Proovime alustuseks lugeda sisse tavalise tekstifaili.
Selleks on meil esmalt vaja System.IO nimeruumi
using System.IO;
ning seejärel kirjutame protseduuri, mis avab tekstifaili ja trükib selle rea kaupa ekraanile.
string FailiNimi = @"c:\mingifail.txt";
if (File.Exists(FailiNimi)) { // kui fail on olemas
StreamReader FailiLugeja = File.OpenText(FailiNimi);
string rida = FailiLugeja.ReadLine();
while (rida != null) {
Console.WriteLine(rida);
rida =FailiLugeja.ReadLine();
}
FailiLugeja.Close();
}
Kui soovite kogu faili mällu laadida ja seejärel temaga edasi toimetada, tuleks
System.Text.StringBuilder klassi abil ehitada faili sisaldav string.
Analoogselt tekstifaili lugemisega käib ka XML faili lugemine. Lugeja on lihtsalt
XmlTextReader tüüpi objekt. XmlTextReader oskab XMLi lugeda voost, stringist ja
TextReader objektist.
XmlTextReader objekti saame tekitada järgneva koodireaga:
XmlTextReader MinuXmlLugeja = new XmlTextReader(@"c:\mingifail.xml");
Kui lugeja on olemas, saame XMLi oksa (node) kaupa lugema hakata. Enamasti on seda
kasulik teha mingi korduslausega:
while (MinuXmlLugeja.Read()) {
// tee midagi
}
Kui on soov XML andmeid analüüsida või muul moel kasutada, on vaja teada, mis tüüpi oksal
parajasti olete. Oksa tüübi selgitamiseks on vaja kontrollida NodeType omadust, selle
omaduse väärtus võib olla üks järgnevatest:
Need kolm oksa tüüpi Element, TypeText, EndElement on kõige olulisemad, kuid lisaks neile
on veel olemas:
Oksa tüüp
Selgitus
XmlNodeType.CDATA
Mitte parsetav tekst
XmlNodeType.Comment
XML kommentaar
XmlNodeType.ProcessingInstruction
Töö juhised erinevatele parseritele ja rakendustele
XmlNodeType.WhiteSpace
Tühjus elementide vahel
XmlNodeType.XmlDeclaration
XMLi kirjeldused
Kui element võib olla tühi e. algus ja lõpu tag on üks ja seesama, siis tuleks seda kontrollida
IsEmptyElement omaduse kaudu.
Järgnevas näites genereerime XmlReader’it kasutades väljundisse uue XMLi.
XmlTextReader MinuXmlLugeja = new XmlTextReader("mingifail.xml");
while (MinuXmlLugeja.Read()) {
switch (MinuXmlLugeja.NodeType) {
case XmlNodeType.Comment:
Console.WriteLine("");
break;
case XmlNodeType.Element:
if (MinuXmlLugeja. IsEmptyElement) {
Console.WriteLine("<" + MinuXmlLugeja.Name + " />");
}else {
Console.WriteLine("<" + MinuXmlLugeja.Name + ">");
}
break;
case XmlNodeType.EndElement:
Console.WriteLine("" + MinuXmlLugeja.Name + ">");
break;
case XmlNodeType.Text:
Console.WriteLine(MinuXmlLugeja.Value);
break;
default:
// siin võiks midagi teha ülejäänud oksadega
Console.WriteLine(" --- Tundmatu oks --- ");
break;
}
}
Lisaks tekstilisele sisule võivad elemendid omada ka atribuute. Kas elemendil on atribuut,
saame teada läbi HasAttribute omaduse. Atribuutide arvu saame teada AttributesCount
omadusest. Edasi on võimalik küsida atribuute, kas nime või indeksi järgi kasutades
GetAttribute meetodit
MinuXmlLugeja.GetAttribute(0); // esimese atribuudi väärtus
MinuXmlLugeja GetAttribute(”ID”); // atribuudi ID väärtus
või palume lugejal liikuda järjest järgmisele atribuudile.
if (MinuXmlLugeja.HasAttributes){
for (int i = 0; i < MinuXmlLugeja.AttributeCount; i++){
MinuXmlLugeja.MoveToAttribute(i);
Console.Write(" {0}='{1}'", MinuXmlLugeja.Name,
MinuXmlLugeja.Value);
}
MinuXmlLugeja.MoveToElement();
}
Nagu iga teisegi sisend/väljund protseduuri, nii ka XML parsimise juures võib tekkida vigu.
Kui XmlReader avastab vea, annab ta sellest teada läbi XmlExeption’i. Seega peaks kogu
XMLi lugemine olema try ... catch struktuuri sees:
XmlTextReader MinuXmlLugeja = new XmlTextReader("mingifail.xml");
try {
while(MinuXmlLugeja.Read()) {
// tee midagi
}
}
catch(XmlException e) {
Console.WriteLine(e.Message);
Console.WriteLine("Pronleem XML failis – rida {0}, veerg {1}",
MinuXmlLugeja.LineNumber, MinuXmlLugeja.LinePosition);
}
Ülesandeid
* Koosta autode andmete fail XMLina
* Tee kindlaks, mitme auto andmed on faili kirjutatud
* Trüki välja leitud automarkide nimed.
XMLi valideerimine
XML failil on kaks staatust, mis näitavad tema kvaliteeti:
* Well-Formed – korrektselt vormistatud, st XML vastab W3 poolt seatud XMLi
reeglitele
* Valid –Well-Formed ning lisaks vastab see XML teie poolt seatud reeglitele
Kasutades XmlTextReader klassi, saame vea siis, kui XML ei ole Well-Formed. Täpsemaid
kontrolle (millised elemendid on olemas, kas nad on õiges järjestuses jne) aga ei rakendata.
XMLi täpsemaks kontrollimiseks on kaks moodust: DTD dokumendid ja XML Schema.
Siinkohal ei hakka vaatama, kuidas neid dokumente moodustada, vaid vaatame, mis saab siis,
kui teil on selline dokument olemas ja tahate teada, kas XML sisend vastab sellele.
Järgnevalt vaatleme kahte erinevat lähenemist XML valideerimisele. .NET raamistiku 1.x
versioonis oli XMLi valideerimiseks XmlValidatingReader klass, raamistiku 2.0 versioonis
seda enam ei ole ning selle asemel saab kasutada XmlReader klassi. Suurim erinevus nende
kahe vahel seisneb selles, et kui ValidatingReader avastas vea, sai selle kinni püüda try ...
catch konstruktsiooni abil, nüüd tuleb selleks aga kasutada sündmusi.
XmlReader on huvitav klass ka selle poolest, et lugemise määranguid ei edastata mitte
omaduste kaudu, vaid spetsiaalse omaduste objektina. Seega, kui soovime XML failist lugeda
ning samal ajal kontrollida, et loetav XML oleks korrektne, peame tegema lugemismäärangute
objekti ning edastama selle lugeja objektile.
Valideerimiseks kasutame schemat, mis on määratud XML failis.
Esmalt vaatame, kuidas valideerida parsimise ajal:
XmlReaderSettings maarangud = new XmlReaderSettings();
// kontrollimiseks kasutame schemat
maarangud.ValidationType = ValidationType.Schema;
// schemat asukoht on näidatud XML failis
maarangud.ValidationFlags = XmlSchemaValidationFlags.ProcessSchemaLocation;
// probleeme lahendame meetodiga xvr_ValidationEventHandler
maarangud.ValidationEventHandler +=
new ValidationEventHandler(xvr_ValidationEventHandler);
// Teeme objekti XMLi lugemiseks
XmlReader lugeja = XmlReader.Create(@"C:\Erki\mingifail2.xml", maarangud);
// Hakkame parsema
while (lugeja.Read());
reader.Close();
Probleeme haldav meetod on üsna lihtsakoeline:
private void xvr_ValidationEventHandler(object sender,
ValidationEventArgs e) {
Console.WriteLine("Viga! {0}", e.Message);
Console.WriteLine("XML exception: Ln {0} Col {1}",
e.Exception.LineNumber,e.Exception.LinePosition);
}
Teine meetod on mitte XMLi jupi kaupa parsida, vaid lugeda kogu XML mällu. Kasulik
väikeste XML failide juures. Et näide oleks põnevam, loeme XMLi kasutades andmevoogu,
mille saame tavalisest faili lugemisest.
FileStream fs = File.Open(@"C:\Erki\mingifail2.xml", FileMode.Open);
XmlDocument xdoc = new XmlDocument();
XmlReaderSettings maarangud = new XmlReaderSettings();
maarangud.ValidationType = ValidationType.Schema;
maarangud.ValidationFlags = XmlSchemaValidationFlags.ProcessSchemaLocation;
maarangud.ValidationEventHandler +=
new ValidationEventHandler(xvr_ValidationEventHandler);
XmlReader lugeja = XmlReader.Create(fs, maarangud);
xdoc.Load(lugeja); // edaspidi on kogu XML kasutatav läbi xdoc’i
fs.Close();
Kui XML failis pole schemat määratud või lihtsalt soovite kontrollida mõne teise schema järgi,
siis saate sobiva schema määrata programmselt. Kontrolli võib teostada peale XML faili mällu
laadimist kasutades Validate meetodit:
XmlDocument xdoc = new XmlDocument();
xdoc.Load("mingifail2.xml");
xdoc.Schemas.Add(null, "naidis.xsd");
ValidationEventHandler veh =
new ValidationEventHandler(xvr_ValidationEventHandler);
xdoc.Validate(veh);
Või XML faili laadimise ajal, määrates schema ära määrangutes:
FileStream fs = File.Open("mingifail2.xml", FileMode.Open);
XmlDocument xdoc = new XmlDocument();
XmlReaderSettings settings = new XmlReaderSettings();
settings.Schemas.Add(null, "naidis.xsd");
settings.ValidationType = ValidationType.Schema;
settings.ValidationEventHandler +=
new ValidationEventHandler(xvr_ValidationEventHandler);
XmlReader reader = XmlReader.Create(fs, settings);
xdoc.Load(reader);
fs.Close();
Lisaks XmlDocument objektile on võimalik XMLi laadida ka DataSet’i sisse. Kuna DataSet
hoiab kõiki andmeid XML kujul, siis on XML andmete DataSeti laadimine tehtud äärmiselt
lihtsaks. Järgnevalt loeme DataSeti XML faili, koos seal näidatud Schemaga:
myDS = new DataSet();
myDS.ReadXml("C:\Erki\mingifail2.xml", XmlReadMode.ReadSchema);
Kui XML failis ei ole Schemat näidatud, on võimalus see genereerida vastavalt laetava XML
faili struktuurile:
myDS = new DataSet();
myDS.ReadXml("C:\Erki\mingifail.xml", XmlReadMode.InferSchema);
Loomulikult on võimalik ka ette määrata, kust tuleb schema ja kust tulevad andmed:
myDS = new DataSet();
myDS.ReadXmlSchema(@"C:\Erki\naidis.xsd");
myDS.ReadXml(@"C:\Erki\naidis.xml", XmlReadMode.IgnoreSchema);
Ülesandeid
* Koosta skeem ühe auto andmete tarvis
* Koosta skeem autode loetelu andmete tarvis
* Kontrolli, kas olemasolev autoandmete dokument vastab loodud skeemile
XMLi salvestamine
Kõige lihtsam on XMLi salvestada, kui olete loonud XmlDocument objekti. Sellisel juhul
tuleb välja kutsuda Save meetod ja ongi salvestatud.
xdoc.Save(@"C:\Erki\mingifail3.xml");
Loomulikult on võimalik genereerida XMLi ka siis, kui XmlDocument objekti ei ole. Sellistel
puhkudel saab kasutada XmlTextWriter objekti.
XmlTextWriter XmlKirjutaja = new XmlTextWriter(
@"mingifail4.xml", Encoding.UTF8);
XmlKirjutaja.Formatting = Formatting.Indented;
Kui kirjutaja loodud, saate hakata moodustama XML faili:
XmlKirjutaja.WriteStartDocument();
XmlKirjutaja.WriteStartElement("juurikas");
XmlKirjutaja.WriteStartElement("tellimus");
XmlKirjutaja.WriteAttributeString("tellimusid", "1");
XmlKirjutaja.WriteElementString("klient", "madis");
XmlKirjutaja.WriteEndElement();
XmlKirjutaja.WriteEndElement();
XmlKirjutaja.WriteEndDocument();
XmlKirjutaja.Close();
Tulemuseks on Well-Formed XML:
madis
XMLi on lisaks eelnevatele meetoditele võimalik salvestada ka otse DataSetist. Selleks on
DataSet’il kaks väga kasulikku meetodit:
myDS.WriteXml("C:\Erki\uus.xml", XmlWriteMode.IgnoreSchema);
myDS.WriteXmlSchema("C:\Erki\uus.xsd");
Ülesandeid
* Küsi kasutajalt auto andmed ning väljasta need XML-faili. Aasta salvestatakse
atribuudina, mark elemendina.
* Loe andmebaasist autode andmed DataSeti. Salvesta andmed XML-faili. Salvesta
skeem eraldi xsd-faili
ASP.NET
ASP.NET ei ole mitte ASP (Active Server Pages) uus versioon, vaid täiesti uus lähenemine
veebi rakenduste loomisele. Erinevalt ASPist ja ka PHPst, mis on peamiselt skriptimise keeled,
on ASP.NET lehtede taga olev kood täielikult objektorienteeritud nagu iga teinegi .NET
rakendus. Seega tuleks ASP.NETi võrrelda mitte PHP vaid JAVA rakendustega.
Koodi ASP.NET lehtede tarbeks võib kirjutada ükskõik millises .NET keeles. Lisaks
veebivormidele on võimalik oma rakendust veebis serveerida ka läbi veebiteenuste.
Ka ASP.NETist on olemas mitmeid versioone. Kui võrrelda ASP.NET versioone 1.1, 2.0 ja
3.5 siis võib öelda, et palju on jäänud samaks, kuid üht teist on ka ümber tehtud ja lisatud. Hea
uudis on see, et kõik vanad konstruktsioonid töötavad, kuid juurde on tulnud mitmed uued
meetodid. Täna provaideri(teenusepakkuja)põhisele lähenemisele on rakenduse loomine 2.0
versioonis muutunud märksa abstraktsemaks ja lihtsamaks. Palju koodi on viidud lehekülje
tagustelt koodilehtedelt provaideritesse. 3.5 juurde on suuremaks uuenduseks tulnud AJAX
ehk võimalus andmeid lehel reaalajas muuta ilma, et peaks kogu lehte selle tarbeks uuesti
laadima.
ASP.NET lehed koosnevad tekstifailidest, mida saab serveerida läbi IIS (Internet Information
Service) virtuaalkaustade. Lehtede loomiseks sobivad kõik tekstiredaktorid. Abivahendeid
usaldavale inimesele on kõige parem kasutada kirjutamiseks sellised abivahendeid, mis lehtede
loomisel kiirendavad koodi kirjutamist lõpetades alustatud sõnu, kontrollivad jooksvalt
süntaksit ning aitavad HTMLi loomisel. Üheks selliseks abivahendiks on Visual Studio. Ehkki
palja "rumala" tekstiredaktoriga kirjutamisel on eeliseks lihtsus, siis nt Visual Studio Web
Developer Expressiga on lootus ka algajal nõnda läbi saada, et ta paljudesse menüüdesse ära ei
upu.
Visual Studio paigaldamine
Visual Studio on arendusvahend, mida kasutavad väga paljud programmeerijad ning
programmeerimisfirmad sh ka Microsoft. Visual Studiost on olemas mitmeid versioone, neist
enamus on mõeldud professionaalsetele programmeerijatele ning programmeerijate
meeskondadele. Lisaks kommertsversioonidele on olemas ka tasuta versioonid (Express
versioonid), mis on mõeldud lihtsalt programmeerimishuvilistele ning õpilastele.
Visual Studio Express versiooni saamiseks tuleb minna aadressile
http://www.microsoft.com/express/ ning tõmmata endale sealt sobiv versioon. Veebilehestike
koostamiseks nagu nimigi ütleb, sobib enesele laadida Visual Web Developer Express.
Lisaks Visual Studiole oleks kasulik paigaldada ka andmebaasimootor Sql Server Express
Edition koos SQLi haldus- ja arendusvahendiga SQL Server Management Studio Express.
WDE paigaldamiseks peaks arvutil olema vähemalt 600 MHz protsessor (soovitavalt üle
1 GHz), vähemalt 192 MB mälu (soovitav 256 MB või koos andmebaasiga 512 MB) ning
sõltuvalt paigaldatavatest komponentidest 500 MB kuni 1,8 GB vaba kettaruumi. Enamuse
sellest pea 2GB suurusest mahust võtab spikker (Help). Kui spikrit ei paigalda on vajalik
kettaruum üle GB väiksem! Spikker on vajalik siis kui Teil ei ole pidevat Interneti ühendust
või töötate kohtades, kus alati ei ole võimalik Internetti kasutada. Kui on olemas püsiv
Internetiühendus võite kasutada veebis olevat spikrit, mis asub aadressil
http://msdn.microsoft.com. Online Spikker on integreeritud ka WDE sisse, mis võimaldab
sealt infot otsida ka WDE abil!
Installil puudub „traditsiooniline“ moodus, kus laetakse installeerimisfail kogu andmestikuga
alla ja pannakse käima. Selle asemel on võimaluseks laadida alla lühikene fail installatsiooni
käivituseks mis siis töö käigus laeb võrgust alla sobivad valitud lisatükid. Või siis juhul, kui
näiteks tahetakse Visual Studio Express panna peale tervele arvutiklassile, siis on võimalik
„offline install“ menüüst tõmmata iso-image, kus peal kõik Visual Studio Expresside juurde
kuuluv ning see kas plaadile kirjutada või mõne vastava utiliidiga (nt ISOBuster või
WinImage) lugeda.
Loomulikult tuleb kõigile paigaldatud toodetele panna peale ka kõik viimased
turvaparandused ning teenuspakid (ServicePack)!
Lihtsa veebilehestiku loomine
Esimene veebileht
Kõige lihtsam veebirakendus koosneb ühest lehest. Veebilehtede levinumaks keeleks on
HTML, mida veebilehitsejad mõistavad lugeda ning loetud teksti põhjal kasutajale lehe ette
kuvada. Mitmete veebitehnoloogiate (ASP.NET, Java servlet, PHP, Python ...) tulemusena
lehitsejasse saadetav tekst on ikkagi „puhas“ HTML, nii et osava peitmise korral ei pruugi
veebisaidi vaatajal kuidagi võimalik olla kindlaks teha, millise tehnoloogia abil vastav lehestik
on kokku pandud. Ning nagu varemalt kombeks ning praegugi lihtsamate, pidevat muutmist
mitte vajavate lehtede puhul kasutatakse, võibki veebileht olla üks harilik HTMLi reeglitele
vastav tekstifail, mida veebilehitsejas näidatakse. Mitmesugused tehnoloogiad on lihtsalt
leidnud võimalusi, kuidas võimalikult mugavalt lehtedel olevad andmeid määrata vastavalt
kasutaja soovidele. Lihtsaim HTMLi reeglitele vastav veebileht näeb aga välja järgmine:
Minu esimene leht
Tere maailm
Selle võib salvestada omale sobiva tekstiredaktoriga (nt. Notepad või ka vastinstalleeritud
Visual Web Developer). Panna failile laiendiks .html (siis teab veebilehitseja, et vastavat teksti
tuleb kujundada HTML-i reeglitele vastavalt), jätta meelde kuhu fail salvestati.
Edasi juba lehitsejas avada nagu tavalist kohaliku masina faili (failimenüüst ava). Tulemuseks
leht, kus ülal pealkirjariba peal on lehe pealkiri ning allpool sisu osas lehe sisu.
Veidi lähemad seletused, et mida miski HTMLi lehe osa tähendada võiks.
Avakäsklus teatab HTMLi versiooni. Nii nagu nt. Wordi dokumentidel on versioonid 2.0, 6.0,
97, 2000, 2003, 2007 jne, nõnda ka HTML on oma arengu käigus muutunud. Kõiki eri
versioone ja „murrakuid“ lugedes saaks neid kokku õite mitukümmend. Siit võib välja lugeda,
et versiooniks on XHTML 1.0 Transitional – ehk siis 1999ndast ligi kümnendi püsinud
kirjapanekuvorming.
Edasi hakkavad HTMLi käsklused. Märkide < ja > vahel olevate sõnadega antakse teada, mis
nüüd tulemas on. Esimese elemendi nimeks ongi html, ehk siis lehitsejale teadmiseks, et
tulemas htmli dokument. Juuresolev atribuut xmlns (xml namespace) näitab, millise
nimeruumiga elemendid seotud on. XHTML 1.0 Transitionali puhul siis järgnev nimi:
http://www.w3.org/1999/xhtml . Näeb välja väga hüperlingi moodi. Aga selline kuju võeti
nimeruumide nimetuste puhul ette vaid selleks, et kogemata ei satuks mitmel eri firmal ette
samanimelist nimeruumi. Kas vastavale lehele ka selgitav tekst andmete kohta pannakse – see
on juba vajaduse ja viisakuse küsimus.
Veebilehe kood koosneb kahest suhteliselt iseseisvast osast. Ühe nimeks head (päis) ning
teiseks body (sisu). Esimesse neist pannakse pealkiri (title) ning soovi korral lisaks igasugu
muud andmed lehe kohta, mida otse näidata ei soovita. Näiteks lehe autor, märksõnad,
kooditabel jm.
Minu esimene leht
Sisu osa lehel on kõigile nähtav. Lihtsamal juhul ongi siin paljas tervitav tekst, aga eks hiljem
saab kujundust keerukamaks hakata muutma.
Tere maailm
Nagu näha, siis kõik alustavad elemendid peavad kusagil ka lõppema. Lehe sisuosa tähistav
element body lõpeb lõpetava tähisega ning dokumendi enese