Blog programisty w Oracle PL/SQL

Jest to blog eksperymentatora programisty w PL/SQL dla Oracle. Wszystkie kody tutaj zamieszczone mogą być dowolnie wykorzystywane i zmieniane. A jeśli Ktoś z Gości znajdzie błąd, będę niezwykle wdzięczny...
Zapisz

Szukaj na tym blogu

sobota, 19 marca 2011

Walidacja NIP, PESEL i REGON - im mniej kodu tym lepiej

W poniższym niewielkim pakiecie chciałem pokazać zastosowanie niektórych rzadziej wykorzystywanych możliwości języka PL/SQL, a mianowicie:
  • kolekcji
  • parametrów domyślnych
  • typu SIMPLE_INTEGER ( ze względów wydajnościowych)
Zauważmy, że wszystkie trzy algorytmy walidacji dla NIP, PESEL i REGON  mają następujące podobne cechy:
  • tekst na wejściu ma mieć długość o jedną cyfrę dłuższą niż długość tablicy wag
  • w jednakowy sposób obliczamy sumę kontrolną - różnica jest w dzielniku
  • walidacja sumy kontrolnej z ostatnim cyfrą wprowadzonego literału występuje w dwóch wersjach dla PESEL oraz dla NIP i REGON
Inne założenia i uwagi:
  •  każda z funkcji zwraca jeden jeśli parametr jest poprawny i zero w przeciwnym przypadku
  • pragma WNDS umozliwia wywołanie funkcji na poziomie SQL
  • argument może być dowolnej długości i zawierać dowolną liczbę znaków nie będących cyframi za pomocą wyrażeń regularnych usuniemy je
  • deklaracja tablic wag w części deklaratywnej ciała pakietu poprawiło wydajność o około 20% w porównaniu z deklaracja tablic wag wewnątrz ciał funkcji. Tablice tworzone są podczas pierwszego wywołania w danej sesji bazodanowej
  • jest to kod szybki, ale stanowi pewien kompromis pomiędzy wydajnością a czytelnością
  • kompilacja za pomocą poniższych parametrów (do kodu maszynowego z zaawansowaną optymalizacją) zwiększa wydajność około dwukrotnie: ALTER PACKAGE PCKG_WALIDUJ_IDS COMPILE PLSQL_CODE_TYPE = NATIVE  PLSQL_OPTIMIZE_LEVEL = 3
  • jest to krótki protest  przeciwko kodowaniu metodą kopiuj & wklej
  • wymagany jest Oracle 11g

CREATE OR REPLACE PACKAGE PCKG_WALIDUJ_IDS AS
    FUNCTION    FDB_Waliduj_NIP(pc_NIP VARCHAR2) RETURN INTEGER;
    FUNCTION    FDB_Waliduj_PESEL(pc_PESEL VARCHAR2) RETURN INTEGER;
    FUNCTION    FDB_Waliduj_REGON(pc_REGON VARCHAR2) RETURN INTEGER;
    PRAGMA RESTRICT_REFERENCES(FDB_Waliduj_NIP, WNDS);
    PRAGMA RESTRICT_REFERENCES(FDB_Waliduj_PESEL, WNDS);
    PRAGMA RESTRICT_REFERENCES(FDB_Waliduj_REGON, WNDS);
END;
/

CREATE OR REPLACE PACKAGE BODY PCKG_WALIDUJ_IDS IS
------------------------------------------------------
TYPE t_Wagi IS TABLE OF SIMPLE_INTEGER;
vt_NIP t_Wagi := t_Wagi(6, 5, 7, 2, 3, 4, 5, 6, 7 );
vt_PESEL t_Wagi := t_Wagi(1, 3, 7, 9, 1, 3, 7,9,1,3);
vt_REGON_9 t_Wagi := t_Wagi(8, 9, 2, 3, 4, 5, 6, 7);
vt_REGON_14 t_Wagi := t_Wagi( 2, 4, 8, 5, 0, 9, 7, 3, 6, 1, 2, 4, 8);
----------------------
FUNCTION PDB_Waliduj( pc_Literal VARCHAR2, pt_Wagi IN t_Wagi,
    pn_Modulo SIMPLE_INTEGER, pb_PESEL BOOLEAN :=FALSE) RETURN NUMBER
IS
    vc_Literal VARCHAR2(14 CHAR);
    vn_SumaKontr SIMPLE_INTEGER:=0;
BEGIN
    IF NVL(REGEXP_COUNT(pc_Literal,'[[:digit:]]'),0) <> pt_Wagi.COUNT + 1 THEN        
        RETURN 0;
     END IF;
---Suma kontrolna
    vc_Literal := REGEXP_REPLACE( pc_Literal,'[^[:digit:]]','');                      
    FOR i IN 1..pt_Wagi.COUNT
    LOOP
        vn_SumaKontr := vn_SumaKontr + TO_NUMBER(SUBSTR( vc_Literal, i,1)) * pt_Wagi(i);
    END LOOP;
---
    vn_SumaKontr := MOD(vn_SumaKontr, pn_Modulo);
--  Dla NIP poniższy przypadek nie powinien wystąpić, dla PESEL nie wystąpi
    IF vn_SumaKontr = 10 THEN
        vn_SumaKontr := 0;
    END IF;  
--  PESEL    
    IF pb_Pesel THEN
        IF vn_SumaKontr + TO_NUMBER(SUBSTR( vc_Literal, Length(vc_Literal),1)) IN ( 0, 10)  THEN
            RETURN 1;
        ELSE
            RETURN 0;
        END IF;
    END IF;
-- NIP, REGON
    IF vn_SumaKontr = TO_NUMBER(SUBSTR( vc_Literal, Length(vc_Literal),1)) THEN
        RETURN 1;
    ELSE
        RETURN 0;
    END IF;
END;
----
FUNCTION  FDB_Waliduj_NIP(pc_NIP VARCHAR2) RETURN INTEGER
IS   
BEGIN    
    RETURN PDB_Waliduj(pc_NIP, vt_NIP, 11);           
END;
------------------------------------------------------  
FUNCTION  FDB_Waliduj_PESEL(pc_PESEL VARCHAR2) RETURN INTEGER
IS  
BEGIN
    RETURN PDB_Waliduj(pc_Pesel, vt_PESEL, 10, TRUE);
END;
------------------------------------------------------
FUNCTION  FDB_Waliduj_REGON(pc_REGON VARCHAR2) RETURN INTEGER
IS  
BEGIN           
    RETURN PDB_Waliduj(pc_REGON, vt_REGON_9,  11) +
    PDB_Waliduj(pc_REGON, vt_REGON_14, 11);
END;
END;
/

niedziela, 6 marca 2011

Niektóre parametry bazy a możliwośc kompilacji i poprawność kodu

Przenosząc kod PL/SQL pomiędzy serwerami można się natknąć na kilka ciekawych zagwozdek..
Skupiujemy schematy, uprawnienia, a pomimo tego kod może działać źle lub nie działać wcale
Ze względu  na rożne ustawienia serwerów bazy na takiej samej wersji bazy danych kod może nie chcieć się skompilować lub działać inaczej..
compatible - jeśli mamy ustaloną zgodność z poprzednią wersją bazy danych, a korzystamy z  dobrodziejstw najnowszej, mamy gwarantowany błąd kompilacji, np. kod zawierający instrukcję CONTINUE w pętlach przestanie się kompilować
open_links, open_links_per_instance - oba parametry ustalają  maksymalną lczbę połączeń do innych baz danych per sesję/instancję. domyślna wartość dla obu parametrów to 4.  Jeśli mamy kod pobierający dane z większej ilości zewnętrznych baz, nie zostanie on skompilowany..
remote_depedencies_mode - jeśli w złożonych aplikacjach PL/SQL korzystających z wywołań RPC zmienia się wartość parametru z signature na timestamp -może to być źródłem błędów kompilacji i wykonania
plsql_ccflags - są to flagi kompilacji warunkowej.  Wg mnie to potencjalne źródło mnóstwa kłopotów.. Rozważmy poniższy przykład
CREATE OR REPLACE PROCEDURE PDB_Conditional_Compilation IS
BEGIN
 DBMS_OUTPUT.PUT_LINE('START');
 $IF $$SPECIAL_COMPILATION $THEN
  DBMS_OUTPUT.PUT_LINE('Special compilation');
 $END
 DBMS_OUTPUT.PUT_LINE('STOP');
END;
Jeśli podczas kompilacji procedury nie ustawimy parametru $$SPECIAL_COMPILATION w opcji plsql_ccflags, i zmienna nie będzie ustawiona na poziomie sesji, kod wykona się bez instrukcji
DBMS_OUTPUT.PUT_LINE('Special compilation');
Można oczywiście z wykorzystaniem kompilacji warunkowej napisać  wyjątkowo złośliwy kod jak poniżej, ale jest to przypadek teoretyczny:
   $IF dbms_db_version.ver_le_11 $THEN
           ....kod....
   $ELSE
       RAISE_APPLICATION_ERROR(-20100, 'Kto mnie wykryje ??');
   $END

nls_length_semantics - problem został dokładniej omówiony w poniższym linku
Semantyka bajtowa i znakowa a poprawność kodu
Ustawienie zmiennej na semantykę bajtową może powodować błędy kompilacji np. poniższa deklaracja zmiennej:
 vc_Test VARCHAR2(4) :=  'kość';
wewnątrz bloku stanie się przyczyną błędu:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
Taki sam błąd (ale już wykonania, nie kompilacji) wygeneruje poniższy blok:
DECLARE
 vc_Test VARCHAR2(4);
BEGIN
  vc_Test  :=  'kość';
END;