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.