我有一些采用这种形式的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不允许您将整个序列用作排序键。我怎么能得到这样的东西?
不依赖于具有相同点数的所有版本号的解决方案将是更可取的,但我将采取我能得到的。
答案 0 :(得分:3)
您所能做的就是规范版本号,以便您可以应用词汇排序。
我没有清理那个代码并从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