4. dubna 2019

JAXP nebo JAXB? Raději SQL/XML


Java API for XML Processing (DOM Interface, SAX Interface) a Java Architecture for XML Binding jsou dobře známé technologie běžně používané pro práci s XML dokumenty v Java světě. Každá z nich má své výhody a nevýhody a z toho vyplývá vhodnost jejich použití pro konkrétní aplikace. Například SAX Parser se hodí pro sekvenční zpracování celých velkých a klidně i nevalidních dokumentů s minimálními nároky na paměť. JAXB zase umí XML schéma, může přistupovat do dokumentu náhodně, je možné dokument měnit a z objektového modelu zpět generovat do XML a to vše prakticky bez nutnosti znát XML. O tom už bylo jinde napsáno dost.

Žádná z těchto technologií ale neumí elegantně řešit přechod mezi světem relační databáze a světem XML, vždy je nutno pro tento účel realizovat nějaké procedurální zpracování.  Pokud je úkolem aplikace jen rozebírání XML dokumentu a ukládání dat z něj získaných do relační databáze nebo naopak generování XML dokumentů z relačních dat, existuje zajímá alternativa v rozšíření jazyka SQL o XML operace – SQL/XML. A jelikož se jedná o SQL, tak vše je výhradně deklarativní.

SQL/XML je definováno ve standardu ANSI.SQL ve verzi SQL-2006 a jako takové je implementováno v řadě databázových systémů aktuálních verzí (Oracle, MS SQL Server, PostgreSQL, IBM DB2, …). Jen je potřeba dát pozor na to, že implementace standardu ANSI.SQL v databázových systémech různých výrobců nejsou úplně totožné, standard nenařizuje vše a každý si vykládá standard po svém.
Oracle implementoval SQL/XML poprvé do své databáze verze 10g, plnohodnotně do verze 11g.

Dále uvedu dva základní příklady použití SQL/XML v databázi Oracle. Zdaleka se nejedná o úplný výčet možností SQL/XML, podrobněji viz. dokumentace Oracle: https://docs.oracle.com/en/database/oracle/oracle-database/18/adxdb/index.html

Generování XML z relačních dat

Následující příklad vygeneruje XML dokument SQL dotazem do relačních dat.
SELECT XMLElement("Stredisko",
         XMLAttributes(d.Department_id AS "CisloStrediska"),
         XMLForest(d.department_name AS "NazevStrediska"),
         XMLElement("Adresa",
            XMLForest(street_address AS "Ulice",
                      city AS "Mesto",
                      postal_code AS "PSC",
                      country_name AS "Stat")),
         XMLElement("SeznamZamestnancu",
           (SELECT XMLAgg(XMLElement("Zamestnanec",
                            XMLAttributes(e.employee_id AS "CisloZamestnance"),
                            XMLForest(e.first_name AS "Jmeno",
                              e.last_name AS "Prijmeni",
                              e.email AS "eMail",
                              e.phone_number AS "Telefon",
                              e.hire_date AS "PracovniPomerOd",
                              j.job_title AS "Pozice",
                              e.salary AS "Plat",
                              m.first_name || ' ' ||
                              m.last_name AS "Nadrizeny")))
            FROM hr.employees e, hr.employees m, hr.jobs j
            WHERE e.department_id = d.department_id
              AND j.job_id = e.job_id
              AND m.employee_id = e.manager_id))) AS XML
FROM hr.departments d, hr.countries c, hr.locations l
WHERE department_name = 'Executive'
  AND d.location_id = l.location_id
  AND l.country_id = c.country_id;

Výsledkem dotazu je datový typ XMLTYPE s následujícím obsahem.
<Stredisko IdStrediska="90">
    <JmenoStrediska>Executive</JmenoStrediska>
    <Adresa>
        <Ulice>2004 Charade Rd</Ulice>
        <Mesto>Seattle</Mesto>
        <PSC>98199</PSC>
        <Stat>United States of America</Stat>
    </Adresa>
    <SeznamZamestnancu>
        <Zamestnanec CisloZamestnance="101">
            <Jmeno>Neena</Jmeno>
            <Prijmeni>Kochhar</Prijmeni>
            <eMail>NKOCHHAR</eMail>
            <Telefon>515.123.4568</Telefon>
            <PracovniPomerOd>1989-09-21</PracovniPomerOd>
            <Pozice>Administration Vice President</Pozice>
            <Plat>17000</Plat>
            <Nadrizeny>Steven King</Nadrizeny>
        </Zamestnanec>
        <Zamestnanec CisloZamestnance="102">
            <Jmeno>Lex</Jmeno>
            <Prijmeni>De Haan</Prijmeni>
            <eMail>LDEHAAN</eMail>
            <Telefon>515.123.4569</Telefon>
            <PracovniPomerOd>1993-01-13</PracovniPomerOd>
            <Pozice>Administration Vice President</Pozice>
            <Plat>17000</Plat>
            <Nadrizeny>Steven King</Nadrizeny>
        </Zamestnanec>
    </SeznamZamestnancu>
</Stredisko>

SQL dotaz z XML dokumentu

V následujícím příkladu je pro jednoduchost dotaz prováděn nad XML přímo vloženým do SQL dotazu pomocí konstruktoru typu XMLTYPE(). Při reálném použití tam bude spíš proměnná nebo DB sloupec s typem XMLTYPE nebo konstruktor XMLTYPE() jehož parametr bude proměnná nebo DB sloupec nějakého textového typu (VARCHAR2, CLOB) nebo BLOB. Dotaz vytěží data z XML dokumentu. Dokument obsahuje oddělení, jejich zaměstnance a pro každého zaměstnance nepovinný seznam telefonních čísel. Jedná se tedy o tři joinované dotazy. Join se zde neprovádí přes PK/FK, protože ve vzorovém XML je vazba zařízena přes vnoření elementů. Join se v takovém případě neprovádí přes SQL JOIN, ale klauzulí PASSING v operátoru XMLTABLE, tak že do podřízeného dotazu se předá celý XML fragment obsahující vnořená data.

SELECT oddeleni.cislo_oddeleni,
       oddeleni.nazev_oddeleni,
       oddeleni.id,
       zamestnanec.osobni_cislo,
       zamestnanec.prijmeni,
       zamestnanec.profese,
       zamestnanec.id,
       telefon.telefonni_cislo
FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'cz:cca:testxmldb:1.0'),'/Document/department'
              PASSING XMLTYPE(
 '<?xml version="1.0" encoding="utf-8"?>
  <Document xmlns="cz:cca:testxmldb:1.0">
    <department deptno="10" dname="ACCOUNTIG">
      <employees>
        <employee empno="7369" ename="SMITH" job="CLERK" hiredate="17-DEC-1980">
          <phones>
            <phone>123456789</phone>
            <phone>666777888</phone>
          </phones>
        </employee>
        <employee empno="7876" ename="ADAMS" job="CLERK" hiredate="23-MAY-1987">
         <phones>
            <phone>000000000</phone>
          </phones>
        </employee>
        <employee empno="7900" ename="JAMES" job="CLERK" hiredate="03-DEC-1981"/>
        <employee empno="7934" ename="MILLER" job="CLERK" hiredate="23-JAN-1982"/>
        <employee empno="7698" ename="BLAKE" job="MANAGER" hiredate="01-MAY-1981"/>
      </employees>
    </department>
    <department deptno="20" dname="SALES">
      <employees>
        <employee empno="7521" ename="WARD" job="SALESMAN" hiredate="22-FEB-1981"/>
        <employee empno="7566" ename="JONES" job="MANAGER" hiredate="02-APR-1981"/>
        <employee empno="7654" ename="MARTIN" job="SALESMAN" hiredate="28-SEP-1981"/>
        <employee empno="7499" ename="ALLEN" job="SALESMAN" hiredate="20-FEB-1981"/>
        <employee empno="7902" ename="FORD" job="ANALYST" hiredate="03-DEC-1981">
          <phones>
            <phone>987654321</phone>
            <phone>111222333</phone>
          </phones>
        </employee>
        <employee empno="7844" ename="TURNER" job="SALESMAN" hiredate="08-SEP-1981"/>
      </employees>
    </department>
  </Document>')
       COLUMNS cislo_oddeleni NUMBER PATH '@deptno',
               nazev_oddeleni VARCHAR2(50) PATH '@dname',
               zamestnanci XMLTYPE PATH 'employees',
               id FOR ORDINALITY) oddeleni,
       XMLTABLE(XMLNAMESPACES(DEFAULT 'cz:cca:testxmldb:1.0'),
                '/employees/employee' PASSING oddeleni.zamestnanci COLUMNS
                osobni_cislo NUMBER PATH '@empno',
                prijmeni VARCHAR2(50) PATH '@ename',
                profese VARCHAR2(50) PATH '@job',
                telefony XMLTYPE PATH 'phones',
                id FOR ORDINALITY) zamestnanec,
       XMLTABLE(XMLNAMESPACES(DEFAULT 'cz:cca:testxmldb:1.0'),
                'phones/phone' PASSING zamestnanec.telefony COLUMNS
                telefonni_cislo VARCHAR2(50) PATH '.',
                id FOR ORDINALITY)(+) telefon;

CISLO_ODDELENI NAZEV_ODDELENI    ID OSOBNI_CISLO PRIJMENI   PROFESE   ID TELEFONNI_CISLO
-------------- --------------- ---- ------------ ---------- --------- -- ---------------
            10 ACCOUNTIG          1         7369 SMITH      CLERK      1 12345678      
            10 ACCOUNTIG          1         7369 SMITH      CLERK      1 66677788      
            10 ACCOUNTIG          1         7876 ADAMS      CLERK      2 00000000      
            10 ACCOUNTIG          1         7900 JAMES      CLERK      3               
            10 ACCOUNTIG          1         7934 MILLER     CLERK      4                
            10 ACCOUNTIG          1         7698 BLAKE      MANAGER    5               
            20 SALES              2         7521 WARD       SALESMAN   1               
            20 SALES              2         7566 JONES      MANAGER    2               
            20 SALES              2         7654 MARTIN     SALESMAN   3               
            20 SALES              2         7499 ALLEN      SALESMAN   4               
            20 SALES              2         7902 FORD       ANALYST    5 98765432      
            20 SALES              2         7902 FORD       ANALYST    5 11122233      
            20 SALES              2         7844 TURNER     SALESMAN   6               
                                                                                        
13 rows selected.                                                                                                  


Autor: Luděk Kratochvíl
           CCA Group a.s.
           ludek.kratochvil@cca.cz

Žádné komentáře:

Okomentovat