Nie zabłądzisz na manowce, gdy pod ręką masz gotowce – część 2
Niedawno opublikowałem zestaw gotowych skryptów SQL, które mogą się przydać w konkretnych sytuacjach lub posłużyć jako wzorzec w innych przypadkach. Ponieważ – w ramach porządków – znowu uzbierało mi się kilka takich skryptów – i je postanowiłem zaprezentować. Tym sposobem samoistnie powstał mi cykl ;).
Na początek przykład sklejenia tekstowej zawartości kolumny w inny sposób niż podany poprzednio (bez użycia XML).
declare @aggregate varchar(max) = ''; if object_id('tempdb..#content') is not null drop table #content; create table #content (content varchar(50)); insert into #content values ('zero'), ('jeden'), ('dwa'), ('trzy'), ('cztery'), ('pięć'), ('sześć'), ('siedem'), ('osiem'), ('dziewięć'); select @aggregate = @aggregate + content + ' ' from #content; select RTrim(@aggregate) as result;
Zdarza się, że dobrze byłoby przechować w tabeli zestaw danych zwracany przez procedurę składowaną. Załóżmy, że jako przykład posłuży trywialna procedura zwracająca listę obiektów bazy danych, czyli:
create procedure [dbo].[ResultSet] as begin select * from sys.objects; return; end;
Aby można było przekazać zwracany przez procedurę zestaw danych do tabeli, taka tabela musi już istnieć i mieć strukturę zgodną ze zwracanym zestawem danych. Oto stosowny skrypt:
if object_id('tempdb..#Tymczasowa') is not null drop table #Tymczasowa; select top 0 * into #Tymczasowa from sys.objects; -- tabela na wynik procedury insert into #Tymczasowa exec dbo.ResultSet; -- przekazanie wyniku do tabeli
Oczywiście w tym przypadku jest dość łatwo przygotować tabelę, bo wystarczy skorzystać z już istniejącej. W innych przypadkach trzeba będzie taką tabelę przygotować własnoręcznie. Tak np. może być, jeśli zechcemy przechować wyniki procedury systemowej sp_who2.
create table #Sessions ( SPID int, [Status] varchar(20), [Login] varchar(50), HostName varchar(100), BlkBy varchar(100), DBName varchar(100), Command varchar(100), CPUTime int, DiskIO int, LastBatch varchar(20), ProgramName varchar(100), SPID2 int, REQUESTID int ); insert into #Sessions exec sp_who2;
Trochę to uciążliwe, nieprawdaż? Dodatkowo, jeśli procedura będzie zwracała dwa zestawy wyników:
create procedure [dbo].[ResultSet] as begin select * from sys.objects; select * from sys.databases; return; end;
wówczas zaprezentowane skrypty przestaną działać.
Na szczęście zarówno wspomnianą uciążliwość, jak i problem wielu wyników, można zniwelować – niemniej niesie to pewne koszty w innych obszarach.
Co zatem uczynić, aby nie tworzyć tabeli przed wykonaniem procedury i uzyskać ją automatycznie, a jednocześnie nie obawiać się wielu zestawów wyników i po prostu pobierać pierwszy z nich? Należy użyć funkcji OpenDataSource:
select * into #Result from OpenRowset('SQLNCLI', 'Server=.\SQLEXPRESS2K8R2;Trusted_Connection=yes;', 'exec Test.dbo.ResultSet;');
Dodatkowe koszty o których była mowa, to:
- konieczność określenia nazwy serwera (chyba, że jest to instancja domyślna, wówczas nazwa to (local))
- konieczność określenia bazy danych (w ostatnim literale widać poprzedzenie wywołania procedury, oprócz nazwy schematu, także nazwą bazy) – niestety nie można tego „skleić” w żaden automatyczny sposób, ani użyć zmiennej, bo OpenDataSource nie dopuszcza zmiennych w swoich parametrach
- serwer SQL musi mieć włączone wykonywanie doraźnie rozprowadzanych zapytań (Ad Hoc Distributed Queries)
Skoro już mowa o OpenDataSource, to warto wspomnieć o funkcji OpenRowSet. Ma ona wiele zastosowań, ale jedno jest szczególnie interesujące. Otóż za jej pomocą można dokonać eksportu danych z poziomu SQL Serwera. Najłatwiej zrobić to do pliku tekstowego rozdzielanego przecinkami, inne formaty wymagają trochę więcej zabiegów. Oto jak wygląda taki eksport:
if object_id('tempdb..#content') is not null drop table #content; create table #content (content varchar(50)); insert into #content values ('zero'), ('jeden'), ('dwa'), ('trzy'), ('cztery'), ('pięć'), ('sześć'), ('siedem'), ('osiem'), ('dziewięć'); insert into OpenRowSet('Microsoft.Jet.OLEDB.4.0', 'Text;Database=C:\FolderEksportu\', 'select * from [eksport#csv]') select * from #content;
Niestety nie jest tu aż tak różowo, albowiem plik eksport.csv musi istnieć na dysku. Dodatkowo i w tym przypadku serwer SQL musi mieć włączone wykonywanie wykonywanie doraźnie rozprowadzanych zapytań (Ad Hoc Distributed Queries). Ale, można zrobić rzecz następującą:
EXEC master..xp_cmdshell 'copy /Y nul C:\FolderEksportu\eksport.csv';
i od tego momentu plik będzie istniał. Niemniej nadal trzeba ponieść dodatkowe koszty, w tym wypadku konieczne jest włączenie opcji xp_cmdshell, co obniża bezpieczeństwo serwera SQL.
Na dzisiaj to wszystkie „gotowce”, jakie chciałem zaprezentować, ale z pewnością nie wszystkie, które w ogóle zaprezentować zamierzam. Należy zatem oczekiwać kolejnych części tego nieoczekiwanego cyklu :).