Как выполнить внутреннее соединение трех таблиц с помощью XMLTABLE в Oracle

У меня есть таблица с именем XML_INFRASTRUCTURE, которая имеет следующий дизайн:

COLUMN_NAME  | DATA_TYPE          | NULLABLE
-------------|--------------------|--------
XMLI_ID      | NUMBER(10,0)       | No
FILENAME     | VARCHAR2(255 CHAR) | Yes
LAST_VERSION | DATE               | Yes
XML_RAW      | CLOB               | Yes

В этой таблице я храню все файлы XML, которые я получаю через FTP, как XMLTYPE. Я использую XMLTABLE для получения информации, и все работает хорошо, пока я не начну объединять таблицы.

В XML_INFRASTRUCTURE у меня есть следующие данные:

XMLI_ID | FILENAME     | LAST_VERSION | XML_RAW
--------|--------------|--------------|--------------------------------
1       | ptcar        | 07-JAN-18    | <?xml version="1.0" encoding="ISO-8859-1"?><cern:ptcars creationDate="2018-03-16T19:35:54" xmlns:cern="http://www.website.com/Infrastructure" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.website.com/Infrastructure ../ns/infrastructure.xsd"><cern:ptcar id="1" validFromDate="1996-06-02" validToDate="2007-12-08" ....>
2       | ptrefColumn  | 07-JAN-18    | <?xml version="1.0" encoding="ISO-8859-1"?><cern:ptrefColumns creationDate="2018-03-20T11:33:21" xmlns:cern="http://www.website.com/Infrastructure" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.website.com/Infrastructure ../ns/infrastructure.xsd"><cern:ptrefColumn id="279" validFromDate="1998-04-01" validToDate="2001-06-11" ....> 
3       | ptref        | 07-JAN-18    | <?xml version="1.0" encoding="ISO-8859-1"?><cern:ptrefs creationDate="2018-03-20T11:33:05" xmlns:cern="http://www.website.com/Infrastructure"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.website.com//Infrastructure ../ns/infrastructure.xsd"><cern:ptref id="232" validFromDate="1998-04-01" validToDate="2001-06-11" ....>

И далее у меня есть следующие требования:

Заданные значения: ptrefId и GivenDate.

select a.longNameFrench , a.longNameDutch
from   ptcar as a, 
       ptrefColumn as b,
       ptref as c
where c.id = ptrefId
and b.id = c.ptrefColumnId
and a.id = b.ptcarId
and a.validFromDate <= givenDate
and a.validToDate >= givenDate
and b.validFromDate <= givenDate
and b.validToDate >= givenDate
and c.validFromDate <= givenDate
and c.validToDate >= givenDate

Зная это, я попытался получить XML с помощью XMLTABLE, но я понятия не имею, как запустить соединение. Как видите, я попытался связать XMLTABLE в цепочку, но в таком виде он работает уже больше часа.

SELECT X.LongNameFrench, X.LongNameDutch
FROM XML_Infrastructure,
    XMLTABLE(
        '$d/*:ptcars/*:ptcar'
        PASSING XMLTYPE(XML_Infrastructure.XML_RAW) as "d"
        COLUMNS
            Id              VARCHAR2(10)    PATH    '@*:id',
            LongNameFrench  VARCHAR2(60)    PATH    '@*:longNameFrench',
            LongNameDutch   VARCHAR2(60)    PATH    '@*:longNameDutch',
            ValidFromDate   VARCHAR2(10)    PATH    '@*:validFromDate',
            ValidToDate     VARCHAR2(10)    PATH    '@*:validToDate'
    ) AS X,
    XMLTABLE(
        '$d/*:ptrefColumns/*:ptrefColumn'
        PASSING XMLTYPE(XML_Infrastructure.XML_RAW) as "d"
        COLUMNS
            Id              VARCHAR2(10)    PATH    '@*:id',
            ValidFromDate   VARCHAR2(10)    PATH    '@*:validFromDate',
            ValidToDate     VARCHAR2(10)    PATH    '@*:validToDate'
    ) AS Y,
    XMLTABLE(
        '$d/*:ptrefs/*:ptref'
        PASSING XMLTYPE(XML_Infrastructure.XML_RAW) as "d"
        COLUMNS
            Id              VARCHAR2(10)    PATH    '@*:id',
            ValidFromDate   VARCHAR2(10)    PATH    '@*:validFromDate',
            ValidToDate     VARCHAR2(10)    PATH    '@*:validToDate'
    ) AS Z
WHERE Z.Id = '512'
AND FILENAME = 'ptcar';

Предложения очень приветствуются! (извините за переизбыток информации)


person Jordec    schedule 18.04.2018    source источник
comment
Ваш XML неполный, что не совсем помогает и, похоже, не соответствует вашим результатам - идентификатор 512, который вы ищете, не существует, поэтому я думаю, вы имеете в виду 232 здесь; но файл ptcar не содержит данных из двух других XML-документов. Итак, предположительно, вам нужно объединить данные из XML из всех трех строк. Итак, как же связаны три строки в таблице — исключительно по дате last_version?   -  person Alex Poole    schedule 18.04.2018
comment
@AlexPoole связь между всеми XML-файлами указана в требованиях. ptcar.Id = ptrefColumn.Id, ptrefColumn.Id = ptref.ptrefColumnId и c.id = ptrefId (данная переменная)   -  person Jordec    schedule 18.04.2018
comment
Однако ваши три XML-документа имеют разные идентификаторы, и ни один из них не равен 512. Предположим, что все они относятся к одному и тому же узлу id. Итак, все ваши примеры должны иметь id="512"?   -  person Alex Poole    schedule 18.04.2018
comment
На самом деле 512 был просто примером. XML содержит примерно от 5000 до 15000 строк, поэтому он просто указывает, что в XML_RAW хранится XML.   -  person Jordec    schedule 18.04.2018


Ответы (2)


Сделайте выбор (или создайте представления) следующим образом:

CREATE VIEW ptcar AS
SELECT Id, LongNameFrench, LongNameDutch,
    TO_DATE(x.ValidFromDate, 'YYYY-MM-DD') as ValidFromDate,
    ...
FROM XML_Infrastructure,
    XMLTABLE(
        '$d/*:ptcars/*:ptcar'
        PASSING XMLTYPE(XML_Infrastructure.XML_RAW) as "d"
        COLUMNS
            Id              VARCHAR2(10)    PATH    '@*:id',
            LongNameFrench  VARCHAR2(60)    PATH    '@*:longNameFrench',
            LongNameDutch   VARCHAR2(60)    PATH    '@*:longNameDutch',
            ValidFromDate   VARCHAR2(10)    PATH    '@*:validFromDate',
            ValidToDate     VARCHAR2(10)    PATH    '@*:validToDate'
    ) as x;

CREATE VIEW ptrefColumn AS
SELECT Id,
    TO_DATE(x.ValidFromDate, 'YYYY-MM-DD') as ValidFromDate,
    ...
FROM XML_Infrastructure,
XMLTABLE(
    '$d/*:ptrefColumns/*:ptrefColumn'
    PASSING XMLTYPE(XML_Infrastructure.XML_RAW) as "d"
    COLUMNS
        Id              VARCHAR2(10)    PATH    '@*:id',
        ValidFromDate   VARCHAR2(10)    PATH    '@*:validFromDate',
        ValidToDate     VARCHAR2(10)    PATH    '@*:validToDate'
) as x

После этого вы можете присоединиться к ним напрямую, как указано в вашем вопросе.

NB, почему вы храните XML_RAW как CLOB, а не XMLTYPE?

person Wernfried Domscheit    schedule 18.04.2018
comment
У меня была такая же идея, но я не знал, как это сделать. Просмотры могут быть хорошим решением. Я попробую это сейчас. Что касается типа данных для XML_RAW, я, честно говоря, не могу вспомнить, но я предполагаю, что это связано либо с удобством, либо с размером XML, в который легче вставлять clob. - person Jordec; 18.04.2018
comment
Итак, после тестирования мне удалось получить все свои данные через 49,079 секунды, что все еще довольно много, зная, что вам нужно выполнить 6 одновременных запросов для загрузки 1 страницы. - person Jordec; 18.04.2018
comment
@JorisDecraecker, возможно, вы можете создать несколько индексов, посмотрите Индексы для Данные типа XML - person Wernfried Domscheit; 18.04.2018
comment
Я посмотрю на индексацию в следующем выпуске. Я сделал небольшой инструмент для сжатия XML-файлов и сумел уменьшить размер всех XML-файлов на 80%. время загрузки теперь составляет 4,3 с вместо 49,079 с! - person Jordec; 23.04.2018

Вы создаете три результата XMLTable X, Y и Z из одного и того же XML-документа, и в нем есть только узел ptcar, поэтому Y и Z не находят никаких данных (поскольку нет узлов, соответствующих этим XPath).

Учитывая сходство между тремя XML-документами и предполагая, что показанные вами узлы идентификаторов должны иметь одно и то же значение (что не так в вашем примере данных), вы можете использовать одну XMLTable для извлечения всех соответствующие данные из всех документов:

SELECT X.Name, X.Id, X.ValidFromDate, X.ValidToDate, X.LongNameFrench, X.LongNameDutch
FROM XML_Infrastructure
CROSS JOIN XMLTABLE(
        '$d/*/*'
        PASSING XMLTYPE(XML_Infrastructure.XML_RAW) as "d"
        COLUMNS
            Name            VARCHAR2(10)    PATH    './local-name()',
            Id              NUMBER          PATH    '@*:id',
            LongNameFrench  VARCHAR2(60)    PATH    '@*:longNameFrench',
            LongNameDutch   VARCHAR2(60)    PATH    '@*:longNameDutch',
            ValidFromDate   DATE            PATH    '@*:validFromDate',
            ValidToDate     DATE            PATH    '@*:validToDate'
    ) X;

Это использует подстановочные знаки для получения любого дочернего узла, но вы можете отфильтровать это, если у вас есть другие типы, которые вы не показали. Он также добавляет столбец name, чтобы вы могли указать, из какого документа взята каждая строка (или вы можете включить имя файла, если хотите). Это даст нулевые значения для атрибутов, которых нет во всех трех.

А затем используйте это в CTE и дважды соедините это с самим собой:

WITH cte AS (
  SELECT X.Name, X.Id, X.ValidFromDate, X.ValidToDate, X.LongNameFrench, X.LongNameDutch
  FROM XML_Infrastructure
  CROSS JOIN XMLTABLE(
          '$d/*/*'
          PASSING XMLTYPE(XML_Infrastructure.XML_RAW) as "d"
          COLUMNS
              Name            VARCHAR2(30)    PATH    './local-name()',
              Id              NUMBER          PATH    '@*:id',
              LongNameFrench  VARCHAR2(60)    PATH    '@*:longNameFrench',
              LongNameDutch   VARCHAR2(60)    PATH    '@*:longNameDutch',
              ValidFromDate   DATE            PATH    '@*:validFromDate',
              ValidToDate     DATE            PATH    '@*:validToDate'
      ) AS X
)
select a.longNameFrench, a.longNameDutch
from cte a
join cte b on b.id = a.id
join cte c on c.id = b.id
where c.name = 'ptref'
and b.name = 'ptrefColumn'
and a.name = 'ptcar'
and c.id = ptrefId
and a.validFromDate <= givenDate
and a.validToDate >= givenDate
and b.validFromDate <= givenDate
and b.validToDate >= givenDate
and c.validFromDate <= givenDate
and c.validToDate >= givenDate;

Это чем-то похоже на создание представлений запросов для каждого типа документа и их последующее объединение, но без необходимости в каких-либо новых постоянных объектах.

С вашими частичными данными выборки в другом CTE и установкой всех идентификаторов на 512 и добавлением отсутствующих имен в ptcar:

with XML_INFRASTRUCTURE (XMLI_ID, FILENAME, LAST_VERSION, XML_RAW) as (
  select cast (1 as number(2,0)), cast('ptcar' as varchar2(255)), date '2018-01-07', to_clob('<?xml version="1.0" encoding="ISO-8859-1"?>
<cern:ptcars creationDate="2018-03-16T19:35:54" xmlns:cern="http://www.website.com/Infrastructure" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.website.com/Infrastructure ../ns/infrastructure.xsd">
 <cern:ptcar id="512" validFromDate="1996-06-02" validToDate="2007-12-08" longNameFrench="Jean Dupont" longNameDutch="Jan Jansen"/>
</cern:ptcars>') from dual
  union all select 2, 'ptrefColumn', date '2018-01-07', to_clob('<?xml version="1.0" encoding="ISO-8859-1"?>
<cern:ptrefColumns creationDate="2018-03-20T11:33:21" xmlns:cern="http://www.website.com/Infrastructure" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.website.com/Infrastructure ../ns/infrastructure.xsd">
 <cern:ptrefColumn id="512" validFromDate="1998-04-01" validToDate="2001-06-11" />
</cern:ptrefColumns>') from dual
  union all select 3, 'ptref', date '2018-01-07', to_clob('<?xml version="1.0" encoding="ISO-8859-1"?>
<cern:ptrefs creationDate="2018-03-20T11:33:05" xmlns:cern="http://www.website.com/Infrastructure" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.website.com//Infrastructure ../ns/infrastructure.xsd">
 <cern:ptref id="512" validFromDate="1998-04-01" validToDate="2001-06-11" />
</cern:ptrefs>') from dual
),
cte AS (
  SELECT X.Name, X.Id, X.ValidFromDate, X.ValidToDate, X.LongNameFrench, X.LongNameDutch
  FROM XML_Infrastructure
  CROSS JOIN XMLTABLE(
          '$d/*/*'
          PASSING XMLTYPE(XML_Infrastructure.XML_RAW) as "d"
          COLUMNS
              Name            VARCHAR2(30)    PATH    './local-name()',
              Id              NUMBER          PATH    '@*:id',
              LongNameFrench  VARCHAR2(60)    PATH    '@*:longNameFrench',
              LongNameDutch   VARCHAR2(60)    PATH    '@*:longNameDutch',
              ValidFromDate   DATE            PATH    '@*:validFromDate',
              ValidToDate     DATE            PATH    '@*:validToDate'
      ) AS X
)
select a.longNameFrench, a.longNameDutch
from cte a
join cte b on b.id = a.id
join cte c on c.id = b.id
where c.name = 'ptref'
and b.name = 'ptrefColumn'
and a.name = 'ptcar'
and c.id = 512
and a.validFromDate <= date '2001-01-01'
and a.validToDate >= date '2001-01-01'
and b.validFromDate <= date '2001-01-01'
and b.validToDate >= date '2001-01-01'
and c.validFromDate <= date '2001-01-01'
and c.validToDate >= date '2001-01-01';

дает

LONGNAMEFRENCH                                               LONGNAMEDUTCH                                               
------------------------------------------------------------ ------------------------------------------------------------
Jean Dupont                                                  Jan Jansen                                                  
person Alex Poole    schedule 18.04.2018
comment
Это рабочее решение, и я понятия не имел о /local-name(), который действительно полезен. Проблема с этим решением заключается в том, что, поскольку я работаю с огромными объемами XML-данных, я получил свои данные только через 193,783 секунды, что может быть слишком долго. Это даже после того, как я сделал это как можно более конкретным. - person Jordec; 18.04.2018