Запрос к таблицам БД, получение XML вместо набора результатов
В большинстве современных корпоративных приложений есть постоянно меняющаяся область, заставляющая разработчиков каждый раз пересматривать модель данных: часть кода, в которой таблицы БД отображаются в структуру XML. То, как это делается, варьируется от одного случая к другому, но цель всегда одна — создать XML из реляционных данных, а более сложная часть — сделать это в обратном порядке, обновить базу данных на основе входящих данных в формате XML.
Возможные решения:
-
Вероятно, худшее решение, которое вы можете выбрать, это запросить базу данных, интерпретировать набор результатов и построить DOM в необработанном коде. а затем закодируйте все это снова и прочитайте DOM, интерпретируйте его и создайте оператор обновления. Почему? Вы только что сгенерировали огромное количество связующего кода, и отображение очень сложно поддерживать.
-
Лучший подход — создавать изолированные бизнес-объекты в соответствии с желаемой структурой XML, заполнять их данными из запроса и сериализовать / маршалировать их в XML с помощью JAXB. Теперь гораздо проще реализовать это задом наперед. Вы даже можете обогатить это решение с помощью JPA, но будьте осторожны, демонтаж сущностей JPA из XML не является хорошей идеей при обновлении!
-
Круто и модно было бы использовать поддержку XQuery на вашем сервере БД. Как насчет запросов к данным БД и вместо набора результатов получить нужный XML-документ? Но есть но. XQuery молод, он стал Рекомендацией W3C 23 января 2007 года. Поддержка в машинах БД все еще развивается, и, на мой взгляд, наиболее важным «но» является то, что он все еще смешивает два языка (SQL и XQuery) для выполнения одного запроса. Это многообещающе, но …
-
Мой любимый SQL / XML или сокращенный SQLX, мы вернемся к нему сразу после короткого примера XQuery.
Будь крут с XQuery
«Но» реализация отличается в Oracle DB и IBM DB2. Ниже приведен пример для DB2, в Oracle DB вы не можете (вы можете использовать sqlplus, но это совсем другая история) напрямую заменить оператор SQL на XQuery, вы должны использовать функции XMLQuery () и XMLTable () и фактически выбирать из результата XQuery. Такое встраивание XQuery в оператор SQL возможно в обоих случаях с небольшими отличиями.
оракул
SELECT XMLQUERY('$y/desc/title' PASSING indoc AS "y" RETURNING CONTENT) INTO titlexml FROM HR.HR_DOCS;
DB2
SELECT XMLQUERY('$y/desc/title' PASSING indoc AS "y") INTO titlexml FROM HR.HR_DOCS;
В DB2 можно встроить SQL-запрос в оператор XQuery, как в этом примере
xquery <ErrorCatalog>{ for $y in db2-fn:sqlquery( 'SELECT XMLELEMENT(NAME "TmpErr", XMLATTRIBUTES(e.ERRCODE AS "code"), XMLTEXT(e.DESC)) FROM CC.CC_ERROR e') where $y/@code=404 return ( <Error> {$y/@code} {$y/text()}</Error> )}</ErrorCatalog>
Выход
<ErrorCatalog> <Error code="404"> The requested resource could not be found but may be available again in the future. Subsequent requests by the client are permissible. </Error> </ErrorCatalog>
Если вы зайдете достаточно глубоко, вы обнаружите, что обратное встраивание XQuery в SQL, с другой стороны, немного более удобно в БД Oracle.
Но при попытке использовать XQuery вы сталкиваетесь с XMLELEMENT (), XMLFOREST (), XMLAGG () и так далее. И это SQL / XML, инструмент, с помощью которого сам SQL может создавать XML.
SQL / XML
Краткая история SQL / XML
Все началось в октябре 2000 года, когда отраслевой консорциум под названием SQLX Group начал работу по расширению поддержки SQL для запросов XML из базы данных. Основными отраслями SQLX Group являются такие известные имена, как Hewlett-Packard, IBM, Microsoft, Oracle, Sybase и многие другие. Первая редакция SQL появилась 3 года спустя,
SQL: 2003-14 принес базовые возможности XML, как мы их знаем, а спустя годы XQuery начал присоединяться к игре. Функция XMLTABLE () появилась в версии SQL / XML: 2006.
Начнем с простого выбора
Мы будем использовать функцию XMLELEMENT и XMLAGG, которые объединяют все найденные строки под одним родительским элементом. XMLAGG — действительно мощный инструмент, потому что с
GROUP BY можно сортировать сгруппированные строки как элементы по разным родителям. Но оставь это сейчас.
SELECT XMLELEMENT(NAME "PhoneBook", -- root element name XMLAGG(-- aggregation over the rows XMLELEMENT(NAME "Contact", XMLATTRIBUTES(cust.FIRST_NAME AS "Name", cust.TEL) ) ) ) FROM TMP.CUSTOMER AS cust;
Выход
<PhoneBook> <Contact Name="Daniel" TEL="788255855"/> <Contact Name="Martin" TEL="889665447"/> <Contact Name="Eva" TEL="111222333"/> <Contact Name="Alena" TEL="444555666"/> <Contact Name="Oliver" TEL="777888999"/> <Contact Name="George" TEL="444882446"/> <Contact Name="Jamie" TEL="123456789"/> </PhoneBook>
Чтобы увидеть результат сразу же, мы должны сериализовать его:
XMLSERIALIZE (XMLELEM …) AS VARCHAR (500))
или же:
XMLSERIALIZE (XMLELEM …) КАК КЛОБ (500), ВКЛЮЧАЯ XMLDECLARATION)
Включение объявления xml добавляет <? Xml version … так что он стал полноценным XML-документом, но работает только в DB2. Еще одним преимуществом сериализации xml является то, что нам не нужен какой-либо специальный новый драйвер jdbc, который знает типы XML.
Пространства имен
Функция XMLNAMESPACES () доступна только в DB2 8.2 и выше, странным образом в Oracle вы можете использовать XMLNAMESPACES () в EXTRACT (), но не в XMLELEMENT (), в Oracle и PostgreSql использовать функцию XMLATTRIBUTES () и добавлять xmlns вручную.
Пространство имен по умолчанию в DB2
SELECT XMLSERIALIZE(CONTENT XMLELEMENT(NAME "PhoneBook", XMLNAMESPACES(DEFAULT 'http://daniel.kecovi.cz/tmp/1.0'), XMLAGG( XMLELEMENT(NAME "Contact", XMLATTRIBUTES(cust.FIRST_NAME||' '||cust.LAST_NAME AS "Name", cust.TEL) ) ))AS VARCHAR(500)) FROM TMP.CUSTOMER AS cust;
Пространство имен по умолчанию в Oracle
SELECT XMLSERIALIZE(CONTENT XMLELEMENT(NAME "PhoneBook", XMLATTRIBUTES('http://daniel.kecovi.cz/tmp/1.0' AS "xmlns"), XMLAGG( XMLELEMENT(NAME "Contact", XMLATTRIBUTES(cust.FIRST_NAME||' '||cust.LAST_NAME AS "Name", cust.TEL) ) ))AS VARCHAR(500)) FROM TMP.CUSTOMER AS cust;
Выход
<PhoneBook xmlns="http://daniel.kecovi.cz/tmp/1.0"> <Contact Name="Daniel Kec" TEL="788255855"/> <Contact Name="Martin Kec" TEL="889665447"/> <Contact Name="Eva Kec" TEL="111222333"/> <Contact Name="Alena Kec" TEL="444555666"/> <Contact Name="Oliver Kec" TEL="777888999"/> <Contact Name="George Takei" TEL="444882446"/> <Contact Name="Jamie Oliver" TEL="123456789"/> </PhoneBook>
Декларация пространства имен различается в oracle и postgre, вам нужно вручную добавить атрибут следующим образом: XMLATTRIBUTES (‘http://daniel.kecovi.cz/tmp/1.0’ AS «xmlns»)
Должен ли я повториться?
There is a thing with which you are going to meet when building some larger documents. One element which is on multiple different places in the document tree. You don’t have to write redundant code in your SQL query, because there is WITH. With clause allows you to
create common table expression before query itself, so why not use it as a function? Instead of pre preparing table we can just pre prepare table with reusable XML fragment.
Tested on DB2 9.7 and Oracle 11g only difference is dummy table name
WITH -- prepared to create Customer element, -- lastName column is possible filtering tmpCustomer(lastName,custEl) AS( SELECT LAST_NAME,XMLELEMENT(NAME "Customer", XMLATTRIBUTES(FIRST_NAME||' '||LAST_NAME AS "FullName" ) ) FROM TMP.CUSTOMER cust ) -- main select SELECT XMLSERIALIZE( CONTENT XMLELEMENT(NAME "CustomerList", -- using prepared Customer (SELECT XMLAGG(t1.custEl) FROM tmpCustomer t1), XMLELEMENT(NAME "KecFamily", -- using prepared Customer -- and filtering only those with last name kec (SELECT XMLAGG(t2.custEl) FROM tmpCustomer t2 WHERE t2.lastName='Kec') ) )AS CLOB) -- in this example using dummy table -- (dual for oracle and SYSIBM.SYSDUMMY1 for db2) --FROM DUAL; FROM SYSIBM.SYSDUMMY1;
Output
<CustomerList> <Customer FullName="Daniel Kec" /> <Customer FullName="Martin Kec" /> <Customer FullName="Eva Kec" /> <Customer FullName="Alena Kec" /> <Customer FullName="Oliver Kec" /> <Customer FullName="George Takei" /> <Customer FullName="Jamie Oliver" /> <KecFamily> <Customer FullName="Daniel Kec" /> <Customer FullName="Martin Kec" /> <Customer FullName="Eva Kec" /> <Customer FullName="Alena Kec" /> <Customer FullName="Oliver Kec" /> </KecFamily> </CustomerList>
Ah please I need variables
Little help with variables brings WITH.
On DB2 simple like this
WITH vars( DATETIMEMASK, -- datetime mask DATEMASK, -- date mask TIMEMASK -- only time mask ) AS (VALUES( 'DD.MM.YYYY HH24:MI', 'DD.MM.YYYY', 'HH24:MI')) -- main select SELECT XMLSERIALIZE( CONTENT XMLELEMENT(NAME "CustomerList", XMLAGG( XMLELEMENT(NAME "Customer", XMLATTRIBUTES(c.FIRST_NAME AS "Name", TO_CHAR(c.LAST_ORDER,vars.DATEMASK) AS "LastOrder" ) ) ) )AS CLOB) FROM vars,TMP.CUSTOMER c;
Oracle need SELECT something
WITH vars( DATETIMEMASK, -- datetime mask DATEMASK, -- date mask TIMEMASK -- only time mask ) AS (SELECT 'DD.MM.YYYY HH24:MI', 'DD.MM.YYYY', 'HH24:MI' FROM DUAL) -- main select SELECT XMLSERIALIZE( CONTENT XMLELEMENT(NAME "CustomerList", XMLAGG( XMLELEMENT(NAME "Customer", XMLATTRIBUTES(c.FIRST_NAME AS "Name", TO_CHAR(c.LAST_ORDER,vars.DATEMASK) AS "LastOrder" ) ) ) )AS CLOB) FROM vars,TMP.CUSTOMER c;
Output
<CustomerList> <Customer Name="Daniel" LastOrder="22.05.2013" /> <Customer Name="Martin" LastOrder="22.05.2013" /> <Customer Name="Eva" LastOrder="22.05.2013" /> <Customer Name="Alena" LastOrder="22.05.2013" /> <Customer Name="Oliver" LastOrder="22.05.2013" /> <Customer Name="George" LastOrder="22.05.2013" /> <Customer Name="Jamie" LastOrder="22.05.2013" /> </CustomerList>
Expression as element name
Maybe you wonder how to make an element with name other than constant. On Oracle you can use EVALNAME() function:
SELECT XMLSERIALIZE(CONTENT XMLELEMENT(NAME "PhoneBook", XMLAGG( XMLELEMENT(EVALNAME(cust.FIRST_NAME), XMLATTRIBUTES(cust.LAST_NAME AS "LastName", cust.TEL) ) ))AS VARCHAR(500)) FROM KYTYR.CUSTOMER cust;
And back to the Table
We have been dealing only with querying tables until now but what about inserts and updates? It is no problem with XMLTABLE which enable us to map the XML document to temporary table with XPath. Then it’s old plain SQL again.
Example of updates which works on both Oracle 11g and DB2 9.7
INSERT INTO TMP.DISCOUNT_CODE (SELECT discount_code, rate FROM XMLTABLE('$doc/discount' PASSING XMLPARSE(DOCUMENT '<discount> <code>H</code> <rate>5</rate> </discount>') as "doc" COLUMNS discount_code CHAR PATH 'code', rate DECIMAL PATH 'rate' )); UPDATE TMP.DISCOUNT_CODE SET (discount_code, rate) = ( SELECT discount_code, rate FROM XMLTABLE('$doc/discount' PASSING XMLPARSE(DOCUMENT '<discount> <code>H</code> <rate>6</rate> </discount>') as "doc" COLUMNS discount_code CHAR PATH 'code', rate DECIMAL PATH 'rate' ) ) WHERE discount_code = 'H';
Recreating of the sample table TMP.CUSTOMER, tested on DB2 9.7 and ORACLE 11g
CREATE TABLE TMP.CUSTOMER ( ID int NOT NULL PRIMARY KEY, FIRST_NAME varchar(255), LAST_NAME varchar(255), LAST_ORDER TIMESTAMP, TEL DECIMAL(20) ); INSERT INTO TMP.CUSTOMER(ID, FIRST_NAME, LAST_NAME,LAST_ORDER, TEL) (SELECT id,first_name,last_name,CURRENT_TIMESTAMP,tel FROM XMLTABLE('$doc/PhoneBook/Contact' PASSING XMLPARSE(DOCUMENT '<PhoneBook> <Contact id="1" Name="Daniel" Surname="Kec" TEL="788255855"/> <Contact id="2" Name="Martin" Surname="Kec" TEL="889665447"/> <Contact id="3" Name="Eva" Surname="Kec" TEL="111222333"/> <Contact id="4" Name="Alena" Surname="Kec" TEL="444555666"/> <Contact id="5" Name="Oliver" Surname="Kec" TEL="777888999"/> <Contact id="6" Name="George" Surname="Takei" TEL="444882446"/> <Contact id="7" Name="Jamie" Surname="Oliver" TEL="123456789"/> </PhoneBook>') as "doc" COLUMNS id INT PATH '@id', first_name VARCHAR(50) PATH '@Name', last_name VARCHAR(50) PATH '@Surname', tel DECIMAL(20) PATH '@TEL' ));
TMP.CUSTOMER
ID | FIRST_NAME | LAST_NAME | LAST_ORDER | TEL |
1 | Daniel | Kec | 2013-05-21 10:28:26.614 | 788255855 |
2 | Martin | Kec | 2013-05-21 10:30:09.294 | 889665447 |
3 | Eva | Kec | 2013-05-16 10:30:10.422 | 111222333 |
4 | Alena | Kec | 2013-05-21 10:30:13.550 | 444555666 |
5 | Oliver | Kec | 2013-05-01 10:30:14.486 | 777888999 |
6 | George | Takei | 2013-02-05 10:30:19.246 | 444882446 |
7 | Jamie | Oliver | 2012-07-17 10:30:23.174 | 123456789 |
Comparison of XML functions support across DB machines
Comparison is not easy because lot of functions can be replaced by different approach or another function. So missing “+” in the table below means in the most cases that function has an alternative, for example XMLCOLATTVAL is almost synonym to XMLFOREST, XMLROOT is almost same thing as XMLDOCUMENT and so on.
Date of release | 2004 | 2006 | 2009 | 2012 | 2011 | 2003 | 2005 | 2010 |
funtions | DB2 8.2 | DB2 9.1 | DB2 9.7 | DB2 10.1 | PostgreSQL 9.1 | Oracle 10.1 | Oracle 10.2 | Oracle 11.2 |
XMLELEMENT | + | + | + | + | + | + | + | + |
XMLNAMESPACES | + | + | + | + | ||||
XMLATTRIBUTES | + | + | + | + | + | + | + | |
XMLAGG | + | + | + | + | + | + | + | + |
XMLFOREST | + | + | + | + | + | + | + | + |
XMLCOLATTVAL | + | + | + | |||||
XMLSEQUENCE | + | + | + | |||||
XMLSERIALIZE | + | + | + | + | + | + | ||
XMLCONCAT | + | + | + | + | + | + | + | + |
XMLTABLE | + | + | + | + | + | |||
XMLPARSE | + | + | + | + | + | |||
XMLDOCUMENT | + | + | + | |||||
XMLROOT | + | + | + | |||||
XMLCOMMENT | + | + | + | + | + | + | ||
XMLPI | + | + | + | + | + | + | ||
XMLCDATA | + | + | ||||||
XMLQUERY | + | + | + | + | + | |||
EXTRACT | + | + | + | |||||
UPDATEXML | + | + | + | |||||
XMLTEXT | + | + | + | |||||
XMLVALIDATE | + | + | + | |||||
XMLXSROBJECTID | + | + | + | |||||
XMLGROUP | + | + | ||||||
XMLROW | + | + | ||||||
XSLTRANSFORM | + | + | ||||||
XMLTRANSFORM | + | + | + | |||||
XPATH | + | |||||||
EXISTSNODE | + | + | + | |||||
XMLEXISTS | + | + | + | + | + | |||
XMLISVALID | + | |||||||
XPATH_EXISTS | + | |||||||
IS DOCUMENT | + | |||||||
xml_is_well_formed | + | |||||||
XMLTYPE | + | + | + |
I’m sorry for possible mistakes in this table it took hard googling to create comparison like this, if you find any inaccuracies leave the comment and I will fix it.
Resources:
- Jim Melton, Stephen Buxton. Querying XML, : XQuery, XPath, and SQL/XML in context, Morgan Kaufmann, 2006.
- Nick Ivanov, Romeo Lupascu, Olaf Mueller. Oracle to DB2 Conversion Guide: Compatibility Made Easy, An IBM Redbooks publication, 2012.
- http://www.sqlx.org
- DB2 Basics: An introduction to the SQL/XML publishing functions
- Using the XMLTABLE function in UPDATE and MERGE statements
- DB2 9.1 Supported functions and administrative SQL routines and views
- DB2 9.7 Infocenter
- DB2 10.1 Infocenter
- Oracle 10.1 Documentation library
- Oracle 10.2 Documentation library
- Oracle 11.2 Documentation library
- PostgreSQL 9.1 — XML Functions
- Edward Carson, Article about SQLX on developerfusion.com
- Comparison of different SQL implementations