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. ;))

sobota, 26 lutego 2011

Identifiers

Do tematu pisania poprawnego kodu SQL podchodziłem już kilkukrotnie. Pisząc ‘poprawnego’ nie mam na myśli tego  czy sam kod działa i czy zwraca poprawne wyniki, ale to czy jest napisany zgodnie ze sztuką. Czy przed nazwami tabel powinny być nazwy schematów, czy przed nimi znajduje się nazwa bazy danych, czy polecenie SQL kończy się średnikiem (;)…? Do tego dochodzi kwestia formatowania kodu, odpowiednich wcięć, itp.

Piszę o tym, bo miałem ostatnio ciekawy przypadek ze zwykłym poleceniem USE.

Z reguły piszą USE, a nazwę bazy przeciągam z okna ‘Object Explorer’ do okna ‘Query’, lub, jeżeli nazwa jest krótka, po prostu ją piszę. Efekt jest ten sam:

USE MOJA_BAZA;

Taki fragment kodu działa bez zarzutu. Problem pojawi się gdy nieodpowiednio nazwiemy bazę. Nieodpowiednio to znaczy jak? A choćby tak : MOJA-BAZA.

Jeżeli będziemy to robić z kodu poleceniem CREATE DATABASE MOJA-BAZA to otrzymamy błąd

Msg 170, Level 15, State 1, Line 1
Line 1: Incorrect syntax near '-'.

Jednakże robiąc to z MANAGEMENT STUDIO nie dostaniemy żadnych błędów. Wszystko przebiegnie bez problemu. Teraz stosując metodę DRAG&DROP przeciągam sobie nazwę bazy danych do okna QUERY dodając ją do słówka USE, odpalam i dostaję :

Msg 911, Level 16, State 1, Line 1
Database 'moja' does not exist. Make sure that the name is entered correctly.

Ale o co chodzi? Przecież ja wpisałem USE MOJA-BAZA. Pewnie, że nie ma bazy MOJA. Wszystko się zgadza, bo przecież jest baza MOJA-BAZA.

Najprostszym rozwiązaniem tego problemu są nawiasy kwadratowe.

USE [MOJA-BAZA];

I wszystko działa bez problemu.

Myślnik (-) nie jest może dobrym rozwiązaniem jako element składowy w nazwie bazy, czy w nazwie każdego innego obiektu, ale nie jest też zabroniony.

Wracając więc do początku mojego wpisu. Jak powinno wyglądać zapytanie, żeby uniknąć takich niespodzianek?

Czy tak :
SELECT KOL1
    , KOL2
    , KOL3
FROM MOJA_TABELA
A może tak :
SELECT [KOL1]
    , [KOL2]
    , [KOL3]
FROM [MOJA_BAZA].[DBO].[MOJA_TABELA];

Muszę chyba jeszcze raz zmierzyć się z tematem :)