Sisukord:
|
* MSSQL 2005 - exceptionid / märkused
Lõpuks tuli ka see aeg, kus MSSQL sai omale normaalse veahalduse. Varem erilisi päästerõngaid nagu polnud (@@error muutuja siiani olemas). Süntaks siis: begin try
raiserror 99999 'Nii tekitame vea'
end try
begin catch
select error_number() as veanumber,
-- Märkus: error_procedure kasulik muutuja, hea debugimisel, et teada saada süsteemselt, kus protseduuris viga tekkis.
error_procedure() as vproc,
error_line() as rida,
error_message() as sonum
end catch
Märkused: kahjuks MSSQL 2005 natuke tekitas ka pettumust, indeksi max suurus(varchar/char) on 900 märki,nvarchar(450).
Südamest lootsin, et ehk jõutakse vähemalt 2048 märgini. Ebameeldiv on see, et varchar(1024) saab indeksit luua,
aga kui sinna tabelisse lisada kirje,
mis on >=991 märki, siis seda kirjet ei sisestata. Üks muudatus samuti, et enam "ametlikult" text/ntext muutujat ei toetata, nende asemel nüüd: varchar(MAX), nvarchar(MAX). Kuna pidevalt suur kiire, siis hetkel pole aega formateerida, siia panen mõne vajaliku SQL koodi: Asjad, mida minult küsitud: * Kuidas teada saada kohaliku aja erinevust GMT ajast tundides? select datediff(hh,GETUTCDATE(),getdate()) * Kuidas konverteerida ajaformaat stringi kujule dd.mm.yyyy või (odbc str )yyyy-mm-dd ? select convert(char(10),getdate(),120),convert(char(10),getdate(),104) * CTE, mis loom see on veel Kõige lihtsam oleks vist öelda, ajutine resulset, mille skoop kehtib kuni järgmise SQL lauseni(kaasa arvatud). Näide: declare @at table(id int identity(1,1),sona varchar(255))
insert into @at(sona)
values('maasika')
insert into @at(sona)
values('jäätis')
insert into @at(sona)
values('on')
insert into @at(sona)
values('hea')
WITH ajutineRez(sona) as
(
select sona
from @at
where sona in ('jäätis','hea')
)
select * from ajutineRez
-- select * from ajutineRez näiteks nüüd uuesti sqli välja kutsudes saame vea, et tabelit pole, sest skoop lõppes.
Milleks, CTE kasulik on...parim suurte päringute tükeldamiseks. * COALESCE ja ISNULL, mis on nende erinevus
Näiteks: select 'Töö telefon : '+telefon+'; Mobiil: '+mobiil+';' as telefonid
from mingitabel
Nüüd, kui telefon või mobiil sisaldab NULL väärtust, oleks selle kirjet kogu väärtuseks samuti NULL. select 'Töö telefon : '+isnull(telefon,'puudub')+'; Mobiil: '+isnull(mobiil,'puudub')+';' as telefonid
from mingitabel
Nüüd, kui telefon puudub, on ka asendatakse NULL väärtus "puudub"; siin pole vaja ka enam karta, et kirje annaks tulemuseks NULL. Coalesce't antud juhul pole mõtet kasutada, kuigi sisult teeb ta sama. Coalesce näited: declare @muutuja varchar(15)
declare @muutuja1 varchar(15)
declare @muutuja2 varchar(15)
set @muutuja=null
set @muutuja1=null
set @muutuja2='OK'
select COALESCE(@muutuja,@muutuja1,@muutuja2)
Põhimõtteliselt käib antud juhul coalesce läbi kõik muutujad, kuni leiab esimese muutuja, mis ei ole tühi (NULL). Kui oleksime soovinud, et telefoni numbri päring oleks tagastanud ühe numbritest, mis polnud tühi, tuleks teha järgnevalt: select 'Telefon : '+coalesce(telefon,mobiil,'puudub') as telefon
from mingitabel
:::Tutvustan ka lühidalt mõningaid ühenduse spetsiifilisi SQL direktiive. SET ANSI_NULLS (ON|OFF) direktiiv
Loome esmalt ajutise tabeli create table #ajutine(i int null)
insert into #ajutine(i) values (123)
insert into #ajutine(i) values (null)
SET ANSI_NULLS ON -- vaikimisi (jälgitakse SQL-92 standardit)
select * from #ajutine where i=NULL
select * from #ajutine where i<>NULL
Tulemuseks mõlemal juhul 0 kirjet, mis ongi õige (määramatus ei võrdu kunagi määramatusega !)
SET ANSI_NULLS off select * from #ajutine where i=NULL
select * from #ajutine where i<>NULL
Nüüd saate mõlemal juhul tulemuseks 1 kirje. Esimesel juhul kirje väärtuseks NULL ja teisel juhul 123. MÄRKUS ! Igasugune standartidest kõrvale kaldumine võib tuua kaasa ebameeldivaid tagajärgi ! SET ANSI_NULLS off kasutada ainult väga äärmuslikes olukordades.Kasutage ikka süntaksit : select * from #ajutine where i IS NULL -- ehk on määramatus /väli tühi
select * from #ajutine where i IS NOT NULL -- ei sisalda määramatust
Kui soovite, et päringusse ikka tuleks ka NULL väärtusega välja; kasutage järgmis lahendust. select * from #ajutine where isnull(i,0)<>100000Kui Teil peaks tekkima olukord, kus võrreldakse kahte NULL väärtusega välja, kasutage lahendust select * from #ajutine where isnull(i,0)=isnull(i,0) SET ANSI_WARNINGS (ON|OFF) Näite illustreerimiseks loome esmalt ajutise tabeli.
create table #test(i int)
insert into #test(i) values (5) insert into #test(i) values (145) insert into #test(i) values (null)
Vaikimisi on ANSI_WARNINGS peal. Nüüd, kui summeerime kõik I väärtused saame hoiatuse:
Warning: Null value is eliminated by an aggregate or other SET operation.
Sama viga tekib ka, kui update lauses liidate, lahutate "I" muutujat.
update #test set i=i-1 või update #test set i=i+1 Kui Te ei soovi, et antud hoiatus tekiks, seadke SET ANSI_WARNINGS OFF
SET ANSI_PADDING (ON|OFF) -- vaike-väärtuseks soovitatakse SET ANSI_PADDING ON See tähendab, et kui toimub insert :
if object_id("tempdb..#test")!=0 drop table #test
create table #test
(
numbridv varchar(10),
numbridc char(10)
)
SET ANSI_PADDING OFF
-- Sooritage kaks päringut, kus ühel juhul ON ja teisel OFF ning jälgige päringu tulemust
insert into #test(numbridv,numbridc) values('TEST ','TEST')
select '['+numbridv+']','['+numbridc+']'
from #test
::: Välja väärtused muutujasse
Mõnikord on olukordi, kus tuleb terve välja väärtused omistada muutujale (ntx koma separated list).
Tihti olen kohanud, et programmeerijad teostavad selle operatsiooni cursori abil,
kuid on olemas palju lihtsam meetod.
CREATE TABLE laused (
[id] [int] IDENTITY (1, 1) NOT NULL ,
[laused] [varchar] (25) NOT NULL ,
CONSTRAINT [PK_laused] PRIMARY KEY CLUSTERED
(
[id]
) ON [PRIMARY]
)
Set nocount on -- ütleme serverile, et ei tagastaks meile infot, mitu kirjet sisestati
insert into laused(laused) values('mina')
insert into laused(laused) values('olen')
insert into laused(laused) values('inimene')
declare @muutuja varchar(8000)
select @muutuja = COALESCE(@muutuja,'')+laused.laused +';'
from laused
where laused.laused is not null
select @muutuja
-- Väljund: mina;olen;inimene;
::: MSSQL server ja ajutised tabelid Ajutisi tabeleid on kahte tüüpi:
Nii, nüüd nende erinevused. Ühe trelliga tabelit on võimalik kasutada ainult loodud ühenduse piires.
Kahe trelliga tabeline saavad ka ligi teised kasutajad (globaalne ajutine tabel)!
Ajutised tabelid on alati soovitav ära kustutada, kui Te enam neid ei kasuta. Antud reeglit peaks järgima eriti siis, kui ajutised tabelid sisaldavad palju kirjeid, sest muidu nad raiskavad mõtetult SQL serveri kettaressursi. Ajutised tabelid kustuvad serverist ka siis, kui lõpetatakse ühendus, mille käigus nad loodi.
St. ka kahe trelliga tabel kustub, enam teised kasutajad seda ei näe.
Näited, ajutiste tabelite loomiseks: 1. Kirjeldame ära tabeli struktuurid:
create table #test1
(
id int not null,
automark varchar(45) not null
)
või siis create table ##test1
(
id int not null,
automark varchar(45) not null
)
On veel üks võimalus, kuidas ajutist tabelit luua, see tipa "laisk" meetod.
(teeme uue ajutise tabeli, kasutades olemas-oleva tabeli struktuuri)
select id,automark
into #test1 või vajadusel into ##test1
from koikautod
where 0=1
Märkus: ajutises tabelis jääb väli Id ikka Identity tüüpi (kuna põhitabelis on identity), kuid ta on kaotanud primary key constraindi ! Soovitav ka ajutise tabeli ID väli muuta Primary Key'ks. Kuid tegelikkuses, me ei vajaks ajutises tabelis IDENTITY tüüpi ID välja, meile piisaks "tavaliselt" ID väljast. select cast(null as int) as id,automark
into #test1 või vajadusel into ##test1
from koikautod
where 0=1
ALTER TABLE #test1 ADD PRIMARY KEY(id) Nüüd liigume tutvustava osa juurest reaalsema näite poole; järgnevalt üritamegi kasutada ajutisi tabeleid, et päringut kiirendada !
MIKS üldse kasutada ajutisi tabeleid; näiteks on kolm tabelit; esimeses on 9 miljonit kirjet, teises 5 miljonit,kolmandas ka 7 miljonit.
Näiteks tahaksime teada isikuid, kelle autoks on (VW või BMW) ning rahvus oleks türklane. select k.automark,i.isikunimi,e.pusiaadress
from isikud i,koikautod k,elukoht e
where (i.rahvus="türklane")
and (k.omanikuId=i.id) -- MÄRKUS: kui siin kasutada OR lauset ((k.automark="VW") or (k.automark="BMW")),
-- oleks päring veel aeglasem. OR võrdlused suht aeglased !!!
and (k.automark in ("VW","BMW"))
and (e.isikuId=i.id)
Antud päring optimiseeritud kujul: create table #turklased
(
id int not null primary key,
isikunimi varchar(85) not null,
)
ID väljad tähistame, kui primary key; teisisõnu unikaalsust tagav constraint, mille peale MSSQL server loob ka automaatselt unikaalse indeksi. create table #autod
(
omanikuId int not null,
automark varchar(45) not null
)
Kuna joinis kasutame omanikuId välja, tekitame antud välja peale ka indeksi
CREATE INDEX omanikId ON #autod(omanikuId) Nüüd meie optimiseeritud päring: insert into #turklased(id,isikunimi)
select id,isikunimi
from isikud
-- PS. üks trikk, lisades parameetri from isikud (NOLOCK), võimaldab meil päringut veelgi kiirendada.
-- nö. "Dirty read"; me ei saa ootama teiste poolt muutmiseks lukustatud kirjete taha, vaid loeme julmalt
-- kõik kirjed. Seda võimalust kasutada vaid siis, kui pole tegemist aeg-kriitiliste andmetega !
where (rahvus="türklane")
order by id
insert into #autod(omanikuId,automark)
select omanikuId,automark
from koikautod (nolock) -- !!
where (automark in ("VW","BMW"))
order by id
select k.automark,i.isikunimi,e.elukoht
from #turklased a,#autod k,elukohad e (nolock)
where (k.omanikuId=a.id)
and (e.isikuId=a.id)
order by e.elukoht,i.isikunimi
Hea tava on ise ajutised tabelid kustutada (eriti ühe trelliga tabelid) ! Nipp, mida mina isiklikult kasutan, enne kustutamist ikka kontrollin, kas tabel ka reaalselt olemas.
Järsku mingi insert veadas alt ning ajutist tabelit ei loodud...:(
if object_id("tempdb..#autod")!=0 drop table #autod
if object_id("tempdb..#turklased")!=0 drop table #turklased
Antud näites kasutasin ajutisi tabeleid päringu tükeldamiseks. Selline tükeldus annab tihti ajalist võitu !
Kui joinis on ohtralt staatilisi tabeleid, jääb päring aeglaseks.
Lõpetuseks üks täiendav võimalus ajutise tabeli loomiseks, on kasutada TABEL tüüpi muutujat. declare @tabelmuutuja table
(id int primary key,midagiveel varchar(255))
Kahjuks neile tabelitele ei saa indeksit genereerida (va. primary key) ning protseduurist saadud selecti pole võimalik sinna tabelisse inseritida.
Tabel tüüpi muutuja võib olla näiteks funktsiooni väljundiks,
mis on mõnikord ülimalt mugav võimalus. Seda tüüpi muutuja, aga ei saa olla protseduuri/ega funktsiooni sisendiks.
Natuke spetsiifikat: See lahendus muutuja puhul ei tööta ! update @tabelmuutuja
set midagiveel='B'
from @tabelmuutuja tbl
where tbl.midagiveel='A'
and @tabelmuutuja.id=tbl2.id
Antud lahendus töötab update tbl
set midagiveel='B'
from @tabelmuutuja tbl,@tabelmuutuja tbl2
where tbl2.midagiveel='A'
and tbl.id=tbl2.id
SQL Table tüübi muutujad kustuvad automaatselt skoobist väljumise hetkel. Lõpetuseks mõned märkused
:::MSSQL serveri veahaldus Kahjuks MSSQL server ei paku vea haldamiseks exception handlereid;
kuid järgmises MSSQL serveris on exception handling täitsa olemas (versioon nimega Yukon).
MSSQL serveris saab vea tõstatada: RAISERROR 50000 'Tekkis viga' sellist tüüpi vea tõstatamist kasutan ka mina enamasti. Esimene parameeter on veakood, mis soovituste järgi algaks alates 50000, kuna allpool on MSSQL enda veakoodid. Raiserrorit saab kasutada ka järgmiselt: RAISERROR('Tekkis viga', 16, 1)
* Esimene parameeter on siis, kas Teie veasõnum või eeldevalt defineeritud veakood(sp_addmessage);
seda viimast varianti kasutatakse suht harva.
* Teine parameeter näitab vea tõsiduse astet: võib-olla vahemikus 0-16;
kui on vahemik 0-10, siis viga ei loeta eriti tõsiseks pigem informatiivseks
ja süsteemsetmuutujat @@ERROR ei väärtustata.
Enamalt jaolt tõstatatakse viga tõsidusega 16; 17-18 on süsteemsed vead.
Tõsiduse nivood 19-25 saavad kasutada vaid süsteemiadministraatorid. Kui tuleb viga nivooga alates 20
ühendus kliendiga katkestatakse ja viga kirjutatakse MSSQL serveri logisse.
Näiteks andmebaasi administraator võib teha protseduuri:
create proc kliendiYhendusKatkestada
as
raiserror('Peale selle veateate saamist katkeb ühendus serveriga',20,1)
WITH LOG
* Kolmas parameeter on veakoodi täiustav informatiivne väli, väärtus saab olla vahemikus 1..127
Täiendavat infot vigade nivoodest saab siit: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/trblsql/tr_errorformats_0tpv.asp
Näited: Eelnevalt, kui lähme näidete juurde, pean mainima, et kõik vead, mis on nivooga >10 jõuavad exceptionina kohale Teie programmi. Kas kasutate ADO,JDBC,ODBC jne...vigaNäiteks: raiserror (17820,10,123)
-- Antud näites kutsume välja informatiivse vea (nivoo 0;10),
-- 17820 - on süsteemis kirjeldatud viga "Invalid data type parameter."
-- Kõiki eeldefineeritud vigu saab vaadata tabelist: select * from master.dbo.sysmessages
või
raiserror ('Ei tohi andmeid töödelda',10,123)
Antud nivooga vead võrduks print käsuga. Neid vigu ei kuva ka Teie andmebaasi lõpprakendused, kui nivoo tõstate üle 10, on vead olemas.
Puuduseks on nivoodel 0-10 see, et SQLi süsteemset muutujat @@ERROR ei väärtustata,
mille kontrollimisel saaksime otsustada, kas jätkame oma SQL loogikaga.
Väike trikk, mis võimaldab meil kasutada nö silent exceptioni create proc vaikeviga
as
raiserror ('Meil tekkis väike viga',10,123)
with seterror -- @@ERROR muutujale omistatakse väärtus 50000
-- Nüüd kutsume protseduuri välja
exec vaikeviga
if (@@ERROR=0) print 'update ... midagi' else print 'Eelmises SQL käsus ilmnes viga'
@@ERROR muutujat kasutamisel tuleb kindlasti jälgida, et antud muutuja väärtust küsitakse kohe näiteks peale protseduuri käivitamist, update lauset, inserti jne declare @mingiMuutuja int
exec vaikeviga
set @mingiMuutuja=123
-- nüüd saab @@ERROR muutuja väärtuseks 0, iga uue SQL käsu järgi, veamuutuja väärtust uuendatakse.
-- Selliseid vigu võib üsna tihti kohata
if (@@ERROR=0) print 'update ... midagi' else print 'Eelmises SQL käsus ilmnes viga'
*** SQL vigade vältimiseks võiks kasutada järgnevaid süsteemseid muutujaid: @@ROWCOUNT - annab töödeldud(INSERT,UPDATE)/selekteeritud (SELECT) kirjete arvu (viimase SQL batchi tulemus) Näiteks: select * from mingitabel
if (@@ROWCOUNT=0) print 'päring oli tühi'
@@TRANCOUNT - juhul, kui meil on vaja transaktioon tagasi tõmmata, siis võiks eelnevalt kontrollida, kas transaktioon üldse töötab või on juba mõni triger eelnevalt rollback ära teinud. if @@TRANCOUNT!=0 commit tran või rollback tran Kui soovite kõiki transaktioone kinnitada / või tagasi tõmmata: while @@trancount>0
rollback tran või commit tran
Kursori operatsioonide puhul on soovitatav kontrollida tema olemasolu
-- nö mina kasutan tavaliselt kursoreid muutuja kujul, nimelised kursorid mulle ei meeldi
if cursor_status('variable', '@sinucursormuutuja')>=0
begin
fetch next from @sinucursormuutuja into @mingidmuutujad
-- Näitab, kas cursor tagastas andmeid 0 - siis kõik korras
while @@fetch_status = 0
begin
fetch next from @sinucursormuutuja into @mingidmuutujad
end
close @sinucursormuutuja
deallocate @sinucursormuutuja
end
...SQL protseduuris võiks veahalduse lahendada järgmise loogika kohaselt... create proc test(@parameeter int)
as
begin
declare @olivigu int
set @olivigu=0 -- staatus, et pole probleemi
...teen midagi...
set @olivigu=@@ERROR
if isnull(@olivigu,0)!=0
goto lopp
...tegevused...
select top 0 *
from andmed
if @@rowcount=0
begin
set @olivigu=55009 -- vajadusel Teie poolt defineeritud veakood
goto lopp
end
lopp: -- goto label
...vajadusel kustutan ajutised tabelid, sulgen kursorid, teen rollbacki jne...
return @olivigu -- märkus, return lõpetab protseduuri töö
end
:::Lühidalt funktsioonidest Funktsioonid on tekkisid MSSQL-2000 -- Andmetüübid, mis ei või olla sisendiks : timestamp, table(muutuja) create function test1(@b int,@a smallint) -- Defineerime väljundi tüübi returns int -- andmetüübid, mida ei saa olla väljundiks timestamp, image, text, ntext, cursor. as begin declare @c int set @c=@a+@b return @c end Funktsiooni väljakutse: select dbo.test1(5,6) -- märkus: funktsiooni ees peate ära märkima, kes funktsiooni looja. -- Enamasti dbo - ehk admin; väljakutse select test(5,6) annaks vea ! Funktsioone võime rakendada paljudes kohtades, viewd, selectid, where tingimused jne select a,b from suvalinetabel where dbo.test1(a,b)>555 create table test3 ( a int default 1, b int default 2, c as (dbo.test1(a,b)) -- kasutame funktsiooni calculated field tüübi jaoks ) Funktsioonide puudused (tuginedes enda kogemustele):
Nüüd tuli küll hulgi miinuseid, kuid funktsioon on mõningates olukordades asendamatu abimees:) Toon veel mõningaid näiteid funktsiooni kasutusvõimalustest: -- Antud funktsiooni väljundiks on tabel create function test2() -- Defineerime väljundi tüübi returns @proov table (id int IDENTITY(1,1) not null primary key, sona varchar(50) not null) as begin declare @id int insert into @proov(sona) values('Võitleva Sõna ') insert into @proov(sona) values('baarist') set @id= scope_identity() update @proov set sona=sona+' saab juua ' where id=@id return end -- Funktsiooni testimine select a.sona from dbo.test2() a
Täiendav märkus !
::: Mõned kasulikud SQL funktsioonid
select SUSER_SNAME() -- annab SQLserveri kasutajanime select ISDATE('07.12.2005')-- kontrollib, kas kuupäev on korrektne (SQLserveri mõistes) select NEWID() -- genereerib unikaalse guid stringi select NULLIF(0,1) -- kontrollib, kas kaks väärtust võrdsed, kuid jah, siis tulemus NULL select ISNUMERIC('51.2') -- kontrollib, kas arv korrektne, ka reaalarv .255 on korrektne select YEAR(getdate()) -- eraldab kuupäevast aasta osa select MONTH(getdate()) -- eraldab kuupäevast kuu osa select DAY(getdate()) -- eraldab kuupäeva select DATEADD(day,5,getdate()) -- ntx. lisab hetke ajale +5 päeva, võib ka neg. number olla -5 select DATEPART(mi,getdate()) -- ntx. eraldab ajaühikust minutite osa select DATENAME(dw,getdate()) -- ütleb, mis päev on select DATENAME(mm,getdate()) -- ütleb kuu nime /* muudab kuupäev char kujule formaadis mm.dd.yyyy, 3 parameeter converdi puhul ütleb, mis kujule kuupäev teisendada */ select convert(char(10),getdate(),104) Kunagi küsiti minult, kuidas kontrollida, et kas ma tohin mingist tabelist selecti teha või kas mul on insert õigus... 0x1 SELECT ALL 0x2 UPDATE ALL 0x4 REFERENCES ALL 0x8 INSERT 0x10 DELETE 0x20 EXECUTE (procedures only) 0x1000 SELECT ANY (at least one column) 0x2000 UPDATE ANY 0x4000 REFERENCES ANY Ülejäänud õiguste väärtused saate SQL server books online abil. if PERMISSIONS (object_id('teie tabel'))&0x2000=0x2000
select 'võid küll update teha'
Soovitan ka uurida protseduuri sp_helprotect |