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.
<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:
<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:
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:
<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.