简体   繁体   中英

Why does this XQuery filter return unwanted elements?

I'm using BaseX 8.4.1 which implements XQuery 3.1

I'm trying to do a quite basic thing in XQuery, but I cannot seem to figure it out.

I created some example data that illustrates what I am trying to do. My data set looks like this. It is a simple embedded structure. There are days, each day has some events, and events have members.

Data

<root>
    <day>
        <name>1</name>
        <event>
            <name>1</name>
            <member>A</member>
            <member>B</member>
        </event>
        <event>
            <name>2</name>
            <member>C</member>
        </event>
    </day>
    <day>
        <name>2</name>
        <event>
            <name>3</name>
            <member>A</member>
            <member>B</member>
        </event>
        <event>
            <name>4</name>
            <member>C</member>
        </event>
    </day>
    <day>
        <name>3</name>
        <event>
            <name>5</name>
            <member>C</member>
        </event>
    </day>
</root>

What I want to do, is get a list of members, and for each member get a list of days on which they have events, and the events they have. So the result should look like this:

Desired Results

<member>
  <name>A</name>
  <day>
    <name>1</name>
    <event>
      <name>1</name>
      <member>A</member>
      <member>B</member>
    </event>
  </day>
  <day>
    <name>2</name>
    <event>
      <name>3</name>
      <member>A</member>
      <member>B</member>
    </event>
  </day>
</member>
<member>
  <name>B</name>
  <day>
    <name>1</name>
    <event>
      <name>1</name>
      <member>A</member>
      <member>B</member>
    </event>
  </day>
  <day>
    <name>2</name>
    <event>
      <name>3</name>
      <member>A</member>
      <member>B</member>
    </event>
  </day>
</member>
<member>
  <name>C</name>
  <day>
    <name>1</name>
    <event>
      <name>2</name>
      <member>C</member>
    </event>
  </day>
  <day>
    <name>2</name>
    <event>
      <name>4</name>
      <member>C</member>
    </event>
  </day>
  <day>
    <name>3</name>
    <event>
      <name>5</name>
      <member>C</member>
    </event>
  </day>
</member>

To accomplish this, I tried the following XQuery:

What I tried

for $member in distinct-values(//member)
return
<member>
  <name>{$member}</name>
    {for $day in //day where $day/event/member = $member
      let $event := $day/event where $event/member = $member
    return 
      <day>
        {$day/name}
        {$event}
    </day>}
</member>

However, this does not apply the filtering. So I keep all the events for a member that they are not a member of:

What I got

<member>
  <name>A</name>
  <day>
    <name>1</name>
    <event>
      <name>1</name>
      <member>A</member>
      <member>B</member>
    </event>
    <event>
      <name>2</name>
      <member>C</member>
    </event>
  </day>
  <day>
    <name>2</name>
    <event>
      <name>3</name>
      <member>A</member>
      <member>B</member>
    </event>
    <event>
      <name>4</name>
      <member>C</member>
    </event>
  </day>
</member>
<member>
  <name>B</name>
  <day>
    <name>1</name>
    <event>
      <name>1</name>
      <member>A</member>
      <member>B</member>
    </event>
    <event>
      <name>2</name>
      <member>C</member>
    </event>
  </day>
  <day>
    <name>2</name>
    <event>
      <name>3</name>
      <member>A</member>
      <member>B</member>
    </event>
    <event>
      <name>4</name>
      <member>C</member>
    </event>
  </day>
</member>
<member>
  <name>C</name>
  <day>
    <name>1</name>
    <event>
      <name>1</name>
      <member>A</member>
      <member>B</member>
    </event>
    <event>
      <name>2</name>
      <member>C</member>
    </event>
  </day>
  <day>
    <name>2</name>
    <event>
      <name>3</name>
      <member>A</member>
      <member>B</member>
    </event>
    <event>
      <name>4</name>
      <member>C</member>
    </event>
  </day>
  <day>
    <name>3</name>
    <event>
      <name>5</name>
      <member>C</member>
    </event>
  </day>
</member>

Surely, this should be quite easy, no?

This is a very minor issue. You wanted to filter the events, and defined $events as the sequence of all events of a $day . Then, you filtered using the = -operator, which has set semantics -- if any item on the left side ( all events's members of that day) equal any of the items on the right side (the current $member ), the where clause evaluates to true .

Loop over the events instead.

for $member in distinct-values(//member)
return
<member>
  <name>{$member}</name>
    {for $day in //day where $day/event/member = $member
      (: for instead of let :)
      for $event in $day/event where $event/member = $member
    return 
      <day>
        {$day/name}
        {$event}
    </day>}
</member>

Using predicates instead of where clauses often results in code that's easier to read and less nested explicit loops. This is a cleaned up example:

for $member in distinct-values(//member)
return
  <member>{
    <name>{ $member }</name>,
    for $day in //day[event/member = $member]
    return 
      <day>{
        $day/name,
        $day/event[member = $member]
      }</day>
  }</member>

Here is an alternative solution using group by twice that only scans over the source data once:

for $member in //member
group by $name := $member/text()
order by $name
return <member>{
    <name>{$name}</name>,
    for $event in $member/..
    group by $day := $event/../name
    order by $day
    return <day>{
        <name>{$day}</name>,
        $event
    }</day>
}</member>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM