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 :)

niedziela, 20 lutego 2011

Profiler i Performance Monitor

Dziś przeczytałem świetny artykuł na temat połączenia wyników profilera z wynikami monitora wydajności w celu znalezienia "wąskich gardeł" systemu. Polecam wszystkim, którzy zajmują się badaniem wydajności SQL serwera.

piątek, 18 lutego 2011

DISABLE TRIGGERS

Nie lubię Triggerów...

Poniżej prosty skrypt do wyłączania wszystkich Triggerów DML na tabelach użytkownika.
DECLARE @NAME VARCHAR(8000)

DECLARE TR_CURSOR CURSOR STATIC FOR
    SELECT DISTINCT SCHEMA_NAME(T.[SCHEMA_ID]) + '.' + T.NAME 
    FROM SYS.TRIGGERS AS TR
        INNER JOIN SYS.TABLES AS T ON TR.PARENT_ID = T.[OBJECT_ID]
    WHERE TR.PARENT_CLASS = 1

OPEN TR_CURSOR

FETCH NEXT FROM TR_CURSOR 
INTO @NAME

WHILE @@FETCH_STATUS = 0
    BEGIN

        EXECUTE('DISABLE TRIGGER ALL ON ' + @NAME)

        FETCH NEXT FROM TR_CURSOR 
        INTO @NAME
    END 

CLOSE TR_CURSOR
DEALLOCATE TR_CURSOR

środa, 16 lutego 2011

CHECKSUM - ciekawostka

Ciekawe zachowanie się funkcji CHECKSUM pokazał mi wczoraj jeden ze znajomych. Otóż CHECKSUM(10.00) = CHECKSUM(100.00). Zaobserwował On taki zachowanie gdy sprawdzał jakie rekordy zmieniły się w tabelce, a zmiana polegała na zwiększeniu ceny 10-krotnie. Korzystając z funkcji CHECKSUM okazało się, że nic się nie zmieniło. Przykład poniżej :

DECLARE @T1 TABLE
(
    ID INT,
    NAZWA VARCHAR(50),
    CENA DECIMAL(10,2)
);

DECLARE @T2 TABLE
(
    ID INT,
    NAZWA VARCHAR(50),
    CENA DECIMAL(10,2)
);

INSERT INTO @T1 (ID, NAZWA, CENA)
VALUES (1, 'TEST1', 125.23)
    , (2, 'TEST2', 10.00)
    , (3, 'TEST3', 23.23);

INSERT INTO @T2 (ID, NAZWA, CENA)
VALUES (1, 'TEST1', 125.23)
    , (2, 'TEST2', 10.00)
    , (3, 'TEST3', 23.23);

-- DANE W OBU TABELKACH SĄ IDENTYCZNE
SELECT * FROM @T1
SELECT * FROM @T2

-- SPRAWDZENIE CZY SĄ JAKIEŚ REKORDY, KTÓRE SIĘ RÓŻNIĄ
SELECT *
FROM @T1 AS T1
    INNER JOIN @T2 AS T2 ON T1.ID = T2.ID
WHERE CHECKSUM(T1.NAZWA, T1.CENA) <> CHECKSUM(T2.NAZWA, T2.CENA)

-- ZWIĘKSZENIE CENY W TABELI @T1
UPDATE @T1
SET CENA = CENA * 10

-- DANE W TABELACH RÓŻNIĄ SIĘ
SELECT * FROM @T1
SELECT * FROM @T2

-- SPRAWDZENIE CZY REKORDY RÓŻNIĄ SIĘ ZA POMOCĄ CHECKSUM DAJE WYNIK NEGATYWNY
-- WEDŁUG CHECKSUM NIC SIĘ NIE ZMIENIŁO
SELECT *
FROM @T1 AS T1
    INNER JOIN @T2 AS T2 ON T1.ID = T2.ID
WHERE CHECKSUM(T1.NAZWA, T1.CENA) <> CHECKSUM(T2.NAZWA, T2.CENA)

-- OBIE SUMY KONTROLNE SĄ TAKIE SAME
SELECT CHECKSUM(T1.NAZWA, T1.CENA) AS CHECKSUM_T1
    , CHECKSUM(T2.NAZWA, T2.CENA) AS CHECKSUM_T2
FROM @T1 AS T1
    INNER JOIN @T2 AS T2 ON T1.ID = T2.ID



















W helpie do funkcji CHECKSUM jest fajne stwierdzenie, które może tłumaczyć takie zachowanie :
However, there is a small chance that the checksum will not change. For this reason, we do not recommend using CHECKSUM to detect whether values have changed, unless your application can tolerate occasionally missing a change.
Jak poczytałem na necie to problem jest znany. Jedni piszą swoje funkcje sprawdzające sumę kontrolną np. w C# i podłączają je jako CLR. Inni korzystają z funkcji  HashBytes, ale tu elementem wejściowym musi być tekst.
Do powyższej sytuacji rozwiązaniem wydaje się "skastowanie" wartości numerycznej na VARCHARa. Wówczas osiągamy to o co nam chodziło.

A może jest inny sposób ...?

wtorek, 15 lutego 2011

Azure UNICODE Friendly

Jeszcze jedna ciekawostka ze świata Azure jest taka, że jest on bardzo UNICODE. Jeżeli w tabelce mamy pole typu VARCHAR, a w nim np. słówko 'PAWEŁ' to szukając prostym zapytaniem :
SELECT *
FROM TABELKA
WHERE NAZWA = 'PAWEŁ';
... otrzymamy zero wyników. Azure sprytnie zamienia sobie "Ł" na "L" i szuka 'PAWEL' i oczywiście nie znajduje. Rozwiązania na znalezienie interesujących nas rekordów są dwa : zmiana pola z VARCHAR na NVARCHAR lub napisanie zapytania w taki sposób :
SELECT *
FROM TABELKA
WHERE NAZWA = N'PAWEŁ';