Oracle XML 多子节点查询

Oracle XML Query with multiple child nodes

我想实现对 XML 元素的查询,但找不到实现此目的的合适方法。

让我们考虑一下 table:

CREATE TABLE SUBSCRIPTIONS(
SUBSCRIPTION_ID NUMBER,
PARAMETERS_XML  CLOB
);

我的 XML 看起来像:

<Model>
  <Parameters>
    <Parameter>
      <userParameter>outlets</userParameter>
       <Values>
         <Value>45</Value>
         <Value>676</Value>
         <Value>1502</Value>
         ...
       </Values>
    </Parameter>
  </Parameters>
</Model>

我正在尝试使用此查询从 XML、参数名称和参数值中获取每个订阅:

SELECT
s.SUBSCRIPTION_ID,
s.PARAMETERS_XML,
xt.*
FROM EFNTA.SUBSCRIPTIONS s,
XMLTABLE(
    '/Model/Parameters/Parameter'
    PASSING XMLTYPE(s.PARAMETERS_XML)
    COLUMNS USER_PARAM  VARCHAR2(100)  PATH 'userParameter',
    PARAM_VALUE VARCHAR2(1000) PATH '//*:Values',
    CHECKED_LIST_EXCLUDE VARCHAR2(100) PATH '//*:CheckedListExclude'                        
) xt ORDER BY 1 DESC;

问题是当有多个值时,结果作为一个大的串联链返回:

有什么简单的方法可以用逗号、分号或其他任何方式分隔这些值吗? (我事先不知道值的数量)我找不到如何用 XPath 实现这个。

您可以将 Values 处理拆分为第二个 XMLTable:

SELECT
    s.SUBSCRIPTION_ID,
    xt1.USER_PARAM,
    xt2.PARAM_VALUE,
    xt1.CHECKED_LIST_EXCLUDE
FROM SUBSCRIPTIONS s
CROSS APPLY XMLTABLE(
    '/Model/Parameters/Parameter'
    PASSING XMLTYPE(s.PARAMETERS_XML)
    COLUMNS USER_PARAM  VARCHAR2(100)  PATH 'userParameter',
    PARAM_VALUES XMLTYPE PATH '//*:Values',
    CHECKED_LIST_EXCLUDE VARCHAR2(100) PATH '//*:CheckedListExclude'
) xt1
OUTER APPLY XMLTABLE(
    '/*:Values/Value'
    PASSING xt1.PARAM_VALUES
    COLUMNS PARAM_VALUE VARCHAR2(1000) PATH '.'
) xt2
ORDER BY 1 DESC;
SUBSCRIPTION_ID USER_PARAM PARAM_VALUE CHECKED_LIST_EXCLUDE
553219 outlets 45 null
553219 outlets 1502 null
553219 outlets 676 null

然后您可以汇总各个值:

SELECT
    s.SUBSCRIPTION_ID,
    xt1.USER_PARAM,
    LISTAGG(xt2.PARAM_VALUE, ',') WITHIN GROUP (ORDER BY xt2.PARAM_VALUE) as PARAM_VALUES,
    xt1.CHECKED_LIST_EXCLUDE
FROM SUBSCRIPTIONS s
CROSS APPLY XMLTABLE(
    '/Model/Parameters/Parameter'
    PASSING XMLTYPE(s.PARAMETERS_XML)
    COLUMNS USER_PARAM  VARCHAR2(100)  PATH 'userParameter',
    PARAM_VALUES XMLTYPE PATH '//*:Values',
    CHECKED_LIST_EXCLUDE VARCHAR2(100) PATH '//*:CheckedListExclude'
) xt1
OUTER APPLY XMLTABLE(
    '/*:Values/Value'
    PASSING xt1.PARAM_VALUES
    COLUMNS PARAM_VALUE VARCHAR2(1000) PATH '.'
) xt2
GROUP BY
    s.SUBSCRIPTION_ID,
    xt1.USER_PARAM,
    xt1.CHECKED_LIST_EXCLUDE
ORDER BY 1 DESC;
SUBSCRIPTION_ID USER_PARAM PARAM_VALUES CHECKED_LIST_EXCLUDE
553219 outlets 1502,45,676 null

db<>fiddle

您的示例没有命名空间,因此通配符看起来没有必要(而且我通常会尽可能避免使用通配符),但我保留了它们以防您简化它。

我将第一个连接更改为 cross apply,这意味着您仍然只能看到存在 parameters_xml 的行,就像您原来的一样;但是做了第二个 outer apply 所以即使没有值你仍然会看到参数名称。

我也忽略了 CHECKED_LIST_EXCLUDE,因为示例中没有;如果它也可以有多个值,那么您可能需要为此做类似的事情。

尝试使用 string-join 函数:'string-join(Values//Value, ", ")'.

您可以使用 FLOWR 将 XML 重写为在每个 Value 元素中包含尾随 ,

SELECT s.SUBSCRIPTION_ID,
       s.PARAMETERS_XML,
       xt.User_Param,
       RTRIM(xt.Param_Value, ',') AS Param_Value,
       xt.Checked_List_Exclude
FROM   EFNTA.SUBSCRIPTIONS s
       CROSS JOIN
       XMLTABLE(
         'copy $e := .
         modify (
           for $i in $e/Model/Parameters/Parameter/Values/Value
           return replace node $i with <Value>{$i},</Value>
         )
         return $e/Model/Parameters/Parameter'
         PASSING XMLTYPE(s.PARAMETERS_XML)
         COLUMNS
           USER_PARAM  VARCHAR2(100)  PATH 'userParameter',
           PARAM_VALUE VARCHAR2(1000) PATH '//*:Values',
           CHECKED_LIST_EXCLUDE VARCHAR2(100) PATH '//*:CheckedListExclude'
        ) xt
ORDER BY 1 DESC;

或者,按照 的建议,使用 string-join():

SELECT s.SUBSCRIPTION_ID,
       s.PARAMETERS_XML,
       xt.*
FROM   EFNTA.SUBSCRIPTIONS s
       CROSS JOIN
       XMLTABLE(
         '/Model/Parameters/Parameter'
         PASSING XMLTYPE(s.PARAMETERS_XML)
         COLUMNS
           USER_PARAM  VARCHAR2(100)  PATH 'userParameter',
           PARAM_VALUE VARCHAR2(1000) PATH 'Values/string-join(./Value/text(), ",")',
           CHECKED_LIST_EXCLUDE VARCHAR2(100) PATH '//CheckedListExclude'
        ) xt
ORDER BY 1 DESC;

这两个输出:

SUBSCRIPTION_ID PARAMETERS_XML USER_PARAM PARAM_VALUE CHECKED_LIST_EXCLUDE
1 <Model
<Parameters>
<Parameter>
<userParameter>outlets</userParameter>
<Values>
<Value>45</Value>
<Value>676</Value>
<Value>1502</Value>
</Values>
</Parameter>
</Parameters>
</Model>
outlets 45,676,1502 null

db<>fiddle here

我终于这样处理了:

SELECT
s.SUBSCRIPTION_ID,
s.PARAMETERS_XML,
xt.USER_PARAM,
xt.CHECKED_LIST_EXCLUDE,
NVL(xt.PARAM_VALUE, xt.PARAM_VALUES) AS PARAM_VALUES
FROM EFNTA.SUBSCRIPTIONS s,
XMLTABLE(
    '/Model/Parameters/Parameter'
     PASSING XMLTYPE(s.PARAMETERS_XML)
     COLUMNS USER_PARAM  VARCHAR2(100)  PATH 'userParameter',
             CHECKED_LIST_EXCLUDE VARCHAR2(100) PATH '//*:CheckedListExclude',
             PARAM_VALUE VARCHAR2(4000) PATH 'Value',
             PARAM_VALUES VARCHAR2(4000) PATH 'string-join(Values//Value,";")'
 ) xt ORDER BY 1 DESC;

我不得不放另一列和一个 NVL,因为我注意到有时我在节点正下方有一个节点,并且有所需的输出:

感谢您的建议,我会保密的,以防万一:-)