Nie zabłądzisz na manowce, gdy pod ręką masz gotowce – część 3
Zgodnie z zapowiedzią pora na kolejną garść „gotowców” – skryptów, które mogą się przydać.
Na początek sposób na przetwarzania prostych formuł (jedynie dodawanie i odejmowanie). Załóżmy że mamy takie wyrażenie: A1+A2-A3, a dodatkowo wartości jego składników przechowywane są w tabeli:
if object_id('tempdb..#Dane') is not null drop table #Dane; create table #Dane (Symbol varchar(10), Wartość int); insert into #Dane (Wartość, Symbol) values (10, 'A1'); insert into #Dane (Wartość, Symbol) values (3, 'A2'); insert into #Dane (Wartość, Symbol) values (2, 'A3');
formułę można przetworzyć i obliczyć następująco:
declare @xml xml, @sign int, @expression varchar(1024); set @expression = 'A1+A2-A3'; -- formuła -- przypadek, gdy wyrażenie zaczyna się od znaku set @sign = case Left(@expression, 1) when '+' then 1 when '-' then -1 else 0 end; -- usunięcie tego znaku, aby nie przeszkadzał if @sign <> 0 set @expression = SubString(@expression, 2, LEN(@expression)); -- obsłużenie plusów set @expression = REPLACE(@expression, '+', '"/><r d="1" s="'); -- obsłużenie minusów set @expression = REPLACE(@expression, '-', '"/><r d="-1" s="'); -- obsłużenie początku i końca wyrażenia set @expression = '<r d="' + case @sign when -1 then '-1' else '1' end + '" s="' + @expression + '"/>'; -- przerobienie na typowany xml set @xml = '<xml>' + @expression + '</xml>'; --uzyskanie struktury pionowej select C.value('@s', 'varchar(40)') as [SkładnikFormuły], C.value('@d', 'int') as [Działanie] into #Formuła from @xml.nodes('/xml/r') T(C) -- obliczenie wyniku select sum(F.Działanie * S.Wartość) as Wynik from #Formuła F inner join #Dane S on F.SkładnikFormuły = S.Symbol
Zdarza się, że w danych pojawia się replika. Coś co powinno być w jednym wierszu, powiela się w kilku (przynajmniej dwóch). Oto sposób na wyłapanie takich przypadków, ich naprawienie i aktualizację tabel korzystających ze zreplikowanych danych. Za przykład posłuży eliminowanie z zamówień replik danych kontaktowych.
if object_id('tempdb..#zamowienia') is not null drop table #zamowienia; if object_id('tempdb..#kontakty') is not null drop table #kontakty; GO create table #zamowienia ( id int not null, adres_id int not null, adres varchar(255) null, miasto varchar(255) null, kod varchar(255) null ) insert #zamowienia (id, adres_id, adres, miasto, kod) values (1,1,'Narutowicza','Suwałki','16-400') insert #zamowienia (id, adres_id, adres, miasto, kod) values (2,1,'Narutowicza','Suwałki','16-400') insert #zamowienia (id, adres_id, adres, miasto, kod) values (3,2,'Kalinowa 2','Warszawa','01-934') insert #zamowienia (id, adres_id, adres, miasto, kod) values (4,2,'Kalinowa 23','Warszawa','01-934') insert #zamowienia (id, adres_id, adres, miasto, kod) values (5,3,'Terespolska','Tczew','23-789') insert #zamowienia (id, adres_id, adres, miasto, kod) values (6,4,'Terespolska','Tczew','23-789') select * from #zamowienia; select row_number() over (order by @@SPID) as adres_id, min(adres_id) as min_adres_id, max(adres_id) as max_adres_id, adres, miasto, kod into #kontakty from #zamowienia group by adres, miasto, kod select * from #kontakty; update Z set Z.adres_id = K.adres_id --select * from #zamowienia Z inner join #kontakty K on K.adres = Z.adres and K.miasto = Z.miasto and K.kod = Z.kod where Z.adres_id between K.min_adres_id and K.max_adres_id; select * from #zamowienia;
Warto zwrócić tutaj uwagę na pewną ciekawostkę. Użyta w jednym z zapytań funkcja row_number używa klauzuli over do ustalenia porządku numerowania wierszy, ale ponieważ owa kolejność nie jest istotna użyta została zmienna określająca numer sesji (@@SPID). W ten sposób można ponumerować wiersze nie uzależniając ich kolejności od kolejności danych, które numerują.
Następny skrypt demonstruje sposób importowania danych z bazy GUS (są to plik XML). Niestety nie wiem czy format się nie zmienił, a na stronie GUS obecnie nie można tak prosto pobrać plików, jak jeszcze w 2011 roku (albo ja po prostu nie mogę tego znaleźć). Niemniej zamieszczam i ten skrypt. Nawet jeśli okaże się nieaktualny, to i tak powinien być wystarczającym szablonem na przystosowanie do obecnego formatu.
eclare @XMLHandle int, @XML xml, @R int, @M varchar(512); select @XML = X from OpenRowSet(bulk 'D:\GUS\simc.xml', SINGLE_BLOB) as Imp(X); exec sp_xml_preparedocument @XMLHandle output, @XML; if exists(select * from sysobjects where name = 'SIMC') drop table SIMC; select * into SIMC from OpenXML(@XMLHandle, 'teryt/catalog/row', 1) with ( WOJ int 'col[@name="WOJ"]', POW int 'col[@name="POW"]', GMI varchar(3) 'col[@name="GMI"]', RODZ_GMI int 'col[@name="RODZ_GMI"]', RM varchar(3) 'col[@name="RM"]', MZ int 'col[@name="MZ"]', NAZWA varchar(100) 'col[@name="NAZWA"]', SYM varchar(10) 'col[@name="SYM"]', SYMPOD varchar(10) 'col[@name="SYMPOD"]' ); exec sp_xml_removedocument @XMLHandle; select @XML = X from OpenRowSet(bulk 'D:\GUS\ulic.xml', SINGLE_BLOB) as Imp(X); exec sp_xml_preparedocument @XMLHandle output, @XML; if exists(select * from sysobjects where name = 'ULIC') drop table ULIC; select * into ULIC from OpenXML(@XMLHandle, 'teryt/catalog/row', 1) with ( WOJ int 'col[@name="WOJ"]', POW int 'col[@name="POW"]', GMI varchar(3) 'col[@name="GMI"]', RODZ_GMI int 'col[@name="RODZ_GMI"]', SYM varchar(10) 'col[@name="SYM"]', SYM_UL varchar(10) 'col[@name="SYM_UL"]', CECHA varchar(100) 'col[@name="CECHA"]', NAZWA_1 varchar(100) 'col[@name="NAZWA_1"]', NAZWA_2 varchar(100) 'col[@name="NAZWA_2"]' ); exec sp_xml_removedocument @XMLHandle; select @XML = X from OpenRowSet(bulk 'D:\GUS\terc.xml', SINGLE_BLOB) as Imp(X); exec sp_xml_preparedocument @XMLHandle output, @XML; if exists(select * from sysobjects where name = 'TERC') drop table TERC; select * into TERC from OpenXML(@XMLHandle, 'teryt/catalog/row', 1) with ( WOJ int 'col[@name="WOJ"]', POW int 'col[@name="POW"]', GMI varchar(3) 'col[@name="GMI"]', RODZ int 'col[@name="RODZ"]', NAZWA varchar(100) 'col[@name="NAZWA"]', NAZDOD varchar(100) 'col[@name="NAZDOD"]' ); exec sp_xml_removedocument @XMLHandle;
Na koniec nie tyle przykład skryptu SQL, co sposób na uruchomienie zestawu poleceń SQL (mogą nimi być także plik – wówczas potrzebna jest inny parametr sqlcmd) z poziomu wiersza poleceń:
@echo off "C:\Program Files\Microsoft SQL Server\100\Tools\Binn\sqlcmd" -S .\SQL2008 -o .\sql.log -b -Q "select 1;" if ErrorLevel 1 goto :error "C:\Program Files\Microsoft SQL Server\100\Tools\Binn\sqlcmd" -S .\SQL2008 -o .\sql.log -b -Q "select 1 from nieistnieje" if ErrorLevel 1 goto :error "C:\Program Files\Microsoft SQL Server\100\Tools\Binn\sqlcmd" -S .\SQL2008 -o .\sql.log -b -Q "select 2;" if ErrorLevel 1 goto :error echo Wszystkie skrypty wykonano prawidłowo goto :end :error echo Wystąpił błąd :end
Użyta powyżej nazwa SQL2008 określa instancję serwera na lokalnym komputerze (trzeba to zmienić adekwatnie do nazwy posiadanego serwera). W powyższym przykładzie wykonają się tylko pierwsze dwa skrypty, trzeci już nie, co demonstruje sposób obsługi błędów, które mogą wystąpić w takim zestawie skryptów.
Słów kilka na temat konstrukcji:
if ErrorLevel 1 goto :error
Otóż sqlcmd, jeśli coś pójdzie nie tak, zwraca do systemu kod powrotny (nazywane też poziomem błędu). Powyższa konstrukcja bada, czy kod ten jest równy 1 lub wyższy (ważne: wyższy) i wówczas przerywa działanie wsadu nie uruchamiając tym samym kolejnych skryptów.