使XML属性在XML中是唯一的

时间:2014-05-30 21:47:57

标签: sql xml oracle plsql xquery

我有一个合并的XML文件(为简单起见)具有以下形式:

<bookstore>
    <books>
        <book id="1"/>
        <book id="2"/>
        <book id="2"/>
        <book id="3"/>
        <book id="10"/>
    </books>
</bookstore>

由于XML已合并,因此存在具有相同ID属性的书籍。我需要遵循以下规则使ID唯一:如果遇到 ID (从上到下),则将此ID更改为 MAX(ID)+1

<bookstore>
    <books>
        <book id="1"/>
        <book id="2"/>
        <book id="11"/>
        <book id="3"/>
        <book id="10"/>
    </books>
</bookstore>

这样做的直接方法是提取ID,检查它们的出现次数,如果它出现多次,则搜索ID的第二次出现(从上到下)并替换它。但这不是很优雅......

当我正在阅读有关XML处理的内容时,我希望有一个(简单的)XQuery可以做到这一点。 如果有人有一些指针或伪代码:他们都欢迎。

我的环境是Oracle(PL)SQL数据库,支持 XMLTYPE XQuery

1 个答案:

答案 0 :(得分:2)

在您的环境中,您可以使用XSLT 1.0转换文档并在此过程中生成ID。请参阅:DBMS_XSLPROCESSOR

使用XSLT样式表,您可以将XML源中的节点复制到结果树,从而在流程中创建唯一ID。 ID不是序列号,而是generate-id()方法生成的唯一字符串序列。你无法控制它们的样子,但你可以保证它们是独一无二的。 (如果这是你的意图,XSLT还允许你摆脱重复的节点(使用),但从你的例子我明白重复的* ID * s实际上并没有表示节点是重复的,因为您要为其生成新的ID。)

下面的样式表有两个模板。第二个是身份转换:它只是将元素和属性复制到结果树。第一个模板创建一个名为id的属性,其中包含唯一ID。

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:strip-space elements="*"/>
    <xsl:output indent="yes"/>

    <xsl:template match="book">
        <xsl:copy>
            <xsl:attribute name="id">
                <xsl:value-of select="generate-id(.)"/>
            </xsl:attribute>
            <xsl:apply-templates select="node()|@*[name() != 'id']"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

其他模板(在本例中仅为标识模板)将针对所有节点和属性进行调用,除 id属性 <xsl:apply-templates ...>之外。结果是原始XML文件的副本,其中为book元素生成了唯一ID。

如果你有一个像这样的XML:

<bookstore>
    <books>
        <book id="1" other="123"/>
        <book id="2"/>
        <book id="2"/>
        <book id="3">
            <chapter number="123" id="ch1">Text</chapter>
        </book>
        <book id="10"/>
    </books>
    <magazines>
        <mag id="non-book-id"></mag>
    </magazines>
</bookstore>

上面的XSLT会将其转换为这个XML:

<bookstore>
   <books>
      <book id="d2e3" other="123"/>
      <book id="d2e4"/>
      <book id="d2e5"/>
      <book id="d2e6">
         <chapter number="123" id="ch1">Text</chapter>
      </book>
      <book id="d2e9"/>
   </books>
   <magazines>
      <mag id="non-book-id"/>
   </magazines>
</bookstore>

(字符串序列是任意的,在您的实现中可能有所不同。)

为了创建ID / IDREF链接,生成的字符串序列优于数字,因为您可以在任何地方使用它们(以数字开头的数字和标识符不能总是用作ID)。但是如果字符串序列不可接受并且您需要顺序数字,则可以在XQuery或XSLT中使用XPath节点position()来生成基于元素位置的数字在整个文档中(这将是唯一的)。如果所有图书都是同一上下文中的兄弟姐妹,则只需替换generate-id(.)上面的样式表中的position()

<xsl:template match="book">
    <xsl:copy>
        <xsl:attribute name="id">
            <xsl:value-of select="position()"/>
        </xsl:attribute>
        <xsl:apply-templates select="node()|@*[name() != 'id']"/>
    </xsl:copy>
</xsl:template>

(如果书籍不是兄弟姐妹,你需要以稍微不同的方式使用变量)。

如果你想保留现有的ID 并且只为重复项生成连续的ID,那么它会更复杂,但你可以通过键(或XQuery而不是XSLT)来实现。可以使用id函数在XPath 2.0中获取最大max()

max(//book/@id)

XPath 1.0中不存在该函数,但您可以使用以下命令获取最大ID:

//book[not(@id < //book/@id)]/@id