xquerymarklogiccts-search

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


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?


Solution

  • 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!