在XQuery中按一系列值排序

时间:2013-08-14 19:08:58

标签: xquery exist-db

我有一些采用这种形式的XML数据:

<products>
  <product version="1.2.3"/>
  <product version="1.10.0"/>
  <product version="2.1.6"/>
</products>

......等等。我想按版本号在XQuery中订购这些。麻烦的是,如果我只做order by $thing/@version,它会进行字典比较,将1.20放在1.2.3之前,这是错误的。

我真正想要的是:

order by tokenize($thing/@version, '\.') ! number(.)

不幸的是,这不起作用,因为XQuery不允许您将整个序列用作排序键。我怎么能得到这样的东西?

不依赖于具有相同点数的所有版本号的解决方案将是更可取的,但我将采取我能得到的。

4 个答案:

答案 0 :(得分:3)

您所能做的就是规范版本号,以便您可以应用词汇排序。

  • 确定版本步骤中的最大字符串长度
  • 用0来填充它(如果你愿意,可以填充空格,但你必须更改代码)
  • 标记每个版本,填充每个版本步骤,重新加入
  • 根据填充版本进行比较

我没有清理那个代码并从functx中提取了两个函数,但它可以工作,并且应该可以根据需要嵌入。代码也能够处理单个字母,如果有必要,你可以替换所有出现的“alpha”,...例如“a”,...

declare namespace functx = "http://www.functx.com"; 
declare function functx:repeat-string 
  ( $stringToRepeat as xs:string? ,
    $count as xs:integer )  as xs:string {

   string-join((for $i in 1 to $count return $stringToRepeat),
                        '')
 } ;
declare function functx:pad-integer-to-length 
  ( $integerToPad as xs:anyAtomicType? ,
    $length as xs:integer )  as xs:string {

   if ($length < string-length(string($integerToPad)))
   then error(xs:QName('functx:Integer_Longer_Than_Length'))
   else concat
         (functx:repeat-string(
            '0',$length - string-length(string($integerToPad))),
          string($integerToPad))
 } ;


declare function local:version-compare($a as xs:string, $max-length as xs:integer)
as xs:string*
{
  string-join(tokenize($a, '\.') ! functx:pad-integer-to-length(., $max-length), '.')
};

let $bs := ("1.42", "1.5", "1", "1.42.1", "1.43", "2")
let $max-length := max(
                     for $b in $bs
                     return tokenize($b, '\.') ! string-length(.)
                   )
for $b in $bs
let $normalized := local:version-compare($b, $max-length)
order by $normalized
return $b

返回:

  

1 1.5 1.42 1.42.1 1.43 2

答案 1 :(得分:2)

Order by不接受序列,但您可以显式地标记版本并将它们添加到订单中,用逗号分隔(注意排除parens)。

let $products := 
<products>
  <product version="1.2.3"/>
  <product version="1.10.0"/>
  <product version="2.1.6"/>
</products>
for $p in $products/product
let $toks := tokenize($p/@version, '\.')
let $main := xs:integer($toks[1])
let $point := xs:integer($toks[2])
let $sub := xs:integer($toks[3])
order by $main, $point, $sub
return $p

更新:对于可变数量的令牌,您可以使order by更加健壮:

order by 
  if (count($toks) gt 0) then $main else (),
  if (count($toks) gt 1) then $point else (),
  if (count($toks) gt 2) then $sub else ()

答案 2 :(得分:2)

我做了类似于Jens的回答:

let $products := //product
let $max-length := max($products/@version ! string-length(.))
for $product in $products
order by string-join(
    for $part in tokenize($product/@version, '\.')
    return string-join((
        for $_ in 1 to $max-length - string-length($part) return ' ',
        $part)))
return $product

答案 3 :(得分:1)

这是一个可以处理任意数量的段的版本,只要它们是数字的并且所有版本字符串具有相同数量的段。它还假设没有一个组件超过999。

这只是将每个数字段组合成一个大数字并按其排序。

declare function local:version-order ($version as xs:string) as xs:double
{
    fn:sum (
        let $toks := fn:tokenize ($version, "\.")
        let $count := fn:count ($toks)
        for $tok at $idx in $toks
        return xs:double ($tok) * math:pow (1000, ($count - $idx))
    )
};

let $products := 
    <products>
        <product version="1.10.0"/>
        <product version="2.1.6"/>
        <product version="1.2.3"/>
    </products>

for $p in $products/product
order by local:version-order ($p/@version)
return $p