2009-03-13 10 views
16

J'ai un document XML que je génère à la volée, et j'ai besoin d'une fonction pour éliminer les nœuds en double.Comment puis-je supprimer des noeuds en double dans XQuery?

Ma fonction ressemble:

declare function local:start2() { 
    let $data := local:scan_books() 
    return <books>{$data}</books> 
}; 

sortie de l'échantillon est:

<books> 
    <book> 
    <title>XML in 24 hours</title> 
    <author>Some Guy</author> 
    </book> 
    <book> 
    <title>XML in 24 hours</title> 
    <author>Some Guy</author> 
    </book> 
</books> 

Je veux juste une entrée dans mes livres balise racine, et il y a d'autres balises, comme disent pamphlet là-dedans aussi qui ont besoin d'avoir des doublons enlevés. Des idées?


Mise à jour des commentaires suivants. Par nœuds uniques, je veux dire supprimer plusieurs occurrences de nœuds qui ont exactement le même contenu et la même structure.

Répondre

16

plus simple et plus solution XPath directe one-liner:

Il suffit d'utiliser l'expression XPath suivante:

/*/book 
     [index-of(/*/book/title, 
        title 
       ) 
        [1] 
     ] 

Lorsqu'il est appliqué, par exemple, sur le document XML suivant :

<books> 
    <book> 
     <title>XML in 24 hours</title> 
     <author>Some Guy</author> 
    </book> 
    <book> 
     <title>Food in Seattle</title> 
     <author>Some Guy2</author> 
    </book> 
    <book> 
     <title>XML in 24 hours</title> 
     <author>Some Guy</author> 
    </book> 
    <book> 
     <title>Food in Seattle</title> 
     <author>Some Guy2</author> 
    </book> 
    <book> 
     <title>How to solve XPAth Problems</title> 
     <author>Me</author> 
    </book> 
</books> 

l'expression XPath ci-dessus sélectionne correctement les nœuds suivants:

<book> 
    <title>XML in 24 hours</title> 
    <author>Some Guy</author> 
</book> 
<book> 
    <title>Food in Seattle</title> 
    <author>Some Guy2</author> 
</book> 
<book> 
    <title>How to solve XPAth Problems</title> 
    <author>Me</author> 
</book> 

L'explication est simple: Pour chaque book, sélectionnez une seule de ses occurences - de telle sorte que son index dans tous les livres est le même que le premier indice de son title en tous les titres.

+0

Hey Dimitre, merci pour la réponse; mais si je comprends bien, cela dépend de tous les éléments ayant la même structure qui est intégrée dans la requête - par exemple il montrerait deux nœuds identiques s'ils avaient le même titre et différents auteurs ... – Brabster

+0

@Brabster Il est pas du tout clair de votre question comment le test d'inégalité/d'unicité devrait être défini. Si vous le définissez, cela vous aidera à trouver une solution plus simple. –

+0

Cela ne semble pas fonctionner avec XPath 1.0, pouvons-nous obtenir une solution XPath 1.0 fonctionnelle? – abarax

1

J'ai résolu mon problème en implémentant une fonction de recherche d'unicité récursive, basée uniquement sur le contenu textuel de mon document pour la correspondance d'unicité.

declare function ssd:unique-elements($list, $rules, $unique) { 
    let $element := subsequence($rules, 1, 1) 
    let $return := 
    if ($element) then 
     if (index-of($list, $element) >= 1) then 
      ssd:unique-elements(insert-before($element, 1, $list), subsequence($rules, 2), $unique) 
     else <test> 
      <unique>{$element}</unique> 
      {ssd:unique-elements(insert-before($element, 1, $list), subsequence($rules, 2), insert-before($element, 1, $unique))/*} 
      </test> 
    else() 
    return $return 
}; 

Appelé comme suit:

declare function ssd:start2() { 
    let $data :=() 
    let $sift-this := 
     <test> 
      <data>123</data> 
      <data>456</data> 
      <data>123</data> 
      <data>456</data> 
      <more-data>456</more-data> 
     </test> 
    return ssd:unique-elements($data, $sift-this/*,())/*/* 
}; 

ssd:start2() 

sortie:

<?xml version="1.0" encoding="UTF-8"?> 
<data>123</data> 
<data>456</data> 

Je suppose que si vous avez besoin d'équivalence correspondant légèrement différente, vous pouvez modifier la mise en correspondance dans l'algorithme en conséquence. Vous devriez commencer à tout prix.

5

Vous pouvez utiliser le haut-distinct-values() fonction ...

+0

Comment pouvez-vous l'utiliser? – obesechicken13

1

Qu'en est-fn: valeurs distinctes?

2

Une solution inspirée de la programmation fonctionnelle. Cette solution est extensible en ce sens que vous pouvez remplacer la comparaison "=" par votre fonction personnalisée booléenne local:compare($element1, $element2).Cette fonction a le cas le plus défavorable complexité quadratique dans la longueur de la liste. Vous pouvez obtenir la complexité n(log n) en triant la liste avant et en comparant seulement avec le successeur immédiat.

À ma connaissance, les fn:distinct-values (ou fn:distinct-elements) fonctions ne permet pas d'utiliser une fonction de comparaison sur mesure.

declare function local:deduplicate($list) { 
    if (fn:empty($list)) then() 
    else 
    let $head := $list[1], 
     $tail := $list[position() > 1] 
    return 
     if (fn:exists($tail[ . = $head ])) then local:deduplicate($tail) 
     else ($head, local:deduplicate($tail)) 
}; 

let $list := (1,2,3,4,1,2,1) return local:deduplicate($list) 
+0

Cette solution semble fonctionner. Pourriez-vous s'il vous plaît expliquer la ligne "fn: exists ($ tail [. = $ Head])"? J'ai modifié ceci pour être "$ head = $ tail" et cela fonctionne. – abarax

0

Vous pouvez utiliser cette fonction functx: functx: distincte profonde

Pas besoin de réinventer la roue

1

Pour supprimer les doublons que j'utilise habituellement une fonction d'aide. Dans votre cas, cela ressemblera à ceci:

declare function local:remove-duplicates($items as item()*) 
as item()* 
{ 
    for $i in $items 
    group by $i 
    return $items[index-of($items, $i)[1]] 
}; 

declare function local:start2() { 
    let $data := local:scan_books() 
    return <books>{local:remove-duplicates($data)}</books> 
}; 
Questions connexes