CREATE TABLE TBL_XML
(
CID NUMBER(6) NOT NULL,
CXML SYS.XMLTYPE NOT NULL,
UZYTKOWNIK VARCHAR2(30 CHAR) DEFAULT USER NOT NULL,
DATA_WSTAWIENIA DATE DEFAULT SYSDATE NOT NULL
)
Dlaczego został wybrany typ XMLTYPE , a nie CLOB ?? Ponieważ umożliwia wstawienie do kolumny CXML tylko poprawnego składniowo.
Jako xml wezmę początek pliku XML z kursami walut ze strony NBP na dzień 3 kwietnia 2020, zawierający pierwsze pięć węzłów.
<tabela_kursow typ="A" uid="20a066"> <numer_tabeli>066/A/NBP/2020</numer_tabeli> <data_publikacji>2020-04-03</data_publikacji> <pozycja> <nazwa_waluty>bat (Tajlandia)</nazwa_waluty> <przelicznik>1</przelicznik> <kod_waluty>THB</kod_waluty> <kurs_sredni>0,1287</kurs_sredni> </pozycja> <pozycja> <nazwa_waluty>dolar amerykański</nazwa_waluty> <przelicznik>1</przelicznik> <kod_waluty>USD</kod_waluty> <kurs_sredni>4,2396</kurs_sredni> </pozycja> <pozycja> <nazwa_waluty>dolar australijski</nazwa_waluty> <przelicznik>1</przelicznik> <kod_waluty>AUD</kod_waluty> <kurs_sredni>2,5527</kurs_sredni> </pozycja> <pozycja> <nazwa_waluty>dolar Hongkongu</nazwa_waluty> <przelicznik>1</przelicznik> <kod_waluty>HKD</kod_waluty> <kurs_sredni>0,5469</kurs_sredni> </pozycja> <pozycja> <nazwa_waluty>dolar kanadyjski</nazwa_waluty> <przelicznik>1</przelicznik> <kod_waluty>CAD</kod_waluty> <kurs_sredni>2,9956</kurs_sredni> </pozycja> </tabela_kursow>
Wstawiamy dane do tabeli:
INSERT INTO TBL_XML (CID, CXML) VALUES ( 1, '<tabela_kursow typ="A" uid="20a066"> <numer_tabeli>066/A/NBP/2020</numer_tabeli> <data_publikacji>2020-04-03</data_publikacji> <pozycja> <nazwa_waluty>bat (Tajlandia)</nazwa_waluty> <przelicznik>1</przelicznik> <kod_waluty>THB</kod_waluty> <kurs_sredni>0,1287</kurs_sredni> </pozycja> <pozycja> <nazwa_waluty>dolar amerykański</nazwa_waluty> <przelicznik>1</przelicznik> <kod_waluty>USD</kod_waluty> <kurs_sredni>4,2396</kurs_sredni> </pozycja> <pozycja> <nazwa_waluty>dolar australijski</nazwa_waluty> <przelicznik>1</przelicznik> <kod_waluty>AUD</kod_waluty> <kurs_sredni>2,5527</kurs_sredni> </pozycja> <pozycja> <nazwa_waluty>dolar Hongkongu</nazwa_waluty> <przelicznik>1</przelicznik> <kod_waluty>HKD</kod_waluty> <kurs_sredni>0,5469</kurs_sredni> </pozycja> <pozycja> <nazwa_waluty>dolar kanadyjski</nazwa_waluty> <przelicznik>1</przelicznik> <kod_waluty>CAD</kod_waluty> <kurs_sredni>2,9956</kurs_sredni> </pozycja> </tabela_kursow>')
Spróbujmy z pola XML pobrać kod waluty pierwszego węzła w sposób prawidłowy, aby wynik zapytania był typu VARCHAR2. Można do tego celu wykorzystać funkcje EXTRACT i EXTRACTVALUE. Ta druga jest lepsza co poniżej pokażemy:
Poniższe polecenie jest niepoprawne:
SELECT EXTRACT(CXML,'//pozycja[1]/kod_waluty' ) FROM TBL_XML;
Co nam zwróci - to zależy od edytora SQL.. w TOAD jest to tekst "
SELECT DUMP(EXTRACT(CXML,'//pozycja[1]/kod_waluty' )) FROM TBL_XML;
Otrzymamy:
Typ=58 Len=48: 128,68,239,156,252,127,0,0,192,142,121,1,178,1,0,0,32,3,128,1,178,1,0,..
Taki sam wynik otrzymamy dla zapytania:
SELECT DUMP(EXTRACT(CXML,'//pozycja[1]/kod_waluty/text()' )) FROM TBL_XML;
Co to jest typ 58 ?? wyjaśnienie znajduje się w pakiecie DBMS_TYPES:
TYPECODE_OPAQUE PLS_INTEGER := 58;
W dokumentacji przeczytamy, że jest to typ abstrakcyjny (w znaczeniu nie można wykorzystywać w PL/SQL), zaimplementowany jako ciąg bajtów..
Niektóre edytory SQL potrafią sobie z tym poradzić i wtedy mamy mylne złudzenie, że zapytanie zwraca wartość tekstową..
Wymusimy konwersję na tekst:
SELECT CAST(EXTRACT(CXML,'//pozycja[1]/kod_waluty' ) AS VARCHAR2 (30 CHAR)) FROM TBL_XML;
Wynik:
Jeśli wynusimy konwersję typu TYPECODE_OPAQUE do typu VARCHAR2 za pomocą funkcji GestStringVal()
SELECT EXTRACT(CXML,'//pozycja[1]/kod_waluty' ).GestStringVal() FROM TBL_XML;
Wynik:
wynik typu TYPECODE_VARCHAR
SELECT EXTRACT(CXML,'//pozycja[1]/kod_waluty/text()' ).GestStringVal() FROM TBL_XML;
Wynik:
THB
Wynik typu TYPECODE_VARCHAR
Z funkcję EXTRACTVALUE parsowanie jest prostsze.
SELECT EXTRACTVALUE(CXML,'//pozycja[1]/kod_waluty' ) FROM TBL_XML;
i
SELECT EXTRACTVALUE(CXML,'//pozycja[1]/kod_waluty/text()' ) FROM TBL_XML;
zwraca:
THB
Zarówno
SELECT DUMP(EXTRACTVALUE(CXML,'//pozycja[1]/kod_waluty' ) ) FROM TBL_XML;
jak i
SELECT DUMP(EXTRACTVALUE(CXML,'//pozycja[1]/kod_waluty/text()' )) FROM TBL_XML;
zwraca wynik:
Typ=1 Len=3: 84,72,66
czyli długość 3 znaki, gdzie 84,72,66 to kody ASCII poszczególnych liter
W pakiecie DBMS_TYPES znajdziemy
TYPECODE_VARCHAR PLS_INTEGER := 1;
Tu jest mała pułapka, bo typ VARCHAR2 nie rozróżnia wartości NULL i pustego tekstu, a typ VARCHAR to rozróżnia. Jest ona niegroźna gdyż puste znaczniki i atrybuty zwracane są jako NULL.
Podsumowując do prostego parsowania najlepsza jest funkcja EXTRACTVALUE, ponieważ wyłuskuje wartość znacznika
Brak komentarzy:
Prześlij komentarz