and-query to match an attribute value and a subelement value

126 Views Asked by At

I have documents containing structures like:

<Reviews>
    <Review complete="false">
        <StartDate>2019-03-05T06:00:00Z</StartDate>
        <EndDate>2019-03-12T05:00:00Z</EndDate>
        <Reviewers>
            <Reviewer userName="jdoe">
                <ReviewStatus>Completed</ReviewStatus>
            </Reviewer>
        </Reviewers>
    </Review>
    <Review complete="false">
        <StartDate>2019-03-06T06:00:00Z</StartDate>
        <EndDate>2019-03-13T05:00:00Z</EndDate>
        <Reviewers>
            <Reviewer userName="jsmith">
                <ReviewStatus>Pending</ReviewStatus>
            </Reviewer>
            <Reviewer userName="jdoe">
                <ReviewStatus>Completed</ReviewStatus>
            </Reviewer>
        </Reviewers>
   </Review>
</Reviews>

Using MarkLogic XQuery, I want to search for docs that have a Reviewer element for jsmith AND with his ReviewStatus=Completed. I.e., I do not want to see this sample above in my results because jsmith's ReviewStatus is not Completed. I have tried a couple of different query types where cts:and-query() uses combinations of attribute value, element word, and even path range queries. But I have not figured out how to find only those docs containing a Reviewer element where both the userName attribute value matches "jsmith" AND the ReviewStatus child element value matches "Completed" in the same Reviewer element. Can anyone suggest an approach for this?

1

There are 1 best solutions below

0
On BEST ANSWER

You are looking for scoping queries, like cts:element-query. It allows you to pick a joint ancestor for sub-queries. Here some code that shows how it works:

let $search-name := "jsmith"
let $search-status := "Completed"

let $xml := <Reviews>
    <Review complete="false">
        <StartDate>2019-03-05T06:00:00Z</StartDate>
        <EndDate>2019-03-12T05:00:00Z</EndDate>
        <Reviewers>
            <Reviewer userName="jsmith">
                <ReviewStatus>Completed</ReviewStatus>
            </Reviewer>
            <Reviewer userName="jdoe">
                <ReviewStatus>Pending</ReviewStatus>
            </Reviewer>
        </Reviewers>
    </Review>
    <Review complete="false">
        <StartDate>2019-03-06T06:00:00Z</StartDate>
        <EndDate>2019-03-13T05:00:00Z</EndDate>
        <Reviewers>
            <Reviewer userName="jsmith">
                <ReviewStatus>Pending</ReviewStatus>
            </Reviewer>
            <Reviewer userName="jdoe">
                <ReviewStatus>Completed</ReviewStatus>
            </Reviewer>
        </Reviewers>
   </Review>
</Reviews>
for $rev in $xml//Reviewer
where cts:contains(
  $rev,
  cts:element-query(
    xs:QName("Reviewer"),
    cts:and-query((
      cts:element-attribute-value-query(xs:QName("Reviewer"), xs:QName("userName"), $search-name),
      cts:element-value-query(xs:QName("ReviewStatus"), $search-status)
    ))
  )
)
return $rev

Note that you will need to enable position indexes if you need accurate results for unfiltered searches.

HTH!