MarkLogic 中用于多个集合中文档的复杂 Xquery

Complex Xquery in MarkLogic for documents in multiple collections

我们在 MarkLogic 中拥有庞大的数据集,文档分布在多个集合中。我们需要通过搜索分布在这些集合中的文档来开发报告。

示例数据集:

    Collection1 - Inventory [Contains 5 million Inventory documents]

    URI: /inventory/inv1
        <xml>
            <INVENTORY>
                <ItemName>10</ItemName>
                <InventoryQuantity>100</InventoryQuantity>
                .
                .
            </INVENTORY>
        </xml>


    Collection2 - Item[Contains 1 million Item documents]

    URI: /item/item1
        <xml>
            <Item>
                <ItemName>10</ItemName>
                <ItemWmos>
                    <UnitPrice>895</UnitPrice>
                    .
                    .
                <ItemWmos>
                .
                .
            </Item>
        </xml>


          For each Inventory in the Inventory collection
            Step 1 : Get "ItemName", its "InventoryQuantity" and for the same "ItemName" find "UnitPrice" from Item document in Item collection.
            Step 2 : CurrentInventoryValue = InventoryQuantity * UnitPrice
            Step 3 : TotalInventoryValue = TotalInventoryValue + CurrentInventoryValue;
         Repeat;

我已经使用下面的 XQuery 实现了上述报告要求。但是它需要很多时间来执行并显示超时异常。 我无法在这里利用路径范围索引,因为我必须将 "ItemName" 及其 "InventoryQuantity" 放在一起,并在具有相同项目名称的不同集合文档中搜索单价。

XQuery :

    sum(for $doc in cts:search(doc(), cts:and-query((cts:collection-query("Inventory"))))
        [(fn:string-length(//INVENTORY/ItemName/text()) > 0) and (fn:string-length(//INVENTORY/InventoryQuantity/text()) > 0)]
        let $itemName := $doc//INVENTORY/ItemName/text()
        let $inventoryQuantity := $doc//INVENTORY/InventoryQuantity/text()
    return (
        for $doc in cts:search(doc(), cts:and-query((cts:collection-query("Item"))))[//Item/ItemName/text()=$itemName 
        and (fn:string-length(//ItemWmos/UnitPrice/text()) > 0)]
        return ($inventoryQuantity * $doc//ItemWmos/UnitPrice/text())
    ))

我们如何在 MarkLogic 中高效地完成如此复杂的查询需求?

创可贴方法:

通过将 XPath 表达式重写为绝对表达式并将它们转换为 cts 查询逻辑,您可能会获得更好的性能。但即使它解决了眼前的问题,它也不会扩展得更远(因为你可以迭代的文档数量没有限制):

(注意:我没有测试过这段代码,所以可能存在语法错误)

sum(for $doc in cts:search(doc(), cts:and-query((cts:collection-query("Inventory"))), "unfiltered")
        let $itemName := $doc/xml/INVENTORY/ItemName/string()
        let $inventoryQuantity := $doc/xml/INVENTORY/InventoryQuantity/string()
    where
      fn:string-length($itemName) gt 0 and fn:string-length($inventoryQuantity) gt 0
    return (
        for $doc in cts:search(doc(), cts:and-query((cts:collection-query("Item")), cts:elementValueQuery("ItemName", $itemName)), "unfiltered")
        return (xs:integer($inventoryQuantity) * xs:integer($doc/xml/Item/ItemWmos/UnitPrice/string()))
    ))

可扩展方法 #1:

我要做的第一件事是将您在 MarkLogic 中的 /inventory/ 数据转换并反规范化为以下格式:

<envelope>
  <headers xmlns="http://yoursite.com/item/headers"> <!-- Note the unique namespace -->
     <ItemName>10</ItemName>
     <InventoryQuantity>100</InventoryQuantity>
     <UnitPrice>85</UnitPrice>
     <ItemPrice>8500</ItemPrice>
  </headers>
  <attachments>
    <INVENTORY>
      <ItemName>10</ItemName>
      <InventoryQuantity>100</InventoryQuantity>
      .
      .
    </INVENTORY>
    <Item>
      <ItemName>10</ItemName>
      <ItemWmos>
        <UnitPrice>895</UnitPrice>
        .
        .
      </ItemWmos>
      .
      .
    </Item>
 </attachments>
</envelope>

现在,对于 运行 报告,您需要做的就是将 ElementRangeIndex 放在 <ItemPrice> 和 运行 cts:sum-aggregate 上。

如果您使用 DataHub 框架,您可以更简单地管理用于提取和协调不相交数据的流程。

可扩展方法 #2:

您可以保持数据原样,但可以在其之上放置 TDE 视图,这将索引数据并允许您使用 Optic API 到 运行 高性能连接和从中聚合。这有一些背景开销,这使得方法 #1 在同类比较中性能更高,但是 运行 快速进行更广泛查询而无需更改摄取的灵活性可能对您来说是值得的。

ItemName 是否只在库存中出现一次?每个 ItemName 值是否只出现在一个库存中?如果是这样,您可以使用带 cts:*-co-occurrences 函数的范围索引来获得两个 'tables',然后将它们相乘。

let $itemQuantities := cts:element-value-co-occurrences(
  xs:QName('ItemName'),
  xs:QName('InventoryQuantity'),
  'map',
  cts:collection-query('Inventory')
)

let $itemPrices := cts:element-value-co-occurrences(
  xs:QName('ItemName'),
  xs:QName('UnitPrice'),
  'map',
  cts:collection-query('Item')
)

return fn:sum(
  for $itemName in map:keys($itemQuantities)
  return map:get($itemQuantities, $itemName) * map:get($itemPrices, $itemName)
)

请使用 xdmp:directory 对 URI

进行迭代
let $var1:=<xml>
    <INVENTORY>
        <ItemName>10</ItemName>
        <InventoryQuantity>100</InventoryQuantity>
    </INVENTORY>
    <INVENTORY>
        <ItemName>10</ItemName>
        <InventoryQuantity>101</InventoryQuantity>
    </INVENTORY>
    <INVENTORY>
        <ItemName>12</ItemName>
        <InventoryQuantity>102</InventoryQuantity>
    </INVENTORY>
</xml>


let $var2:=
<xml>
    <Item>
        <ItemName>10</ItemName>
        <ItemWmos>
            <UnitPrice>895</UnitPrice>
        </ItemWmos>
    </Item>
    <Item>
        <ItemName>11</ItemName>
        <ItemWmos>
            <UnitPrice>896</UnitPrice>
        </ItemWmos>
    </Item>
    <Item>
        <ItemName>12</ItemName>
        <ItemWmos>
            <UnitPrice>897</UnitPrice>
        </ItemWmos>
    </Item>
</xml>

let $mergeInvertory:= <merge>{for $in in $var2//Item/ItemName[. eq $var1//INVENTORY/ItemName]

    return <mergeFile>{$in/..,$var1//ItemName[. eq $in]/following-sibling::InventoryQuantity}</mergeFile>
    }</merge>

let $CurrentInventoryValue:=<mergeInventory>{for $item in $mergeInvertory/mergeFile
    return <INVENTORY><ItemName>{$item//ItemName}</ItemName><UnitPrice>{data($item//UnitPrice)}</UnitPrice><InventoryQuantity>{$item//InventoryQuantity}</InventoryQuantity><TotalInventoryQuantity>{$item//UnitPrice * sum($item//InventoryQuantity)}</TotalInventoryQuantity></INVENTORY>
    }</mergeInventory>
return $CurrentInventoryValue