czwartek, 14 kwietnia 2011

Nieudokumentowane procedury.

Jakiś czas temu natknąłem się na dwie fajne procedurki :

    SP_MSFOREACHDB
    SP_MSFOREACHTABLE

Są to dwie nieudokumentowane przez MS procedury SQL.

Za pomocą pierwszej z nich można wykonać kod SQL na wszystkich bazach danej instancji. Za pomocą drugiej na wszystkich tabelach danej bazy.

Postanowiłem zwrócić uwagę na te dwie procedurki, bo ułatwiają pracę. Zwłaszcza moją :).

Ostatnio miałem przypadek, że musiałem porównać wartości w jednej z tabelek we wszystkich bazach na danej instacji. Baz było dokładnie 358.

Prosty skrypcik i wynik był gotowy :

EXECUTE SP_MSFOREACHDB 'SELECT ''?'', * FROM ?.DBO.TABELA'

? – Zastępuje w tym przypadku nazwę bazy. W procedurze SP_MSFOREACHTABLE byłaby to nazwa tabeli.

Taki prosty kod, który zwraca nam już potrzebne informacje ze wszystkich baz, można rozbudować według własnych potrzeb. Można wyniki wrzucić do tabeli tymczasowej i już dalej na niej operować. Co istotne, błędy nie są istotne w tej procedurze ;). Tzn. jeżeli danej tabeli nie na bazie to zgłoszony zostanie błąd wykonania zapytania, ale cała procedura pojedzie dalej i zwróci nam wyniki dla baz, dla których tabela istnieje.

Szkoda tylko, że procedury nie są udokumentowane i w sumie nie wiadomo czy będą rozwijane. Może się okazać, że znikną w kolejnych wersjach MS SQL. Oby nie.

Jeden ze znajomych powiedział mi, że ważne jest na koniec posta zadać czytelnikom pytanie :). Pytam więc, czy też czujecie nieocenioną radość przy korzystaniu z  powyższych procedur?

niedziela, 27 marca 2011

Przeniesienie bazy z SQL 2008 na SQL 2005 (downgrade).

Ostatnio uczestniczyłem w przenoszeniu bazy z SQL 2008 na SQL 2005. Proste backup i restore w tym przypadku nie zadziała. Do przeniesienia struktury wykorzystałem narzędzie GENERATE SCRIPTS, a do transferu danych EXPORT DATA.


Cały proces przeszedł bez problemu... (tak mi się tylko wydawało).

Napotkałem w sumie na dwa problemy. Pierwszy polegał na tym, że niektóre obiekty, głównie klucze i defaulty. W tych miejscach gdzie te obiekty nie miały nadanej konkretnej nazwy, a została ona wygenerowana przez SQL serwer (dostaje ona wtedy dziwny numerek na końcu nazwy), na nowym serwerze nazwa ta też została wygenerowana. Czyli na kolumnie była zdefiniowana wartość domyślna ale constraint miał inny numerek w nazwie.

Drugi problem polegał na przeniesieniu danych. Okazało się, że nie przeniosły się one jeden w jeden. W moim przypadki problem był w kolumnach, które miały ustawioną wartość domyślną, ale dopuszczały NULL. Jeżeli na bazie źródłowej w kolumnie takiej był NULL to w bazie docelowej w tym samym polu pojawiła się wartość domyślna.

Napisałem skrypciorka, który pokazał mi w jakich tabelach zmieniły się dane.
USE ADVENTUREWORKSLT2008;

-- DEKLARACJA ZMIENNYCH
DECLARE @TABLE_ID INT -- ID TABELI
    , @TABLE_NAME VARCHAR(50) -- NAZWA TABELI
    , @SCHEMA_NAME VARCHAR(50) -- NAZWA SCHEMATU
    , @COLUMN_NAME VARCHAR(MAX) -- NAZWY KOLUMN
    , @S_SERVER_NAME VARCHAR(100) -- SERWER ŹRÓDŁOWY
    , @D_SERVER_NAME VARCHAR(100) -- SERWER DOCELOWY
    , @S_DB_NAME VARCHAR(100) -- BAZA ŹRÓDŁOWA
    , @D_DB_NAME VARCHAR(100) -- BAZA DOCELOWA
    , @MYSQL VARCHAR(MAX)
    , @MYSQL_EXT VARCHAR(MAX); 

-- PRZYPISANIE WARTOŚCI ZMIENNYM
SET @S_SERVER_NAME = '[SQL2008]';
SET @D_SERVER_NAME = '[SQL2005]';
SET @S_DB_NAME = 'ADVENTUREWORKSLT2008';
SET @D_DB_NAME = 'ADVENTUREWORKSLT2005';
    
DECLARE TABLE_CURSOR CURSOR FOR -- DEKLARACJA KURSORA DO POBIERANIA INFORMACJI O TABELACH
SELECT [OBJECT_ID]
    , SCHEMA_NAME(SCHEMA_ID) AS SCH_NAME
    , NAME
FROM SYS.TABLES
ORDER BY NAME ASC;

OPEN TABLE_CURSOR;

FETCH NEXT FROM TABLE_CURSOR 
INTO @TABLE_ID
    , @SCHEMA_NAME
    , @TABLE_NAME;

WHILE @@FETCH_STATUS = 0
BEGIN
    
    SET @COLUMN_NAME = '';
    SET @MYSQL = '';
    SET @MYSQL_EXT = '';
    
    -- ZŁOŻENIE KOLUMN TABELI
    SELECT @COLUMN_NAME = @COLUMN_NAME + ',[' + C.NAME + ']'
    FROM SYS.COLUMNS AS C
        INNER JOIN SYS.TYPES AS T ON C.SYSTEM_TYPE_ID = T.SYSTEM_TYPE_ID
    WHERE C.[OBJECT_ID] = @TABLE_ID
        AND T.NAME NOT IN ('IMAGE', 'TEXT', 'NTEXT', 'XML') -- NIE WSZYSTKIE DANE ZA POMOCĄ EXCEPT DA SIĘ PORÓWNAĆ
    ORDER BY C.COLUMN_ID ASC;

    SET @COLUMN_NAME = STUFF(@COLUMN_NAME, 1, 1, '') -- USUNIĘCIE PRZECINKA Z POCZĄTKU STRINGA

    -- ZŁOŻENIE ZAPYTANIE
    SET @MYSQL_EXT = @SCHEMA_NAME + '.' + @TABLE_NAME;
    
    SET @MYSQL =    'SELECT ' + @COLUMN_NAME 
                    + ' FROM ' + @S_SERVER_NAME + '.' + @S_DB_NAME + '.' + @SCHEMA_NAME + '.' + @TABLE_NAME
                    + ' EXCEPT '
                    + 'SELECT ' + @COLUMN_NAME 
                    + ' FROM ' + @D_SERVER_NAME + '.' + @D_DB_NAME + '.' + @SCHEMA_NAME + '.' + @TABLE_NAME;
    
    EXEC ('SELECT ''' + @MYSQL_EXT + '''') -- TABELA ANALIZOWANA
    EXEC (@MYSQL); -- ANALIZA DANYCH
        
    FETCH NEXT FROM TABLE_CURSOR 
    INTO @TABLE_ID
        , @SCHEMA_NAME
        , @TABLE_NAME;
    
END

CLOSE TABLE_CURSOR;
DEALLOCATE TABLE_CURSOR;
Aby nie mieć problemów z wartościami NULL w kolumnach z wartością domyślną nie należy generować ich za pomocą GENERATE SCRIPTS przed wstawieniem danych.

Co do pierwszego problemu to obejścia dalej szukam. ;))