(: I include these variables so that it is easy to access docs. :) declare variable \$emps := doc("docs/v-emps.xml"); declare variable \$depts := doc("docs/v-depts.xml"); (: BASIC TEMPORAL FUNCTIONS :) (: I include two functions to calculate whether periods overlap. These functions help with just about all of the queries. They are very simple: The first takes an element with tstart and tend attributes as well as a date. It determines whether the element overlaps the date, i.e., whether the date occurs in between tstart and tend. The second takes an element with start and tend attributes as well as two dates, one marking the start of a period and one marking the end. It determines whether the element overlaps that period. :) declare function local:overlaps(\$a as element(), \$b as xs:date) as xs:boolean { \$a/@tstart <= \$b and \$a/@tend >= \$b }; declare function local:overlaps(\$a as element()*, \$tstart as xs:date, \$tend as xs:date) as xs:boolean { \$a/@tstart <= \$tend and \$a/@tend >= \$tstart }; (: Min and max functions for dates, to make some queries a bit easier. :) declare function local:min(\$a as xs:date, \$b as xs:date) as xs:date { (if (\$a > \$b) then \$b else (\$a)) }; declare function local:max(\$a as xs:date, \$b as xs:date) as xs:date { (if (\$a > \$b) then \$a else (\$b)) }; (: LONGEST PERIOD :) (: Find the longest element in a list of elements, where length is defined by the difference between tend and tstart. This works by calculating the difference between tend and tstart for each element, then computing the max, and finally, going through each element, and returning it if its length matches the max. :) declare function local:longest(\$a as element()*) as element()* { (let \$diffs := (for \$n in \$a return (xs:date(\$n/@tend) - xs:date(\$n/@tstart))), \$max := max(\$diffs) for \$n in \$a where (xs:date(\$n/@tend) - xs:date(\$n/@tstart)) = \$max return \$n) }; (: COALESCING :) (: This function is useful in simple_coalesce (see below). It takes an element \$e and a list of elements and returns the time marking the end of the maximal period for that starts at the element's start time. It does this by assuming that the only possible overlaps between elements is that the earlier one's tend matches the later one's tstart (i.e., no elements overlap by more than one time unit). Because of the way that we are storing temporal data, this assumption should usually hold. The function runs recursively: if no element overlaps with the current element's end time, the maximal period ends at the element's tend. Otherwise, the maximal period ends at max_time on the next element. :) declare function local:max_time(\$node as element(), \$nodes as element()*) as xs:date { let \$next := \$nodes[@tstart=\$node/@tend][text()=\$node/text()] return (if (empty(\$next)) then (xs:date(\$node/@tend)) else (local:max_time(\$next, \$nodes))) }; (: Coalesce while assuming that no elements overlap by more than one time unit (see max_time above). It simply goes through the list of original elements, ignores any element that does not mark the start of a maximal period and returns the result of max_time on the rest. It checks to see if an element starts a maximal period by ensuring that there is no element that ends when it starts. :) declare function local:simple_coalesce(\$original as element()*) as element()* { ( for \$node in \$original where not(some \$n in \$node satisfies (some \$node2 in \$original satisfies \$n/@tstart = \$node2/@tend and \$n/text() = \$node2/text())) return {\$node/node()} ) }; (: COUNTING :) (: This function computes a temporal count using delta values. It is given an current count, an current time, a list of times to count, and a list of events holding information about those times (see p7.xml). It runs recursively, by grabbing the minimum time in the list (in \$new_time), getting all of the start and end events that occur at that time (in \$plus and \$minus, respectively), and then running itself on the remaining times, with \$new_time as the current time, and an updated count after including \$plus and \$minus. Its results are stored in count elements that are in turn stored in recursively nested result elements. I tried to return only the count elements, but got several complaints from Galax. :) declare function local:count_smart(\$curr_count as xs:integer,\$last_time as xs:date,\$times as xs:date*,\$events as element()*) as element()* { (if (empty(\$times)) then () else (let \$new_time := min(\$times), \$plus := count(\$events//time[@type = "start"][@id = \$new_time]), \$minus := count(\$events//time[@type = "end"][@id = \$new_time]), \$new_count := (\$curr_count + \$plus - \$minus), \$new_times := (for \$time in \$times where \$time > \$new_time return \$time), \$result := ({\$curr_count}) return { (\$result), (local:count_smart(\$new_count,xs:date(\$new_time),\$new_times,\$events)) } ) ) };