[PL] Skryptowanie w SQL Server 2008 – Obiekty proceduralne
Pilot
Umiejętność korzystania z metadanych w celu skryptowania obiektów w bazach danych (tabel, procedur, itd.) przydaje się w życiu administratora i programisty baz danych. Sam wykorzystuję skryptowanie do cyklicznego tworzenia kopii kodu T-SQL obiektów w bazach danych (na wypadek, gdyby akurat okazało się, że w bazie uszkodzeniu uległ któryś z obiektów przechowujących metadane). Zapytasz, czemu nie użyć do tego mechanizmu skryptowania oferowanego przez Management Studio? Otóż skryptowanie z użyciem własnego kodu T-SQL daje mi więcej swobody, a ponadto skryptowanie w Management Studio ma swoje wady (patrz tutaj: [EN] SQL Server – Don’t (sp_)rename your database objects) i na dodatek często występują spore problemy w przypadku, gdy baza danych zawiera sporo obiektów (jakoś listowanie pokaźnej listy obiektów nie wychodzi narzędziu Management Studio – często aplikacja się zawiesza albo odpowiada wyjątkiem klasy OutOfMemoryException – patrz tutaj: Dziennik pokładowy MSSQL 2008 (wpis 0×0011) – Problem z Generate Script…).
Dlatego postanowiłem napocząć na blogu nową serię wpisów poświęconych tworzeniu własnego kodu T-SQL do skryptowania. Zaczynam od skryptowania obiektów proceduralnych – funkcji, procedur, widoków i wyzwalaczy. Potem spróbuję pokazać, jak można skryptować typy danych użytkownika, więzy, obiekty CLR i tabele. Tam, gdzie ilość kodu nie będzie wykraczała poza możliwości percepcji, będę się starał pokazać pełne rozwiązanie, ale na przykład w przypadku tabel już wiem, że nie będę podawał pełnego rozwiązania, a tylko podsunę kawałek kodu i parę pomysłów na dobry początek :-)
Kod będę tworzył z myślą o skryptowaniu w SQL Server 2008 i SQL Server 2008 R2. Przy odrobinie wysiłku kod będzie do zaadoptowania na potrzeby SQL Server 2005.
Funkcje, procedury, widoki
Zadanie: zeskryptować wszystkie funkcje, procedury i widoki w wybranej bazie danych.
Rozwiązanie:
USE AdventureWorks2008R2; GO SET NOCOUNT ON; DECLARE @SQL TABLE ( LineId int IDENTITY(1,1) NOT NULL PRIMARY KEY, Line nvarchar(4000) NOT NULL ); DECLARE @schema_name sysname, @object_name sysname, @type_desc nvarchar(60), @type char(2), @definition nvarchar(max), @pindex int; DECLARE CursorObjects CURSOR LOCAL STATIC READ_ONLY FORWARD_ONLY FOR SELECT SCHEMA_NAME(o.[schema_id]), o.name, o.type_desc COLLATE database_default, o.type COLLATE database_default, m.definition FROM sys.objects AS o INNER JOIN sys.sql_modules AS m ON m.[object_id] = o.[object_id] WHERE o.type IN ('FN', 'IF', 'P', 'TF', 'V') AND o.is_ms_shipped = 0 ORDER BY SCHEMA_NAME(o.[schema_id]), o.name; OPEN CursorObjects; FETCH NEXT FROM CursorObjects INTO @schema_name, @object_name, @type_desc, @type, @definition; WHILE @@FETCH_STATUS = 0 BEGIN SET @pindex = 0; -- generujemy PRINT INSERT INTO @SQL (Line) SELECT 'PRINT N''-- ' + @type_desc + ': ' + REPLACE( QUOTENAME(@schema_name) + '.' + QUOTENAME(@object_name), '''', '''''' ) + ''''; IF @definition IS NOT NULL BEGIN -- generujemy DROP INSERT INTO @SQL (Line) SELECT 'IF OBJECT_ID(N''' + REPLACE( QUOTENAME(@schema_name) + '.' + QUOTENAME(@object_name), '''', '''''' ) + ''', N''' + (@type) + ''') IS NOT NULL'; INSERT INTO @SQL (Line) SELECT ' DROP ' + CASE WHEN @type = 'P' THEN 'PROCEDURE' WHEN @type IN ('FN', 'IF', 'TF') THEN 'FUNCTION' ELSE 'VIEW' END + ' ' + QUOTENAME(@schema_name) + '.' + QUOTENAME(@object_name) + ';'; -- poprzedzamy definicję słowem GO INSERT INTO @SQL (Line) SELECT 'GO'; -- kroimy definicję na poszczególne wiersze SET @pindex = PATINDEX('%' + CHAR(13) + CHAR(10) + '%', @definition); WHILE @pindex > 0 BEGIN INSERT INTO @SQL(Line) SELECT REPLACE(SUBSTRING(@definition, 1, @pindex), CHAR(13), ''); SET @definition = SUBSTRING(@definition, @pindex + 2, LEN(@definition)); SET @pindex = PATINDEX('%' + CHAR(13) + CHAR(10) +'%', @definition); END; END; ELSE BEGIN INSERT INTO @SQL (Line) SELECT 'PRINT N''-- *** This object is encrypted ***'''; END; -- kończymy definicję słowem GO INSERT INTO @SQL (Line) SELECT 'GO'; FETCH NEXT FROM CursorObjects INTO @schema_name, @object_name, @type_desc, @type, @definition; END; CLOSE CursorObjects; DEALLOCATE CursorObjects; SELECT Line AS ' ' FROM @SQL ORDER BY LineId; GO
Fragment wyniku:
Uwagi: w Management Studio ustaw Results to Text, sugeruję też zwiększenie maksymalnej długości tekstu w jednej kolumnie zwracanej w wynikach zapytań (w menu głównym kliknij Tools – Options – Query Results – SQL Server – Results to Text i w polu Maximum number of characters displayed in each column wpisz 8192, a następnie uruchom ponownie Management Studio).
Komentarz do kodu:
- definicje obiektów są trzymane w widoku sys.sql_modules w kolumnie definition, wyjątek stanowią obiekty z opcją WITH ENCRYPTION użytą w definicji (takie obiekty są “szyfrowane”),
- listę obiektów stworzonych przez użytkowników w bazie danych zwraca widok sys.objects (ale jest też widok sys.all_objects, w którym dodatkowo są zawarte metadane obiektów systemowych),
- fragmenty generowanego skryptu T-SQL są dodawane w kursorze (iteracja = definicja jednego obiektu) do zmiennej tabelarycznej @SQL (linie są numerowane autonumeracją w kolumnie LineId, zakładam maksymalną długość jednej linii kodu w obiekcie na 4000 znaków),
- zapytanie filtruje obiekty wg typu – wybiera funkcje trzech rodzajów, procedury i widoki, jest też filtr odrzucający ewentualne obiekty systemowe (kolumna is_ms_shipped w widoku sys.objects),
- przed definicją każdego obiektu dorzucam kod wykrywający obecność obiektu w bazie danych i usuwający obiekt w przypadku, gdy takowy istnieje,
- kolumny type i type_desc z widoku sys.objects mają na sztywno przypisane collation, stąd użycie funkcji COLLATE (inaczej nie dałoby się sklejać tych kolumn z tekstem, gdy collation bazy jest inne niż collation wspomnianych kolumn),
- operuję zawsze dwuczłonowymi nazwami obiektów (dzięki temu nazwa jest jednoznaczna w kontekście bazy danych) – do uzyskania nazwy schematu obiektu wykorzystuję funkcję SCHEMA_NAME,
- funkcja REPLACE jest przeze mnie użyta do “wyescape’owania” apostrofów w nazwach obiektów (swoją drogą, jak ktoś używa niestandardowych identyfikatorów zawierających dziwne znaczki i/lub słowa kluczowe, sam prosi się o kłopoty),
- funkcja QUOTENAME pozwala otoczyć identyfikatory nawiasami kwadratowymi – zabezpieczam się przed niestandardowymi identyfikatorami.
Wyzwalacze na poziomie bazy
Zadanie: zeskryptować wszystkie wyzwalacze w wybranej bazie danych.
Rozwiązanie:
-- wyzwalacze DML i DDL (ON DATABASE) USE AdventureWorks2008R2; GO SET NOCOUNT ON; DECLARE @SQL TABLE ( LineId int IDENTITY(1,1) NOT NULL PRIMARY KEY, Line nvarchar(4000) NOT NULL ); DECLARE @schema_name sysname, @object_name sysname, @type_desc nvarchar(60), @parent_class int, @definition nvarchar(max), @pindex int; DECLARE CursorObjects CURSOR LOCAL STATIC READ_ONLY FORWARD_ONLY FOR SELECT OBJECT_SCHEMA_NAME(t.[object_id]), t.name, t.type_desc COLLATE database_default, t.parent_class, m.definition FROM sys.triggers AS t INNER JOIN sys.sql_modules AS m ON m.[object_id] = t.[object_id] WHERE t.is_ms_shipped = 0 ORDER BY OBJECT_SCHEMA_NAME(t.[object_id]), t.name; OPEN CursorObjects; FETCH NEXT FROM CursorObjects INTO @schema_name, @object_name, @type_desc, @parent_class, @definition; WHILE @@FETCH_STATUS = 0 BEGIN SET @pindex = 0; -- generujemy PRINT INSERT INTO @SQL (Line) SELECT 'PRINT N''-- ' + @type_desc + ': ' + REPLACE( ISNULL(QUOTENAME(@schema_name) + '.', '') + QUOTENAME(@object_name), '''', '''''' ) + ''';'; IF @definition IS NOT NULL BEGIN -- generujemy DROP INSERT INTO @SQL (Line) SELECT 'IF EXISTS (SELECT * FROM sys.triggers WHERE name = N''' + REPLACE( ISNULL(QUOTENAME(@schema_name) + '.', '') + QUOTENAME(@object_name), '''', '''''' ) + ''')'; INSERT INTO @SQL (Line) SELECT ' DROP TRIGGER ' + ISNULL(QUOTENAME(@schema_name) + '.', '') + QUOTENAME(@object_name) + CASE WHEN @parent_class = 0 THEN ' ON DATABASE' ELSE '' END + ';'; -- poprzedzamy definicję słowem GO INSERT INTO @SQL (Line) SELECT 'GO'; -- kroimy definicję na poszczególne wiersze SET @pindex = PATINDEX('%' + CHAR(13) + CHAR(10) + '%', @definition); WHILE @pindex > 0 BEGIN INSERT INTO @SQL(Line) SELECT REPLACE(SUBSTRING(@definition, 1, @pindex), CHAR(13), ''); SET @definition = SUBSTRING(@definition, @pindex + 2, LEN(@definition)); SET @pindex = PATINDEX('%' + CHAR(13) + CHAR(10) +'%', @definition); END; END; ELSE BEGIN INSERT INTO @SQL (Line) SELECT 'PRINT N''-- *** This object is encrypted ***'''; END; -- kończymy definicję słowem GO INSERT INTO @SQL (Line) SELECT 'GO'; FETCH NEXT FROM CursorObjects INTO @schema_name, @object_name, @type_desc, @parent_class, @definition; END; CLOSE CursorObjects; DEALLOCATE CursorObjects; SELECT Line AS ' ' FROM @SQL ORDER BY LineId; GO
Fragment wyniku:
Uwagi: w Management Studio ustaw Results to Text, sugeruję też zwiększenie maksymalnej długości tekstu w jednej kolumnie zwracanej w wynikach zapytań (w menu głównym kliknij Tools – Options – Query Results – SQL Server – Results to Text i w polu Maximum number of characters displayed in each column wpisz 8192, a następnie uruchom ponownie Management Studio).
Komentarz do kodu:
- listę wyzwalaczy w bazie danych zwraca widok sys.triggers,
- fragmenty generowanego skryptu T-SQL są dodawane w kursorze (iteracja = definicja jednego obiektu) do zmiennej tabelarycznej @SQL (linie są numerowane autonumeracją w kolumnie LineId, zakładam maksymalną długość jednej linii kodu w obiekcie na 4000 znaków),
- drobnym wyzwaniem było rozróżnienie wyzwalaczy DML i DDL – każdy z tych typów wyzwalaczy wymaga nieco innego kodu przy usuwaniu, posłużyłem się kolumna parent_class z widoku sys.triggers,
- ponieważ w widoku sys.triggers nie ma kolumny schema_id, musiałem użyć funkcji OBJECT_SCHEMA_NAME do wyłuskania nazwy schematu,
- przy okazji taka obserwacja – wyzwalacz DML można utworzyć tylko w tym samym schemacie, w którym znajduje się nadrzędna tabela/widok.
Wyzwalacze na poziomie serwera
Zadanie: zeskryptować wszystkie wyzwalacze DDL na poziomie serwera.
Rozwiązanie:
USE master; GO SET NOCOUNT ON; DECLARE @SQL TABLE ( LineId int IDENTITY(1,1) NOT NULL PRIMARY KEY, Line nvarchar(4000) NOT NULL ); DECLARE @object_name sysname, @type_desc nvarchar(60), @definition nvarchar(max), @pindex int; DECLARE CursorObjects CURSOR LOCAL STATIC READ_ONLY FORWARD_ONLY FOR SELECT t.name, t.type_desc COLLATE database_default, m.definition FROM sys.server_triggers AS t INNER JOIN sys.server_sql_modules AS m ON m.[object_id] = t.[object_id] WHERE t.is_ms_shipped = 0 ORDER BY t.name; OPEN CursorObjects; FETCH NEXT FROM CursorObjects INTO @object_name, @type_desc, @definition; WHILE @@FETCH_STATUS = 0 BEGIN SET @pindex = 0; -- generujemy PRINT INSERT INTO @SQL (Line) SELECT 'PRINT N''-- ' + @type_desc + ': ' + REPLACE( QUOTENAME(@object_name), '''', '''''' ) + ''';'; IF @definition IS NOT NULL BEGIN -- generujemy DROP INSERT INTO @SQL (Line) SELECT 'IF EXISTS (SELECT * FROM sys.server_triggers WHERE name = N''' + REPLACE( @object_name, '''', '''''' ) + ''')'; INSERT INTO @SQL (Line) SELECT ' DROP TRIGGER ' + QUOTENAME(@object_name) + ' ON ALL SERVER;' -- poprzedzamy definicję słowem GO INSERT INTO @SQL (Line) SELECT 'GO'; -- kroimy definicję na poszczególne wiersze SET @pindex = PATINDEX('%' + CHAR(13) + CHAR(10) + '%', @definition); WHILE @pindex > 0 BEGIN INSERT INTO @SQL(Line) SELECT REPLACE(SUBSTRING(@definition, 1, @pindex), CHAR(13), ''); SET @definition = SUBSTRING(@definition, @pindex + 2, LEN(@definition)); SET @pindex = PATINDEX('%' + CHAR(13) + CHAR(10) +'%', @definition); END; END; ELSE BEGIN INSERT INTO @SQL (Line) SELECT 'PRINT N''-- *** This object is encrypted ***'''; END; -- kończymy definicję słowem GO INSERT INTO @SQL (Line) SELECT 'GO'; FETCH NEXT FROM CursorObjects INTO @object_name, @type_desc, @definition; END; CLOSE CursorObjects; DEALLOCATE CursorObjects; SELECT Line AS ' ' FROM @SQL ORDER BY LineId; GO
Fragment wyniku:
Uwagi: w Management Studio ustaw Results to Text, sugeruję też zwiększenie maksymalnej długości tekstu w jednej kolumnie zwracanej w wynikach zapytań (w menu głównym kliknij Tools – Options – Query Results – SQL Server – Results to Text i w polu Maximum number of characters displayed in each column wpisz 8192, a następnie uruchom ponownie Management Studio).
Komentarz do kodu:
- listę wyzwalaczy na poziomie serwera zwraca widok sys.server_triggers,
- definicje widoków na poziomie serwera znajdują się w kolumnie definition widoku sys.server_sql_modules,
- fragmenty generowanego skryptu T-SQL są dodawane w kursorze (iteracja = definicja jednego obiektu) do zmiennej tabelarycznej @SQL (linie są numerowane autonumeracją w kolumnie LineId, zakładam maksymalną długość jednej linii kodu w obiekcie na 4000 znaków),
- tu było bodaj najłatwiej, ponieważ wyzwalacze na poziomie serwera nie mają przypisanego schematu.
Słowo podsumowania
Skryptowanie pojedynczego obiektu proceduralnego to nie problem. SQL Server oferuje procedurę sp_helptext oraz funkcję OBJECT_DEFINITION. Ale jeżeli chcemy hurtowo skryptować obiekty, podany w powyżej kod może okazać się przydatny. Można na jego podstawie zbudować własne procedury do skryptowania i używać ich na codzień. Tu polecam wpis z mojego starego bloga: [PL][util] Piszemy narzędzia w T-SQL cz. 1 – sp_getcolumns.
Zachęcam do zabawy z metadanymi SQL Servera. Można się dzięki takiej zabawie sporo nauczyć. I oczywiście zachęcam do komentowania tego wpisu. Może masz jakieś uwagi, coś nie gra w podanym tu kodzie albo masz lepszy pomysł na realizację takich zadań.
| [EDYCJA] To już druga wersja tego wpisu. W pierwszej kod T-SQL nie bazował na kursorach, a definicja każdego obiektu była zwracana w postaci jednej wartości typu nvarchar(max), co powodowało problem w przypadku, gdy definicja obiektu była dłuższa niż 8192 znaki (a nawet mniej, gdyż Management Studio domyślnie wyświetla w kolumnie bodajże do 255 znaków). Druga wersja powstała dzięki czujności (po raz kolejny) Krzysia Stachyry. Krzysiu, wielki szacunek za cierpliwe testowanie moich wypocin! Tym razem powinno być ciut lepiej :-) [/EDYCJA] |
No comments yet.
No trackbacks yet.
Wirtualna premiera SQL Server 2012
2012/01/23 - 22:21
Tags: Denali, SQL Server
Posted in Denali, SQL Server | 3 comments
Właśnie dowiedziałem się, będąc w Redmond na szkoleniu Bare Metal SQL Server 2012, że 7 marca będzie miała miejsce wirtualna premiera SQL Server 2012! Jeżeli interesuje Cię nowa wersja SQL Servera, chcesz obejrzeć 30+ sesji prowadzonych przez czołowych specjalistów z całego świata i pracowników Microsoft, nie przegap tego wydarzenia i rejestruj się już dzisiaj na [...]
Materiały z dwóch edycji SQLDay Workshop
2012/01/07 - 08:49
Tags: SQL Server, SQLDay
Posted in SQL Server | No comments
Ostatnio informowałem o trzeciej edycji warsztatów SQLDay Workshop. Miejsca na warsztaty rozeszły się błyskawicznie, co tylko potwierdza, jak potrzebne są podobne przedsięwzięcia.
Miło mi poinformować za Tobiaszem Koprowskim, aktualnym prezesem Polskiej Grupy Użytkowników SQL Server (PLSSUG), że na kanale YouTube PLSSUG dostępne są nagrania wideo z dwóch poprzednich edycji warsztatów.
Osoby zainteresowane materiałami zapraszam na podane poniżej [...]
Wydarzenie – warsztaty SQLDay Workshop 2012
2011/12/27 - 21:34
Tags: SQL Server, SQLDay, warsztaty
Posted in Communities | 8 comments
W imieniu krakowskiego oddziału Polskiej Grupy Użytkowników SQL Server (PLSSUG) zapraszam na doroczne warsztaty SQLDay Workshop 2012. Warsztaty odbędą się, tradycyjnie już, w Krakowie. Termin imprezy: 4 lutego 2012. Miejsce: Krakowski Park Technologiczny Sp. z o.o., Al. Jana Pawła II 41L. Warsztaty będą bezpłatne, ale wymagana będzie rejestracja (wkrótce na stronie warsztatów). Wszelkich informacji na [...]
[PL] Nowe obiekty systemowe w SQL Server 2012 RC0
2011/12/12 - 21:11
Tags: Denali, metadane, obiekty, SQL Server
Posted in Denali, SQL Server | No comments
Całkiem niedawno Aaron Bertrand (SQL Server MVP) opublikował na swoim blogu listę nowych obiektów systemowych, które pojawiły się w SQL Server 2012 RC0, a nie było ich w SQL Server 2012 CTP3. Pomyślałem, że przydałaby się analogiczna lista, ale w odniesieniu do obiektów z SQL Server 2008 R2.
Jak taką listę przyrządzić? W moim przypadku było [...]
[PL] Konferencje, prelekcje, wydarzenia
2011/11/24 - 20:36
Tags: konferencja, PLSSUG, SQL Server, WWSI
Posted in Communities, SQL Server | No comments
Szykuje się sporo atrakcji dla osób zainteresowanych bazami danych, w szczególności tymi implementowanymi w systemie Microsoft SQL Server. Pomyślałem, że dobrze by było zebrać informacje o nadchodzących wydarzeniach i wyostrzyć czujność pasjonatów baz danych.
Już za tydzień, w czwartek 1 grudnia, o godzinie 18:00 w siedzibie firmy Microsoft w Warszawie przy Al. Jerozolimskich 195A rozpocznie się [...]
[PL] Najlepsze praktyki dla programisty T-SQL – collation i "obce znaczki"
2011/11/06 - 22:26
Tags: konferencja, MTS, SQL Server, T-SQL
Posted in SQL Server | No comments
Na tegorocznej konferencji Microsoft Technology Summit (MTS) przedstawiłem wraz z Markiem Adamczukiem prezentację pt. "Najlepsze praktyki dla programisty Transact-SQL". Po tej sesji sporo osób prosiło mnie o materiały, a głównie demonstracje. Dobra informacja jest taka, że materiały te można już pobrać ze strony konferencji. Najwięcej dyskusji po prezentacji dotyczyło fragmentu poświęconego collation i pracy z [...]
[PL] SQL Server MVP Deep Dives vol. 2 już jest!
2011/09/27 - 20:35
Tags: książka, MVP, SQL Server
Posted in Communities, SQL Server | 2 comments
Miło mi poinformować, że wydawnictwo Manning rozpoczęło przyjmowanie zamówień na książkę SQL Server MVP Deep Dives vo. 2, której jestem współautorem. Oficjalna premiera książki będzie miała miejsce w Redmond w czasie konferencji PASS Summit 2011. Dla osób, które nie zetknęły się z pierwszą częścią książki – SQL Server MVP Deep Dives to książka pisana przez [...]
[PL] Będę na MTS 2011
2011/08/16 - 21:28
Tags: konferencja, MTS, SQL Server
Posted in Communities | 5 comments
Miło mi zakomunikować, że w tym roku pojawię się w roli prelegenta na konferencji Microsoft Technology Summit 2011 (MTS 2011). Wraz z moim przyjacielem, Markiem Adamczukiem, zostaliśmy zaproszeni przez firmę Microsoft do udziału w konferencji i przygotowanie wspólnej prezentacji. Co ciekawe, w dobie nadchodzącej następnej wersji SQL Servera – Denali – poproszono nas o sesję [...]
[PL] Denali CTP3 – Nowe lepsze DBCC IND?
2011/07/26 - 07:46
Tags: Denali, DMV, SQL Server
Posted in SQL Server | 5 comments
Niedawno ukazała się nowa publiczna wersja Community Preview 3 (CTP3) systemu SQL Server vNext (codename Denali). Przejrzenie listy DMVs w tej wersji zaowocowało znalezieniem kilku ciekawych obiektów, ale najciekawszym z mojego punktu widzenia wydaje się być sys.dm_db_database_page_allocations.
[PL] Zmiany w PLSSUG
2011/06/20 - 13:40
Tags: PLSSUG, SQL Server
Posted in Communities | 5 comments
W ostatnią sobotę, 18 czerwca, odbyła się we Wrocławiu trzecia już w historii konferencja SQLDay. Organizatorem, jak zwykle, była Polska Grupa Użytkowników SQL Server (PLSSUG). Niestety, sprawy zawodowe tym razem nie pozwoliły mi wziąć udziału w konferencji, nad czym boleję. Tak czy owak, tradycji musiało stać się za dość i w trakcie konferencji został ogłoszony [...]

Nazywam się Paweł Potasiński i pracuję w polskim oddziale Microsoft w dziale Small and Midmarket Solutions & Partners (SMS&P) jako Partner Technology Advisor.




