Posts tagged skryptowanie
[PL] SQL Server – Jak przenieść dane geograficzne z bazy do bazy?
Sep 22nd
Kolega zapytał mnie dzisiaj, jak przenieść między bazami danych w SQL Server 2008 / 2008 R2 dane geograficzne za pomocą Import/Export Wizarda. Przyznam, że nie znałem odpowiedzi na pytanie, ale z kontekstu pytania domyśliłem się, w czym problem. Otóż wizard ten nie oferuje w ogóle obsługi typów danych zaimplementowanych z użyciem CLR (wyjątkiem jest tu XML, który ma dość oczywistą postać tekstową). Jak sobie zatem poradzić z problemem, gdy chcemy przenieść takie dane między dwoma serwerami (nie między bazami na jednym serwerze – to jest do zrobienia za pomocą prostych poleceń SELECT…INTO lub INSERT INTO…SELECT) ? Rozwiązań jest kilka. Ja pokażę trzy.
Metoda 1. – Generowanie skryptu w Management Studio
W Management Studio (SSMS) w oknie Object Explorer klikamy prawym przyciskiem myszy na bazie danych i wybieramy Tasks – Generate Scripts…
W wizardzie do generowania skryptów wybieramy tabelę z danymi geograficznymi i klikamy Next, aż zobaczymy ekran z przyciskiem Advanced…
Oczywiście, klikamy na tym przycisku (swoją drogą łatwym do przeoczenia!), by ujrzeć zaawansowaną (?) konfigurację skryptowania. Szukamy pozycji Types of data for script i wybieramy w niej Schema and data. To zapewni nam zeskryptowanie zarówno definicji tabeli, jak i jej danych.
W przypadku, gdy zdecydujesz, że wygenerowany kod T-SQL ma być od razu wrzucony do okna nowego skryptu, możesz otrzymać komunikat informujący o dłuuuuugich liniach w tekście. Rozchodzi się o to, że dane binarne (geograficzne) są skryptowane “as is” tj. w postaci heksadecymaliów. Nie trzeba dużej wyobraźni, by domyśleć się, że kształt granic Polski zapisanych za pomocą tysięcy punktów to całkiem duuuże binarium wymagające spoooorej liczby bajtów :-)
WAŻNE: Jeżeli odpowiesz “Tak” (a czy masz jakiś wybór? ;-)) na pytanie z okienka powyżej, spodziewaj się, że SSMS zacznie działać zauważalnie wolniej (a czasem po prostu zawiśnie lub zwróci OutOfMemoryException)…
Skrypt zapisujemy (albo i nie), otwieramy połączenie z drugą instancją SQL Server i jesteśmy gotowi do przerzucenia danych.
Metoda 2. – Import/Export Wizard
Druga metoda bazuje na użyciu Import/Export Wizarda i pewnego chwytu, który może wydać się dość rozpaczliwy, ale przy braku obsługi danych geograficznych w owym wizardzie… :-)
A zatem: Start – Uruchom – dtswizard.exe. Otwieramy “czarodzieja”, wybieramy serwer źródłowy i bazę źródłową, serwer docelowy i bazę docelową, a następnie… Skoro wiemy, że wizard nie daje sobie rady z typem geography, trzeba ów typ zmienić. Jak? Po prostu rzutując dane geograficzne na typ binarny varbinary(max).
Dalej jest już prosto. Dla tak spreparowanej binarnej kolumny wybieramy jako kolumnę docelową kolumnę typu image lub varbinary(max). A potem? Gdy już dane uda się zaimportować, logujemy się do docelowego serwera i na docelowej tabeli wykonujemy dwie operacje:
- Dodajemy kolumnę typu geography.
- Kopiujemy dane z kolumny binarnej, w której wylądowały dane geograficzne w procesie przenoszenia danych wizardem, do kolumny utworzonej chwilę wcześniej.
Bez dwóch zdań, wstyd dla Microsoftu, że wizard nie daje sobie rady z typami przestrzennymi (zresztą, to samo jest z typem hierarchyid).
Metoda 3. – Pass-through query
Trzecia metoda oparta jest na mechanizmie serwerów dołączonych (linked servers). Tworzymy na instancji źródłowej, z której chcemy eksportować dane geograficzne, linked server wskazujący na instancję docelową. Na docelowej instancji w odpowiedniej bazie danych zakładamy tabelę, do której dane geograficzne chcemy wrzucić, i konstruujemy tzw. pass-through query, czyli coś w stylu:
INSERT INTO OPENQUERY( [ASUS\SQL2008], 'SELECT CountryName, CountryNameLocal, geom FROM Spatials.dbo.Country_Copy' ) SELECT CountryName, CountryNameLocal, geom FROM Spatials.dbo.Country;
W powyższym kodzie [ASUS\SQL2008] to nazwa linked servera. Drugim parametrem w powyższym użyciu funkcji OPENQUERY jest zapytanie, którego zadaniem jest wskazanie tabeli i kolumn, do których chcemy wstawić dane. Wygląda niecodziennie, ale najważniejsze, że działa.
A za to nie działają dwa, wydawałoby się bardziej pospolite polecenia:
SELECT * INTO [ASUS\SQL2008].Spatials.dbo.Country_Copy FROM Spatials.dbo.Country; -- Wynik: -- Msg 117, Level 15, State 1, Line 1 -- The object name 'ASUS\SQL2008.Spatials.dbo.Country_Copy' -- contains more than the maximum number of prefixes. The maximum is 2. INSERT INTO [ASUS\SQL2008].Spatials.dbo.Country_Copy (CountryName, CountryNameLocal, geom) SELECT CountryName, CountryNameLocal, geom FROM Spatials.dbo.Country; -- Wynik: -- Msg 7325, Level 16, State 1, Line 1 -- Objects exposing columns with CLR types are not allowed -- in distributed queries. Please use a pass-through query -- to access remote object '"Spatials"."dbo"."Country_Copy"'.
Podsumowanie
Jak widać, przemieszczenie danych geograficznych (lub jakichkolwiek danych zapisanych w kolumnach typów opartych o CLR) nie jest trywialne. Pewnie, można wziąć backup bazy i odtworzyć na docelowym serwerze. Ale jeżeli baza jest duża, a ilość danych do przemieszczenia niewielka, ta metoda już nie wydaje się najlepsza. Poza tym co, jeśli chcemy przenieść dane między SQL Server 2008 R2 a SQL Server 2008? (swoisty downgrade) Wtedy już metoda oparta o backup (czy – równoważnie – o detach/attach) nie wchodzi w grę, bo po prostu nie da się przenieść bazy z wersji wyższej SQL Servera do wersji niższej. O ile wiadomo, że dtswizard.exe to prosta aplikacja, której zadaniem jest dokonywanie najprostszych transferów danych, o tyle brak obsługi danych przestrzennych w SSIS już nieco dziwi. Ale cóż, widać rozwój jednych “ficzerów” SQL Servera jest zbyt szybki i niektóre teamy w Redmond zwyczajnie za nim nie nadążają. Normalna sytuacja w rozbudowanym procesie twórczym skomplikowanego systemu, jakim jest SQL Server :-)
[PL] Skryptowanie w SQL Server 2008 – Klucze obce
Sep 6th
Intro
To już trzeci wpis z serii “Skryptowanie w SQL Server 2008″. Dwa poprzednie dostępne są tu:
- [PL] Skryptowanie w SQL Server 2008 – Obiekty proceduralne
- [PL] Skryptowanie w SQL Server 2008 – Proste typy danych użytkownika
Tym razem przedstawiam sposób na skryptowanie kluczy obcych. Kiedy takie skryptowanie może się przydać? Na przykład wtedy, gdy chcemy usunąć, a następnie odtworzyć owe klucze (typowy scenariusz: import wyczyszczonych i zwalidowanych danych). W podsumowaniu podam, jakie rozwinięcia mojego kodu można zaimplementować, by za pomocą praktycznie tego samego kodu wykonywać inne zadania związane z kluczami obcymi.
Skryptowanie kluczy obcych
Zadanie: zeskryptować wszystkie klucze obce w bazie danych.
Rozwiązanie:
USE AdventureWorks2008R2;
GO
SET NOCOUNT ON;
-- tu będziemy trzymać generowany kod
DECLARE @SQL TABLE (
LineId int IDENTITY(1,1) NOT NULL PRIMARY KEY,
Line nvarchar(4000) NOT NULL
);
DECLARE
@object_name sysname,
@object_id int,
@object_schema_name sysname,
@parent_object_name sysname,
@parent_object_schema_name sysname,
@is_not_for_replication bit,
@delete_referential_action int,
@update_referential_action int,
@referenced_object_schema_name sysname,
@referenced_object_name sysname,
@column_name sysname,
@referenced_column_name sysname,
@columns nvarchar(4000),
@referenced_columns nvarchar(4000);
-- kursor do wyciągania pojedynczych kluczy obcych
DECLARE CursorFK CURSOR LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR
SELECT
name,
[object_id],
SCHEMA_NAME([schema_id]),
OBJECT_SCHEMA_NAME(parent_object_id),
OBJECT_NAME(parent_object_id),
OBJECT_SCHEMA_NAME(referenced_object_id),
OBJECT_NAME(referenced_object_id),
is_not_for_replication,
delete_referential_action,
update_referential_action
FROM sys.foreign_keys
WHERE is_ms_shipped = 0;
OPEN CursorFK;
FETCH NEXT FROM CursorFK
INTO @object_name, @object_id, @object_schema_name,
@parent_object_schema_name, @parent_object_name,
@referenced_object_schema_name, @referenced_object_name,
@is_not_for_replication,
@delete_referential_action, @update_referential_action;
WHILE @@FETCH_STATUS = 0 BEGIN
SELECT @columns = '', @referenced_columns = '';
-- generujemy sprawdzenie, czy klucz nie istnieje
INSERT INTO @SQL (Line)
SELECT 'IF OBJECT_ID(N''' +
REPLACE(
QUOTENAME(@object_schema_name) + '.' +
QUOTENAME(@object_name), '''', ''''''
) + ''', N''F'') IS NULL';
-- generujemy polecenie ALTER TABLE ... ADD CONSTRAINT
INSERT INTO @SQL (Line)
SELECT ' ALTER TABLE ' +
QUOTENAME(@parent_object_schema_name) + '.' +
QUOTENAME(@parent_object_name);
INSERT INTO @SQL (Line)
SELECT ' ADD CONSTRAINT ' + QUOTENAME(@object_name);
-- kursor do wyciągania kolejnych kolumn klucza obcego
DECLARE CursorFKColumns CURSOR LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR
SELECT
pc.name,
rc.name
FROM sys.foreign_key_columns AS f
INNER JOIN sys.columns AS pc
ON f.parent_object_id = pc.[object_id]
AND f.parent_column_id = pc.column_id
INNER JOIN sys.columns AS rc
ON f.referenced_object_id = rc.[object_id]
AND f.referenced_column_id = rc.column_id
WHERE f.constraint_object_id = @object_id
ORDER BY f.constraint_column_id
OPEN CursorFKColumns;
FETCH NEXT FROM CursorFKColumns
INTO
@column_name,
@referenced_column_name;
WHILE @@FETCH_STATUS = 0 BEGIN
-- doklejamy do zestawów kolumn
SET @columns = @columns + ', ' + QUOTENAME(@column_name);
SET @referenced_columns = @referenced_columns + ', ' + QUOTENAME(@referenced_column_name);
FETCH NEXT FROM CursorFKColumns
INTO
@column_name,
@referenced_column_name;
END;
CLOSE CursorFKColumns;
DEALLOCATE CursorFKColumns;
-- generujemy kod z listami kolumn
INSERT INTO @SQL (Line)
SELECT ' FOREIGN KEY (' + STUFF(@columns, 1, 2, '') + ')';
INSERT INTO @SQL (Line)
SELECT ' REFERENCES ' +
QUOTENAME(@referenced_object_schema_name) + '.' +
QUOTENAME(@referenced_object_name) + '(' +
STUFF(@referenced_columns, 1, 2, '') + ')';
-- akcje klucza przy usuwaniu wierszy
IF @delete_referential_action <> 0 BEGIN
INSERT INTO @SQL (Line)
SELECT ' ON DELETE ' +
CASE @delete_referential_action
WHEN 1 THEN 'CASCADE'
WHEN 2 THEN 'SET NULL'
WHEN 3 THEN 'SET DEFAULT'
END;
END;
-- akcje klucza przy modyfikacji wierszy
IF @update_referential_action <> 0 BEGIN
INSERT INTO @SQL (Line)
SELECT ' ON UPDATE ' +
CASE @update_referential_action
WHEN 1 THEN 'CASCADE'
WHEN 2 THEN 'SET NULL'
WHEN 3 THEN 'SET DEFAULT'
END;
END;
-- czy wymuszamy regułę klucza w replikacjach
IF @is_not_for_replication = 1
INSERT INTO @SQL (Line)
SELECT ' NOT FOR REPLICATION';
UPDATE @SQL
SET Line = Line + ';'
WHERE LineId = SCOPE_IDENTITY();
INSERT INTO @SQL (Line) SELECT 'GO';
FETCH NEXT FROM CursorFK
INTO @object_name, @object_id, @object_schema_name,
@parent_object_schema_name, @parent_object_name,
@referenced_object_schema_name, @referenced_object_name,
@is_not_for_replication,
@delete_referential_action, @update_referential_action;
END;
CLOSE CursorFK;
DEALLOCATE CursorFK;
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:
- metadane kluczy obcych zwracają widoki sys.foreign_keys (metadane tabeli, w której tworzymy klucz, opcje dla poleceń DELETE / UPDATE, opcja wyłączenia klucza dla replikacji) oraz sys.foreign_key_columns (metadane kolumn oraz tabeli, do której odwołuje się klucz obcy),
- funkcja QUOTENAME jak zwykle służy mi do otaczania identyfikatorów nawiasami kwadratowymi i apostrofami,
- 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),
- rozwiązanie oparłem o dwa kursory: CursorFK – do iterowania po kluczach obcych oraz zagnieżdżony CursorFKColumns – do iterowania po kolumnach bieżącego klucza,
- ponieważ nie jestem zwolennikiem wyłączania więzów, moje rozwiązanie nie uwzględnia kolumny is_disabled z widoku sys.foreign_keys (nie generuję klauzuli WITH NOCHECK nawet, gdy klucz jest wyłączony).
Słowo podsumowania
Powyższy kod można rozwinąć:
- można dołożyć filtrowanie po tabelach (czy to tabeli, w której klucz jest utworzony, czy po tabeli, do której klucz się odwołuje),
- można generować tylko składnie ALTER TABLE … DROP CONSTRAINT, jeżeli zależy nam tylko na usunięciu wybranych kluczy,
- można wykorzystać kolumnę is_system_named z widoku sys.foreign_keys, jeżeli chcemy odnaleźć te klucze, które otrzymały nazwy wygenerowane systemowo (jeżeli nie chcemy mieć w bazie danych obiektów nazywanych dość przypadkowo).
Na koniec informacja – w repozytorium kodu umieściłem kod pseudo-systemowej procedury sp_dropforeignkeys, która może posłużyć do wygenerowania kodu usuwającego klucze obce wskazujące na wybraną tabelę. W procedurze tej wykorzystałem te same obiekty systemowe, co w powyższym kodzie, więc komentarz do niej wydaje mi się zbędny. Po stworzeniu w bazie master i oznaczeniu jako obiekt systemowy procedura powinna działać w kontekście dowolnej bazy danych.


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




