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

środa, 27 kwietnia 2011

NULL a poprawnośc algorytmów obliczeniowych..

Wartość NULL bywa przyczyną wielu błędów w algorytmach  obliczeniowych - większość  z nich wynika  braku inicjacji zmiennych:
 Rozważmy trzy przykłady:
1.Nie działa inicjalizacja dla typów zakotwiczonych w PL/SQL
CREATE TABLE TBL_TEST
 (FIELD NUMBER DEFAULT 0 NOT NULL);

 DECLARE
   vn_Number TBL_TEST.FIELD%TYPE;
 BEGIN
   IF vn_Number IS NULL THEN
    DBMS_OUTPUT.PUT_LINE('Inicjalizacja nie zadziałała');
   END IF;
 END;
 Powyższy przypadek jest dość podstępny- zakotwiczenia pobierają tylko informację o typie bez checków...
2.Funkcje agregujące

 CREATE TABLE TBL_TEST
 (FIELD NUMBER  );


ciąg instrukcji insert, takze z wartością NULL;
INSERT INTO TBL_TEST (FIELD) VALUES (NULL);
INSERT INTO TBL_TEST (FIELD) VALUES (1);
INSERT INTO TBL_TEST (FIELD) VALUES (8); 
COMMIT;
Wykonajmy teraz zapytanie:
SELECT SUM(FIELD), AVG(FIELD), AVG(NVL(FIELD,0)) FROM TBL_TEST
Otrzymane wyniki to
9        4.5         3
Powyższy wynik świadczy o tym, że funkcja AVG do liczenia średniej wybiera wartości tylko  NOT NULL.  Dlatego warto dokładnie przestudiować dokumentację opisującą działanie funkcji agregujących..

3. Operatory arytmetyczne

DECLARE
 vn_A NUMBER(2) :=1;
 vn_B NUMBER(2) ;
 vn_C NUMBER(3);
BEGIN
  vn_C := vn_A + vn_B;
  IF vn_C IS NULL THEN 
    DBMS_OUTPUT.PUT_LINE('RESULT IS NULL');
   END IF;
END
Dla operatorów arytmetycznych i wbudowanych funkcji matematycznych np. ABS i SIN, jeśli co najmniej jeden parametr jest NULL, to zwracana jest także wartość NULL..

Rozpatrując powyższe przykłady, nasuwa się oczywisty wniosek - należy unikać wartości NULL w algorytmach obliczeniowych.... są one źródłem bardzo złośliwych i podstępnych błędów
Proponuję zastosowanie poniższych zaleceń:
  • na etapie projektowania struktury baz danych dla pól tabel wykorzystywanych w algorytmach numerycznych powinno się wymuszać wartość  NOT NULL.  Wartość domyślna jest niekonieczna, niech lepiej zostanie bezpośrednio zainicjowana w kodzie
  •  należy zamiast zmiennych typu NUMBER, BINARY_INTEGER stosować zmienne typu SIMPLE_DOUBLE, SIMPLE_INTEGER, SIMPLE_FLOAT oraz inne z przedrostkiem SIMPLE, które wymagają inicjacji na poziomie deklaracji.. Wykonanie poniższego bloku
       DECLARE
           vn_Test SIMPLE_INTEGER;
       BEGIN
           vn_Test := 0;
       END;
zakończy się błędem: PLS-00218: a variable declared NOT NULL must have an initialization assignment. oprócz tego jakiekolwiek przypisanie wartości NULL do zmiennej vn_Test też wygeneruje wyjątek... Wyjątki wyłowią niebezpieczne fragmenty kodu w algorytmach..
  • Jeśli musimy użyć typu NUMBER(12,2) to wówczas najlepiej stworzyć  nagłówek pakietu do definicji własnych typów
     CREATE OR REPLACE PACKAGE PCKG_MyTypes IS
          SUBTYPE t_Number_12_2 IS NUMBER(12,2) NOT NULL;
     END;
     wówczas wykonanie poniższego bloku
     DECLARE
        vn_Test PCKG_MyTypes.t_Number_12_2;
     BEGIN
        vn_test := 0;
   END;
   także zakończy się wyjątkiem : PLS-00218: a variable declared NOT NULL must have an initialization assignment

środa, 13 kwietnia 2011

DBMS_METADATA - generowanie skryptów DDL

Bardzo ciekawym pakietem dostarczonym z bazą danych jest DBMS_METADATA – jego głównym zadaniem jest tworzenie skryptów DDL dla różnych obiektów bazodanowych..

Dokumentacji nie będę przepisywać, ale przedstawię kilka zastosowań

Należy zauważyć, że wywołania w instrukcjach SQL umożliwiają ograniczone możliwości wykorzystania API, skrypty PL/SQL pozwalają na bardziej zaawansowane mozliwości tworzenia skryptów DDL

  • Skrypt do tworzenia listy uprawnień dla bieżącego użytkownika:
SELECT
DBMS_METADATA.GET_GRANTED_DDL('ROLE_GRANT', USER)
            ||
DBMS_METADATA.GET_GRANTED_DDL('SYSTEM_GRANT', USER)
            ||
DBMS_METADATA.GET_GRANTED_DDL('OBJECT_GRANT', USER)
FROM DUAL
  • Skrypt do tworzenia indeksów w bieżącym schemacie
SELECT DBMS_METADATA.GET_DDL('INDEX', INDEX_NAME, USER) ||';' FROM USER_INDEXES WHERE VISIBILITY = 'VISIBLE'
  • Skrypt do tworzenia bieżącego użytkownika
SELECT DBMS_METADATA.GET_DDL('USER', USER)
FROM DUALpod warunkiem, że użytkownik nie jest SYS/SYSDBA
  • Przeniesienie tabel  ze schematu SOURCE_SCHEMA  do schematu DESTINATION_SCHEMA wszystkich z tabel z nazwami zaczynającymi się na B, z pominięciem atrybutów storage i  kluczy obcych…
zacznijmy od  stworzenia tabeli na wynikowy skrypt
CREATE  TABLE TBL_DDL_DATA(
DDL_SCRIPT CLOB DEFAULT EMPTY_CLOB()
);

poniżej skrypt
DECLARE
v_hdl NUMBER;
v_th1 NUMBER;
v_th2 NUMBER;
v_doc sys.ku$_ddls := sys.ku$_ddls();
   BEGIN
      delete from T_BLOB;
      v_hdl := DBMS_METADATA.OPEN('TABLE', 'LATEST');
      DBMS_METADATA.SET_FILTER (v_hdl,'SCHEMA_EXPR','
IN( ''SOURCE_SCHEMA'')');
      DBMS_METADATA.SET_FILTER (v_hdl,'NAME_EXPR','LIKE ''B%''');
      v_th1 := DBMS_METADATA.ADD_TRANSFORM (v_hdl,'MODIFY', NULL, 'TABLE');
      DBMS_METADATA.SET_REMAP_PARAM(v_th1,'REMAP_SCHEMA', 'SOURCE_SCHEMA', 'DESTINATION_SCHEMA');
      v_th2:=DBMS_METADATA.ADD_TRANSFORM(v_hdl,'DDL');
      DBMS_METADATA.SET_TRANSFORM_PARAM(v_th2,    'SQLTERMINATOR', TRUE);
DBMS_METADATA.SET_TRANSFORM_PARAM(v_th2,    'SEGMENT_ATTRIBUTES', FALSE);
DBMS_METADATA.SET_TRANSFORM_PARAM(v_th2,'REF_CONSTRAINTS', FALSE, 'TABLE');
DBMS_OUTPUT.PUT_LINE(DBMS_METADATA.GET_QUERY(v_hdl));
LOOP
   
    v_doc := DBMS_METADATA.FETCH_DDL(v_hdl);
    IF v_doc IS NOT EMPTY THEN
        IF v_doc.COUNT >0 THEN
            INSERT INTO TBL_DDL_DATA(DDL_SCRIPT)
            VALUES (v_doc(v_doc.last).DDLTEXT);
        END IF;
    END IF;
EXIT
WHEN v_doc IS NULL;
END LOOP;
COMMIT; 
DBMS_METADATA.CLOSE(v_hdl);
END;

Jeśli chcemy wykonać skrypt, zaleca się posiadać rolę CREATE TABLE do schematu docelowego

Moje uwagi:
  • z poziomu SQL nie ma możliwości parametryzacji skryptów - pominięcia parametrów storage, ustawienia filtra, zmiany schematu - to wszystko można zrobić w PL/SQL
  • Skrypty  dla lokalnych dla tabeli partycjonowanych działają dobrze pod warunkiem, że owe tabele są puste(zgodnie z założeniem pakiety – tworzenie  struktur, a nie ich rozbudowywanie)
  • Skrypty DDL dla tabel partycjonowanych  interwałowo niekiedy są generowane błędnie
  • Dłuższe skrypty opłaca się zapisywać do pól CLOB
  • Pakiet jest dość powolny