Zacznijmy od stworzenia tabeli
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 "
THB. Ale jakiego rzeczywiście typu typu jest wynik powyższego zapytania ????
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:
THB
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:
THB
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