Как включить список объектов в XMLTABLE Oracle? - PullRequest
1 голос
/ 02 апреля 2020

У меня есть следующее (минимальное) XML:

<root>
  <person>
    <name>Miguel Martins</name>
    <age>32</age>
    <list_of_numbers>
      <number>1</number>
      <number>2</number>
    </list_of_numbers>
  </person>

  <person>
    <name>Another Person</name>
    <age>19</age>
    <list_of_numbers>
      <number>3</number>
      <number>4</number>
    </list_of_numbers>
  </person>
</root>

И следующий запрос:

with my_with_clause as
 (select '
<root>
  <person>
    <name>Miguel Martins</name>
    <age>32</age>
    <list_of_numbers>
      <number>1</number>
      <number>2</number>
    </list_of_numbers>
  </person>

  <person>
    <name>Another Person</name>
    <age>19</age>
    <list_of_numbers>
      <number>3</number>
      <number>4</number>
    </list_of_numbers>
  </person>
</root>
' my_xml
    from dual)
select t1.*
  from my_with_clause,
       xmltable('/root/person' passing xmltype(my_with_clause.my_xml) columns name path 'name', age path 'age') t1;

, который выдает следующий вывод:

+----------------+-----+
|      Name      | Age |
+----------------+-----+
| Miguel Martins |  32 |
| Another Person |  19 |
+----------------+-----+

Пока все хорошо. Теперь я хотел бы добавить числа в таблицу с псевдонимом T1. То есть мне нужен следующий вывод:

+----------------+-----+-------------+
|      Name      | Age | Some_Number |
+----------------+-----+-------------+
| Miguel Martins |  32 |           1 |
| Miguel Martins |  32 |           2 |
| Another Person |  19 |           3 |
| Another Person |  19 |           4 |
+----------------+-----+-------------+

Я попытался добавить столбец some_number в XMLTABLE. То есть:

with my_with_clause as
 (select '
<root>
  <person>
    <name>Miguel Martins</name>
    <age>32</age>
    <list_of_numbers>
      <number>1</number>
      <number>2</number>
    </list_of_numbers>
  </person>

  <person>
    <name>Another Person</name>
    <age>19</age>
    <list_of_numbers>
      <number>3</number>
      <number>4</number>
    </list_of_numbers>
  </person>
</root>
' my_xml
    from dual)
select t1.*
  from my_with_clause,
       xmltable('/root/person' passing xmltype(my_with_clause.my_xml) columns name path 'name', age path 'age', some_number path 'list_of_numbers/number') t1;

Однако я не получаю желаемый вывод. Вместо этого я получаю следующую ошибку:

ORA-19025: EXTRACTVALUE возвращает значение только одного узла

Как мне достичь желаемого результата? Вот SQLFiddle , чтобы вы могли попробовать (при необходимости).

Ответы [ 2 ]

2 голосов
/ 02 апреля 2020

Вы можете использовать цепочечные вызовы XMLTable:

select t1.name, t1.age, t2.some_number
from my_with_clause
cross join xmltable (
  '/root/person'
  passing xmltype(my_with_clause.my_xml)
  columns name varchar2(20) path 'name',
    age number path 'age',
    list_of_numbers xmltype path 'list_of_numbers/number'
) t1
cross join xmltable (
  '/number'
  passing t1.list_of_numbers
  columns some_number number path '.'
) t2;

NAME                        AGE SOME_NUMBER
-------------------- ---------- -----------
Miguel Martins               32           1
Miguel Martins               32           2
Another Person               19           3
Another Person               19           4

SQL Fiddle это не нравится , но db <> fiddle делает , и это работает локально против 11gR2. (На самом деле SQL Fiddle в порядке с реальной таблицей вместо CTE ...)

или

select t1.name, t1.age, t2.some_number
from my_with_clause
cross join xmltable (
  '/root/person'
  passing xmltype(my_with_clause.my_xml)
  columns name varchar2(20) path 'name',
    age number path 'age',
    list_of_numbers xmltype path 'list_of_numbers'
) t1
cross join xmltable (
  '/list_of_numbers/number'
  passing t1.list_of_numbers
  columns some_number number path '.'
) t2;

db <> fiddle

С помощью этого XML вы также можете сделать это с одной XMLTable, начиная с чисел и затем просматривая узлы для других данных:

select t1.name, t1.age, t1.some_number
from my_with_clause
cross join xmltable (
  '/root/person/list_of_numbers/number'
  passing xmltype(my_with_clause.my_xml)
  columns name varchar2(20) path './../../name',
    age number path './../../age',
    some_number number path '.'
) t1;

NAME                        AGE SOME_NUMBER
-------------------- ---------- -----------
Miguel Martins               32           1
Miguel Martins               32           2
Another Person               19           3
Another Person               19           4

SQL Fiddle и db <> fiddle .

, но ваш реальный (не минимальный) XML может не сделать это практичным.


Это также работает с таблицами с несколькими строками, а не только с CTE или таблицей с одним значением XML для распаковки.

Если у вас может быть сценарий, в котором list_of_names отсутствует или пуст, и вы все еще хотите показать имя / возраст, вы можете использовать внешнее объединение вместо перекрестного, но для этого нужно уродливое предложение on 1=1. SQL Fiddle , показывающий перекрестное соединение и левое соединение для такого рода данных, но я бы не стал использовать этот подход с левым соединением, если вы можете.

Если вы используете 12 c или выше, вы можете использовать outer apply вместо left join ... on 1=1, что довольно менее оскорбительно. (И если вам не нужно беспокоиться о пропущенных номерах, вы можете использовать cross apply вместо cross join, как показало @Lukasz - похоже, здесь ничего не изменится.)


ORA-19025 интересно. SQL Fiddle работает Oracle База данных 11g Express Edition, выпуск 11.2.0.2.0. В Enterprise Edition 11.2.0.4 ваш код получает

ORA-19279: XPTY0004 - XQuery dynamici c несоответствие типов: ожидаемая одноэлементная последовательность - получена последовательность из нескольких элементов

вместо этого, но это та же проблема; под каждым человеком есть несколько number узлов, и он не знает, что с ними делать, как тип данных по умолчанию - поскольку вы не указали типы данных, все возвращается в виде строк. В моей первой версии я использую тот же путь, но объявляю этот столбец как XMLType, поэтому вы получаете числа в виде фрагмента XML, как в первой версии:

<number>1</number><number>2</number>

или во второй :

<list_of_numbers><number>1</number><number>2</number></list_of_numbers>

Затем они могут быть использованы цепным вызовом XMLTable.

1 голос
/ 02 апреля 2020

Если у вас более одной строки, вы можете легко «связать» XMLTABLE с помощью CROSS/OUTER APPLY:

select t.id, t1.Name, t1.Age, t2."number"
from t
CROSS APPLY xmltable('/root/person' passing xmltype(t.my_xml) 
                     columns name path 'name', age path 'age', 
                     list_of_numbers xmltype path 'list_of_numbers/number') t1
CROSS APPLY xmltable('/number' passing t1.list_of_numbers 
                     columns  "number" number path '.') t2

db <> fiddle demo

Основная таблица - это t, t1 ссылается на xmltype (t.my_ xml), а t2 ссылается на проанализированный XML с использованием t1.list_of_numbers.


Добавление :

CROSS APPLY и CROSS JOIN эквивалентны, когда все строки XMLTABLE дают:

declare
  x VARCHAR2(2000);
begin
 dbms_utility.expand_sql_text(
        input_sql_text => q'{
          select t.id, t1.Name, t1.Age, t2."number"
from t
CROSS JOIN xmltable('/root/person' passing xmltype(t.my_xml) columns name path 'name', age path 'age', list_of_numbers xmltype path 'list_of_numbers/number') t1
CROSS JOIN xmltable('/number' passing t1.list_of_numbers columns  "number" number path '.') t2
}',
        output_sql_text => x);

  dbms_output.put_line(x);
end;
/

declare
  x VARCHAR2(2000);
begin
 dbms_utility.expand_sql_text(
        input_sql_text => q'{
          select t.id, t1.Name, t1.Age, t2."number"
from t
CROSS APPLY xmltable('/root/person' passing xmltype(t.my_xml) columns name path 'name', age path 'age', list_of_numbers xmltype path 'list_of_numbers/number') t1
CROSS APPLY xmltable('/number' passing t1.list_of_numbers columns  "number" number path '.') t2
}',
        output_sql_text => x);

  dbms_output.put_line(x);
end;
/

дБ < > fiddle demo


Разница видна, когда у нас XML как:

<root>
  <person>
    <name>XXXX</name>
    <age>32</age>
  </person>
</root>

select t.id, t1.Name, t1.Age, t2."number"
from t
OUTER APPLY xmltable('/root/person' passing xmltype(t.my_xml) columns name path 'name', age path 'age', list_of_numbers xmltype path 'list_of_numbers/number') t1
OUTER APPLY xmltable('/number' passing t1.list_of_numbers columns  "number" number path '.') t2

CROSS JOIN - 0 строк

НАРУЖНОЕ ПРИМЕНЕНИЕ - 1 ряд

...