środa, 27 października 2010

Trigger dla kopiowania wartości atrybutów w zależności od innego atrybutu.

W nawiązaniu do tematu http://cdn.3lance.pl/viewtopic.php?f=13&t=1428 zamieszczam przykładowe rozwiązanie, wraz z opisem.

CREATE TRIGGER [CDN].[_Kopiowanie_z_wysylki]
   ON  [CDN].[Atrybuty]
   FOR INSERT, UPDATE
AS
BEGIN
    SET NOCOUNT ON;

-- Dla zmiany wartości atrybutu
    IF UPDATE (Atr_Wartosc)
    BEGIN
-- Jeżeli ID klasy atrybutu jest równe X (u mnie 79 - atrybut na dokumentach PKA/WKA z numerem wysyłki)
-- i numer wysyłki jest na parametrze ustalony:
        IF EXISTS (SELECT inserted.Atr_ID FROM inserted WHERE inserted.Atr_Atkid = 79 AND inserted.Atr_Wartosc <> '' AND inserted.Atr_Wartosc <> '<Brak>')
            BEGIN
                DECLARE @GIDWysylka INT            -- GID numer wysyłki
                DECLARE @ObiNumer INT            -- GID obiektu dla którego uruchamiany jest wyzwalacz
                DECLARE @ObiTyp INT                -- GID Typ obiektu dla którego uruchamiany jest wyzwalacz
                DECLARE @Wysylka VARCHAR(30)    -- Wartość atrybutu z numerem wysyłki
                DECLARE @param1 VARCHAR(100)    -- Wartość parametru pierwszego
                DECLARE @param2 VARCHAR(100)    -- Wartość parametru drugiego
                DECLARE @param3 VARCHAR(100)    -- Wartość parametru trzeciego

-- Pobranie wartości atrybutu z numerem wysyłki
                SET @Wysylka = (SELECT Atr_wartosc FROM inserted)

-- Pobranie GID-u obiektu (w tym przypadku PKA/WKA) dla którego zmieniana jest wartość atrybutu.
                SET @ObiNumer = (SELECT Atr_ObiNumer FROM inserted)

-- Pobranie GID typ-u obiektu (w tym przypadku PKA/WKA) dla którego zmieniana jest wartość atrybutu.
                SET @ObiTyp = (SELECT Atr_ObiTyp FROM inserted)

-- Pobranie GID-u wysyłki o określonym numerze (w moim przypadku seria jest pusta więc ją pomijam).
-- Generalnie jest to zależne od tego w jaki sposób uzupełniamy wartość atrybutu z numerem wysyłki.
-- Dla mnie seria zawsze jest pusta, a więc nie biorę jej pod uwagę - numer wysyłki na atrybucie określam poprzez wybór z listy za pomocą zapytania:
-- SELECT     cast(Wys_numer as varchar(5)) + '/' +  cast(Wys_rok as varchar(4)) as Numer FROM CDN.Wysylki
-- Stąd też wyjściowy sposób zwrócenia GID-u dla moich wysyłek:

                SET @GIDWysylka = (SELECT isnull(WYS_GIDNumer,0) FROM CDN.Wysylki
                                   WHERE WYS_GIDTyp = 337
                                   AND WYS_Numer = cast(substring(@Wysylka,1,charindex('/',@Wysylka)-1) as int)
                                   AND WYS_Rok = cast(substring(@Wysylka,charindex('/',@Wysylka)+1,4) as int))
               
-- Pobranie wartości pierwszego parametru z wysyłki, który będziemy kopiować
                SET @param1 = (SELECT isnull(Atr_Wartosc,'') FROM CDN.Atrybuty
                               WHERE Atr_ObiTyp = 337 AND Atr_ObiNumer = @GIDWysylka AND Atr_AtkID = 80) 

-- Pobranie wartości drugiego parametru z wysyłki, który będziemy kopiować
                SET @param2 = (SELECT isnull(Atr_Wartosc,'') FROM CDN.Atrybuty
                               WHERE Atr_ObiTyp = 337 AND Atr_ObiNumer = @GIDWysylka AND Atr_AtkID = 81)

-- Pobranie wartości trzeciego parametru z wysyłki, który będziemy kopiować
                SET @param3 = (SELECT isnull(Atr_Wartosc,'') FROM CDN.Atrybuty
                               WHERE Atr_ObiTyp = 337 AND Atr_ObiNumer = @GIDWysylka AND Atr_AtkID = 82)
               
-- Skopiowanie wartości pierwszego parametru na obiekt docelowy
                UPDATE CDN.Atrybuty
                SET Atr_Wartosc = @param1
                WHERE Atr_ObiTyp = @ObiTyp AND CDN.Atrybuty.Atr_ObiNumer = @ObiNumer AND Atr_AtkID = 80

-- Skopiowanie wartości drugiego parametru na obiekt docelowy
                UPDATE CDN.Atrybuty
                SET Atr_Wartosc = @param2
                WHERE Atr_ObiTyp = @ObiTyp AND CDN.Atrybuty.Atr_ObiNumer = @ObiNumer AND Atr_AtkID = 81

-- Skopiowanie wartości trzeciego parametru na obiekt docelowy
                UPDATE CDN.Atrybuty
                SET Atr_Wartosc = @param3
                WHERE Atr_ObiTyp = @ObiTyp AND CDN.Atrybuty.Atr_ObiNumer = @ObiNumer AND Atr_AtkID = 82
   
            END
    END
   
END


Zaznaczam, iż wyzwalacz ma charakter edukacyjny i z tym wiąże się deklarowanie tylu zmiennych oraz jego dość rozbudowana forma. To samo można osiągnąć przy użyciu jednego UPDATE FROM SELECT, aczkolwiek uznałem, że nie dla wszystkich byłoby to czytelne.

Pole Atr_AtkID to oczywiście ID atrybutów które nas interesują - w moim przypadku są to ID ze zbioru {79,80,81,82} - w waszym zapewne będą inne i wymaga to sprawdzenia. Wyzwalacz skopiuje wartość danego atrybutu tylko w przypadku gdy ten atrybut będzie istniał na docelowym elemencie.

Jak na razie to pierwszy trigger jaki tu zamieszczam, aczkolwiek zapewne nie ostatni, jako iż jest to ciekawa funkcja SQL Server, która daje administratorowi dużo możliwości.

poniedziałek, 25 października 2010

Sklejanie pól tekstowych - unikanie kursora.

Przeglądając, jak co dzień, forum 3lance, zauważyłem temat gdzie jeden z użytkowników umieścił funkcję w której wykorzystywał kursor do sklejania wartości tekstowych. http://cdn.3lance.pl/viewtopic.php?f=13&t=1435 Tak troszkę mnie tknęło, bo przecież generalnie kursor mistrzem wydajności nie jest i w miarę możliwości winno się go unikać. Napisałem więc swoje rozwiązanie, które jest bardziej wydajne - być może się wam przyda :)
Przykładowy kod generujący "sklejone" płatności dla pierwszych 49 GID-ów w tabeli płatności.

SELECT DISTINCT    tab1.TrP_GIDNumer,
        STUFF((SELECT ',' + LTRIM(RTRIM(tab2.TrP_FormaNazwa))  FROM CDN.TraPlat AS tab2 WHERE tab2.TrP_GIDNumer = tab1.TrP_GIDNumer FOR XML PATH('')), 1, 1, '') AS Formy
FROM        CDN.TraPlat AS tab1 WHERE trp_GIDNumer < 50
ORDER BY    tab1.TrP_GIDNumer


Rozwiązanie trochę bardziej eleganckie, no i pozbyliśmy się nieszczęsnego kursora :P Na dzisiaj to by było na tylko, bo czasu brak :)

EDIT: Jeszcze mały edit - podane przeze mnie rozwiązanie będzie działać od SQL w wersji 2005.

piątek, 22 października 2010

HASP i jego aktualizacja

Będąc kiedyś na szkoleniu, spotkałem się rozmową dwóch Panów, którzy byli przekonani, iż serwisu klucza HASP (a w zasadzie licencji na kluczu) nie da się zaktualizować poprzez połączenie pulpitu zdalnego - trzeba się udać do klienta i dopiero można działać. Nie jest to do końca zgodne z prawdą. Otóż w momencie kiedy zalogujemy się na nasz serwer w sesji terminalowej, sam serwis klucza nie będzie widoczny:
Udajemy się więc do narzędzia administracyjne/pulpity zdalne. Tworzymy nowe połączenie do serwera na którym obecnie jesteśmy (nawet bez licencji terminalowych Win 2003/2008 Serwer ma 2 licencje dla administratora, więc zalogowanie będzie możliwe) i logujemy się już bardziej "lokalnie" do tego samego serwera. Efekt końcowy wygląda następująco:
Jesteśmy w stanie tak zaktualizować klucz, jak i zwalniać z niego "zawieszone" licencje. Czemu tak się dzieje ? Otóż sesja wewnętrzna nie jest do końca traktowana jak połączenie pulpitu zdalnego i dzięki temu widzi wszystkie lokalne porty i usługi na nich działające.

Co prawda nie jest to odkrycie Ameryki, ale myślę, że komuś może się przydać - może akurat kogoś wybawiłem przed 50 kilometrową wycieczką tylko dlatego, że coś nie działa :)

EDIT: W ramach uzupełnienia, Adam S uświadomił mnie, iż ten sam efekt można uzyskać poprzez wywołanie mstsc /admin -  na pewno jest szybciej :) Niemniej jednak zostawiam obie możliwości.

czwartek, 21 października 2010

Wyświetlanie komunikatów błędów w XL (i nie tylko).

Nie raz, nie dwa, zdarzało się, iż podczas pracy z XL (np. przy wywoływaniu wydruku) przywitał nas komunikat o błędzie. Problem w tym, że sam błąd nie był opisany, a zamiast tego witał nas komunikat "Kod dostawcy bazy danych = X", gdzie X jest liczbą naturalną. Jeśli nie wiemy co tak naprawdę oznacza dany komunikat, warto zapoznać się z tabelą systemową sys.messsages w kontekście bazy danych .  Tak więc wywołanie z poziomu np. SSMS:
        SELECT     message_id, language_id, severity, is_event_logged, text
       FROM         sys.messages

zwróci nam informację o wszystkich błędach zdefiniowanych we wspomnianej tabeli.
Skoro wyskoczył nam ten jeden konkretny (X) to najłatwiej znaleźć go poprzez dopisanie do naszego zapytania klauzuli WHERE (WHERE message_id = X) i ograniczenie wyników do języka, który nie wygląda dla nas jak stado znaczków - w moim przypadku wybrałem angielski (AND language_ID = 1033). Otrzymamy więc prościutkie zapytanie, np.
       SELECT     severity, text
       FROM         sys.messages
       WHERE language_id = 1033 AND message_id = 120

które zwróci nam informację na ile poważny jest błąd (kolumna severity) i jego opis (text). Dla ciekawskich zamieszczam jeszcze link do opisu tabeli wg. Microsoft-u http://msdn.microsoft.com/en-us/library/ms187382.aspx.

Niezbyt obszerny ten post, ale od czegoś trzeba zacząć :)

Wstęp...

Tytułem wstępu wypada powiedzieć coś o sobie. Mam na imię Mateusz i właśnie rozpoczynam prowadzenie tego oto bloga - wraz z czasem będzie się zmieniać zarówno jego treść, jak i wygląd. Jak długo będę go prowadzić, nie wiem, aczkolwiek docelowo mam zamiar stworzyć coś swojego, a nie działać w oparciu o szablon. No tak.... Ale o czym będzie ten blog ? W założeniu poświęcony on będzie zagadnieniom związanym z technologiami Microsoftu - SQL Server i .NET. Jako, że na co dzień pracuję w firmie działającej z Comarch XL, a wcześniej, przez ponad 2 lata byłem programisto-wdrożeniowcem w firmie wdrażającej to oprogramowanie, wpadnie tutaj sporo informacji i o tym. Swoją wiedzę rozwijam z każdym kolejnym dniem i w związku z tym chce się nią po części podzielić poprzez zamieszczanie tutaj wpisów - będą to tak drobne informacje, jak i rozwiązania problemów z którymi się borykam, oraz ciekawostki które odnalazłem w odmętach internetu, a uznałem je za interesujące. Daleko mi do doskonałości w tym co robię, ale myślę, że to co tutaj zamieszczę przyda się niejednej osobie. Pozdrawiam i miłej lektury !