Группировка и подсчет в Xquery - PullRequest
6 голосов
/ 22 февраля 2012

Слушай, это XML.Я пытаюсь получить Количество названий , опубликованных автором в диапазоне дат * от 1005 * 15/02/2012 до 24/02/2012 в порядке убывания(количество названий).

<entries>
<entry>
    <id>1</id>
    <published>23/02/2012</published>
    <title>Title 1</title>
    <content type="html">This is title one</content>
    <author>
        <name>Pankaj</name>
    </author>
</entry>
<entry>
    <id>2</id>
    <published>22/02/2012</published>
    <title>Title 2</title>
    <content type="html">This is title two</content>
    <author>
        <name>Pankaj</name>
    </author>
</entry>
<entry>
    <id>3</id>
    <published>21/02/2012</published>
    <title>Title 3</title>
    <content type="html">This is title three</content>
    <author>
        <name>Rob</name>
    </author>
</entry>
<entry>
    <id>4</id>
    <published>20/02/2012</published>
    <title>Title 4</title>
    <content type="html">This is title four</content>
    <author>
        <name>Bob</name>
    </author>
</entry>
<entry>
    <id>5</id>
    <published>19/02/2012</published>
    <title>Title 1</title>
    <content type="html">This is title five</content>
    <author>
        <name>Pankaj</name>
    </author>
</entry>

Я пытаюсь получить вывод из xquery:

<?xml version="1.0" encoding="UTF-8"?>
<results>
<result>
    <author>
        <name>Pankaj</name>
    </author>
    <numberOfTitles>3</numberOfTitles>
</result>
<result>
    <author>
        <name>Rob</name>
    </author>
    <numberOfTitles>1</numberOfTitles>
</result>
<result>
    <author>
        <name>Bob</name>
    </author>
    <numberOfTitles>1</numberOfTitles>
</result>

Пожалуйста, помогите мне ..

Ответы [ 6 ]

4 голосов
/ 23 февраля 2012

Вот решение, характерное для MarkLogic, использующее карты для эффективной реализации группировки. Входной XML был объявлен как $INPUT, но вы можете заменить его вызовом doc() или любым другим средством доступа.

Я также исследовал эту тему в своем блоге в прошлом году: http://blakeley.com/blogofile/archives/560/

element results {
  let $m := map:map()
  let $start := xs:date('2012-02-15')
  let $stop := xs:date('2012-02-24')
  let $group :=
    for $entry in $INPUT/entry
    let $key := $entry/author/name/string()
    let $date := xs:date(xdmp:parse-yymmdd("dd/MM/yyyy", $entry/published))
    where $date ge $start and $date le $stop
    return map:put($m, $key, 1 + (map:get($m, $key), 0)[1])
  for $key in map:keys($m)
  let $count := map:get($m, $key)
  order by $count
  return element result {
    element author { element name { $key }},
    element numberOfTitles { $count } } }
4 голосов
/ 22 февраля 2012

Это решение XQuery 1.0 выполняется любым совместимым процессором XQuery 1.0 :

Примечание : Нет group by и нет distinct-values().

<results> 
 {
 let $entries := 
    /*/entry
           [for $d in 
                    xs:date(string-join(reverse(tokenize(published, '/')), '-'))
                return
                   xs:date('2012-02-15') le $d and $d le xs:date('2012-02-24')
             ],

  $vals := $entries/author/name
      return
         for $a in  $vals[index-of($vals, .)[1]],
                $cnt in count(index-of($vals, $a)) 
           order by $cnt descending
             return
              <result>
                <author>
                  {$a}
                 </author>
                 <numberOfTitles>
                   {count(index-of($vals, $a))}
                 </numberOfTitles>
              </result>
    }
</results>

при применении к предоставленному документу XML :

<entries>
    <entry>
        <id>1</id>
        <published>23/02/2012</published>
        <title>Title 1</title>
        <content type="html">This is title one</content>
        <author>
            <name>Pankaj</name>
        </author>
    </entry>
    <entry>
        <id>2</id>
        <published>22/02/2012</published>
        <title>Title 2</title>
        <content type="html">This is title two</content>
        <author>
            <name>Pankaj</name>
        </author>
    </entry>
    <entry>
        <id>3</id>
        <published>21/02/2012</published>
        <title>Title 3</title>
        <content type="html">This is title three</content>
        <author>
            <name>Rob</name>
        </author>
    </entry>
    <entry>
        <id>4</id>
        <published>20/02/2012</published>
        <title>Title 4</title>
        <content type="html">This is title four</content>
        <author>
            <name>Bob</name>
        </author>
    </entry>
    <entry>
        <id>5</id>
        <published>19/02/2012</published>
        <title>Title 1</title>
        <content type="html">This is title five</content>
        <author>
            <name>Pankaj</name>
        </author>
    </entry>
</entries>

дает желаемый, правильный результат :

<?xml version="1.0" encoding="UTF-8"?>
<results>
   <result>
      <author>
         <name>Pankaj</name>
      </author>
      <numberOfTitles>3</numberOfTitles>
   </result>
   <result>
      <author>
         <name>Rob</name>
      </author>
      <numberOfTitles>1</numberOfTitles>
   </result>
   <result>
      <author>
         <name>Bob</name>
      </author>
      <numberOfTitles>1</numberOfTitles>
   </result>
</results>
4 голосов
/ 22 февраля 2012

Вот мой путь к решению:

<results>{
  for $entry in //entry
  let $date := xs:date(string-join(reverse(tokenize($entry/published, '/')), '-')),
      $author := $entry/author/string()
  where xs:date('2012-02-15') le $date and $date le xs:date('2012-02-24')
  group by $author
  order by count($entry) descending
  return <result>{
    <author>
      <name>{$author}</name>
    </author>,
    <numberOfTitles>{count($entry)}</numberOfTitles>
  }</result>
}</results>

При выполнении с BaseX он дает правильный результат.

Он использует функции XQuery 3.0, такие как group by, в противном случае это будет сложнее. Я не знаю, поддерживает ли MarkLogic это.

2 голосов
/ 23 февраля 2012

+ 1 в решении на основе карты *1001*. Другие решения имеют предложение count(/entry/author[$name=xx]) или другое XPath, вложенное в FLWOR , который фактически является вложенным циклом. Вложенные циклы вызывают производительность O (N ^ 2) , которая может быть хорошей при тестировании, а затем замедляется при увеличении размера данных.

2 голосов
/ 23 февраля 2012

Вот еще одно решение, аналогичное Leo Wörteler:

declare function local:FormatDate($origDate as xs:string) as xs:date 
  {
      xs:date(string-join(reverse(tokenize($origDate, '/')), '-'))
  };

<results>
  {
  for $author in distinct-values(/entries/entry/author/name)
  let $startDate := xs:date('2012-02-15')
  let $endDate := xs:date('2012-02-24')
  order by count(/entries/entry[author/name=$author][$startDate <= local:FormatDate(published) and local:FormatDate(published) <= $endDate]) descending
  return
    <result>
      <author>
        <name>{$author}</name>
      </author>
      <numberOfTitles>{count(/entries/entry[author/name=$author][$startDate <= local:FormatDate(published) and local:FormatDate(published) <= $endDate])}</numberOfTitles>
    </result>
  }
</results>
2 голосов
/ 22 февраля 2012

Следующее должно работать в большинстве процессоров. Вероятно, в MarkLogic вы можете сделать более эффективные запросы, но это поможет вам начать работу.

let $doc := <entries>
<entry>
    <id>1</id>
    <published>23/02/2012</published>
    <title>Title 1</title>
    <content type="html">This is title one</content>
    <author>
        <name>Pankaj</name>
    </author>
</entry>
<entry>
    <id>2</id>
    <published>22/02/2012</published>
    <title>Title 2</title>
    <content type="html">This is title two</content>
    <author>
        <name>Pankaj</name>
    </author>
</entry>
<entry>
    <id>3</id>
    <published>21/02/2012</published>
    <title>Title 3</title>
    <content type="html">This is title three</content>
    <author>
        <name>Rob</name>
    </author>
</entry>
<entry>
    <id>4</id>
    <published>20/02/2012</published>
    <title>Title 4</title>
    <content type="html">This is title four</content>
    <author>
        <name>Bob</name>
    </author>
</entry>
<entry>
    <id>5</id>
    <published>19/02/2012</published>
    <title>Title 1</title>
    <content type="html">This is title five</content>
    <author>
        <name>Pankaj</name>
    </author>
</entry>
</entries>

return
 <results>
    {
        for $author in distinct-values($doc/entry/author/name/string())
        return
        <result><author>
            <name>{$author}</name>
            <numberOfTitles>{count($doc/entry[author/name/string() eq $author])} </numberOfTitles>
        </author></result>
    }
 </results>
...