Advanced search parameters


Introduction


In this module, we will go through some more advanced ways of searching. This module assumes you have basic knowledge on searching. If you prefer to start with the basics, please go through the Search operations and parameters module first. In the exercise of this module, we will run queries on our own Vonk server. The end-point of this server is http://vonk.fire.ly

The topics covered in this module are:

  • Chaining and reverse chaining
  • Filter search
  • Compartment search
  • Composite search parameters (pairs)
  • Include and revinclude
  • Operations

Reading material


1. Chaining and reverse chaining

1.1 Chaining

Chaining allows you to search on properties of a referenced resource. This means that you can go through a graph of connected resources and get back a result by using search parameters defined for the last resource type in a chain.

To illustrate how chaining works in practice, let's go through an example. An Observation contains a reference to a Patient resource in the subject element. The search parameters patient and subject allow you to filter Observations on a specific subject (the difference being that the patient search parameter is restricted to the resource type Patient, while the subject search parameter searches for all resource types that can be the subject of the Observation). Now suppose you want to filter the Observations based on properties of the Observation's subject. To do so, you would only need to append the search parameter (e.g. patient) with a . followed by a valid search parameter for the referenced resource (e.g. birthdate).

For example, to search for all Observations from Patients born since the 1st of January 2001, you would get:

GET [base]/Observation?patient.birthdate=ge2001-01-01

Note that you could also use the subject search parameter, but in this case, you will still need to specify the resource type that you are looking for. It's not allowed to address multiple resource types at the same time (correctly implemented FHIR servers will reject searches in which this is the case).

The correct syntax for the same search based on the subject parameter is as follows:

GET [base]/Observation?subject:Patient.birthdate=ge2001-01-01

Without chaining you would have needed multiple search requests to perform this search:

  1. Get all Patients with the specified birthdate
  2. Get all Observations which contain a reference to the Patients that were gathered in the previous request.

It would be nessesarry to do some kind of filtering and processing on the client side.

Please note, you can only chain search parameters of the type reference that point to elements of type Reference.reference. In FHIR, references using so-called logical references (using Reference.identifier) are also allowed. However, because a FHIR Server cannot reasonably verify that the resources behind these references exist, chaining on these elements are not supported.

Chaining can be very efficient as you can cross more than one resource in a single chain. For example, try to search for all Observations based on a MedicationRequest for a Medication that contains the ingredient Lorazepam (substance):

GET [base]/Observation?basedOn:MedicationRequest.medication.ingredient-code=387106007

Note that except for the last parameter, all parameters are of the type reference.

1.2 Reverse chaining

Instead of filtering resources based on properties of referenced resources, it's also possible to filter resources based on properties of resources referring to them. For example, you may want to select all Patients having an appointment today. In this case, you would need to use the _has parameter followed by:

  • the type of the referring resource (e.g. Encounter)
  • the name of the search parameter containing the reference to the base resource (e.g. patient)
  • the name of the search parameter you want to filter upon (e.g. date) and the comparison value

These parts are separated by a :. The correct syntax of this search would be:

GET [base]/Patient?_has:Encounter:patient:date=[today]

Let's make it a bit more complex. Say you want to select all Patients having an Observation performed by a specific Organization, let's assume the identifier of this Organization is 1234.

GET [base]/Patient?_has:Observation:patient:performer:Organization.identifier=1234

It's also possible to add more than one _has parameter to your search. But be aware that this would result in a joined set of two independently executed _has queries. For example, the following query:

GET [base]/Patient?_has:Encounter:patient:date=[today]&_has:Encounter:patient:practitioner=1234

will not result in a list of Patients having an appointment with Practitioner 1234 today. Instead, it will return all Patients having an appointment today as well as all Patients having an appointment with Practitioner 1234 at any point in time.

2. Filter

A _filter expression can be used for more advanced searches in which multiple queries need to be combined. _filter provides a range of advantages compared to the regular way of using search parameters:

  • Being able to use logical OR and NOT operatiors
  • Use parentheses to form logical groups
  • Use aliases for commonly used Code Systems

For example, you could use a filter to select all Patients from Organizations 1234 and 1235 with inactive records.

GET [base]/Patient?_filter=active eq false and ( organization eq 1234 and organization eq 1235 )

Note that this expression is equivalent to:

GET Patient?active=false&organization=1234,1235

It gets more challenging when you want to combine these queries with an or. For example, when you want to select all Patients with an inactive record as well as all Patients from Organizations 1234 or 1235 you will definitely need a filter.

GET [base]/Patient?_filter=active eq false or ( organization eq 1234 or organization eq 1235 )

Would you have tried to define this expression as following:

GET Patient?active=false,organization=1234,1235

The server would interpret this as a search for Patients where the value of active is either 'false', 'organization=1234' or '1235', which is obviously not what we try to achieve.

All search parameters that are normally defined for resources can be used in a _filter expression.

Additionally, negated expressions can be used in a filter search request. This feature can otherwise only be achieved with the :not modifier for token search parameters.

For example, the following search request retrieves all Patient resources which contain a different name than "Sarah":

GET Patient?_filter=name ne Sarah 

Another powerful search function is the compartment search. The syntax of a compartment search is as follows:

GET [base]/[compartment]/[id]/[ResourceWithOutgoingReference]?[SearchParam]=<value>

Adding search parameters to a compartment search is optional. To return all resources in a compartment use a * instead. For example, use the following request to select all resources that have any reference to the Patient with id 1234.

GET [base]/Patient/1234/*

You can also define the types of resources you want to retrieve using the _type parameter. For example, the following request will return all Observations and Encounters of the Patient with id “1234”.

GET [base]/Patient/1234/?_type=Observation,Encounter

Finally, you can add additional criteria to your compartment search. Let's say you want to retrieve all Observation resources that contain a reference to the Patient with the logical id 1234 and which have the observation status set to final. The following requests, of which the first one is the compartment search, are both valid and will produce the exact same results.

GET [base]/Patient/1234/Observation?status=final
GET [base]/Observation?subject=Patient/1234&status=final

4. Composite search parameters (pairs)

Composite search parameters are parameters that take a combination of values to evaluate. For example, the composite search parameter component-code-value-quantity evaluates Observations against a pair of values for the elements code and valueQuantity. The values are separated by $. The request given below returns all Observations with the given LOINC code and a value greater than 80kg.

GET [base]/Observation?component-code-value-quantity=http://loinc.org|29463-7$gt80||kg

Note that this search would be equivalent to the following request using the code and value-quantity search parameters:

GET [base]/Observation?code=http://loinc.org|29463-7&value-quantity=gt80||kg

The power of composite search parameters becomes more clear in the following examples based on the composite search parameter characteristic-value, which searches for the pair of values for characteristic and value.

GET [base]/Group?characteristic-value=gender$mixed
GET [base]/Group?characteristic-value=gender$mixed,owner$peter

The first one returns all Groups where the gender is mixed. In this case using the parameters characteristics and value (e.g. characteristic=gender&value=mixed) would not return the same results. Groups having the characteristic gender and the value female and having the value mixed for another key would be returned as well. The second one returns all Groups where gender is mixed as well as groups where the owner is peter. These kinds of searches would be impossible to express without composite search parameters.

5. Including other resources in result

5.1 Include

Sometimes it can be useful to include related resources in your search results. For example, when you search for Observations, it can be useful to include the Patient resources that are subject of the returned Observations.

GET [base]/Observation?_include=Observation:patient
GET [base]/Observation?_include=Observation:patient&patient=1234

The first request returns all Observations and the related Patient resources. The second one returns all Observations for a specific Patient with id=1234 as well as the Patient resource itself. It is possible to include multiple related resources by repeating the include parameter.

6.2 Revinclude

Instead of adding resources that are referenced from the returned resources in your search results, you may also want to add resources that refer to them. For example, when you are searching for Patients, you may want to add Observations and Conditions that have a reference to these Patients in their subject fields. The request below returns a specific Patient (the Patient with id=1234) and includes all Observation and Condition resources of this Patient in the search Bundle.

GET [base]/Patient?_revinclude=Observation:patient&_revinclude=Condition:patient&_id=1234

A revinclude may come in handy in combination with a reverse chaining expression. You can get back the resources due to which the matches of the reverse chaining where included in the search results:

GET [base]/Patient?_has:Observation:patient:code=29463-7&_revinclude=Observation:patient

6.3 Recursion

The include and revinclude process can be recursive. With a :recurse modifier, resources can be included based on parameters defined for resources that were included through a different _include or _revinclude expression:

GET [base]/Patient?_has:Observation:patient:code=29463-7&_revinclude=Observation:patient&_revinclude:recurse=DiagnosticReport:result

6. Operations

FHIR provides a set of operations that can be invoked on the base endpoint, a resource type, a resource instance or a specific version of a resource instance. Usually, operations are invoked using a POST request, but in some cases, a GET request is allowed as well. To invoke an operation its name should be prefixed with a $. Below is an example of an operation that retrieves all resources related to Patient with ID=1234. Note that this is similar to a compartment search.

POST [base]/Patient/1234/$everything

The body of a request invoking an operation often contains a Parameters resource to specify the in parameters of the operation. For example, in the everything operation optional start and end dates can be provided. The body may also contain other resources, for example, the resource you want to validate by using the validate operation.

Operations can be resource specific or apply to all resource types. In the last case, we speak about base operations. It is also possible for implementations to define their own operations using the OperationDefintion resource.

6.1 Validate

One operation that is often used is the validate operation. Let's say you want to validate a Patient resource against the daf-patient profile. You could do so by running the following request and adding your Patient resource in the request body.

POST [base]/Patient/$validate?profile=http://hl7.org/fhir/StructureDefinition/daf-patient

6.2 Expand

ValueSets are often composed of codes from other ValueSets or CodeSystems by using include and exclude filters. As you will learn (or already have learned) in the [Terminology] module, ValueSets contain both a compose element, which is used to create a ValueSet, and an expansion element, which can be used by a FHIR Terminology server to return all codes in a ValueSet. Below are a couple of example requests.

GET [base]/ValueSet/1234$expand
POST [base]/ValueSet/$expand
POST [base]/ValueSet/1234/$expand

The GET request can be used when a ValueSet is already known to the server. When a POST request is used, the request body contains a Parameters resource in which one or more in parameters are defined. The Parameters may include the ValueSet resource or contain a canonical url of an existing ValueSet. The returned result of the expand operation is the expanded ValueSet resource. Note that the expansion can change over time depending on changes in included ValueSets and CodeSystems.


Real-life examples


Nictiz

Nictiz is the centre of expertise for standardization and eHealth in The Netherlands. HL7 Netherlands core and MedMij profiles are published on Simplifier. MedMij is a national project that aims to give Dutch citizens integrated access to all their health data in one personal health environment. FHIR is used as a standard to exchange health information between the involved parties. The profiles are based on standardized clinical building blocks called Health and Care Information Models (HCIM).

The Implementation Guide of Nictiz contains a section called 'List of invocations' in which examples are given on how the data can be retrieved using the Dutch national FHIR model.

Castor EDC

Castor EDC is a company that supports researchers in collecting data for medical research. The Castor EDC application is linked to the electronic health record of the hospital, in which the researchers can mark his or her patients for inclusion. We implemented a Vonk Facade server for Castor EDC in a university hospital in the Netherlands, which maps the data from their clinical data warehouse to FHIR resources and sends them to the Castor EDC application. In the first pilot, three resources were implemented: Patient, Observation and Condition.

The Castor EDC application needs to be able to retrieve all resources from a specific Patient. The include and revinclude functions are supported in the implementation and can be used to retrieve a specific Patient including all Observations and Conditions referring to this Patient in the subject field.

[base]/Patient?_revinclude=Observation:patient&_revinclude=Condition:patient&_id=[id]

Note that in this query it's only possible to filter on search parameters implemented for the Patient resource. Researchers should however be able to filter Observations and Conditions on date. To do so, a more complex query is required. For example, to retrieve a specific Patient (let's assume the id of this Patient is 'example') as well as all his/her Observations since January 2018, the following query can be used.

[base]/Patient/example/Patient,Observation?date=ge2018-01-01T00:00:00.000+02:00

Exercise


In this exercise you will run some advanced search requests. You may either use a REST client like Postman to run your requests or paste them directly in your browser. Start by reading the case description. Here below are a couple of links that you may find useful during this exercise:

    Case description
    Hospital X has a FHIR server which they use to exchange data with patients and regional care providers. The hospital started building a portal in which patients and care providers can access their data. The hospital wants to add some more advanced search functionality in this portal and asks you for advice how to do this in FHIR.
  • Retrieve the entire health record for a specific Patient
  • Retrieve all observations from patients having a specific condition, e.g. a malignant lung tumor
  • Retrieve all appointments of patients from a specific general practitioner
  • Retrieve all observations that are either final or recently measured
  • Retrieve all observations after a specific date with a specific code

Steps to follow

Either try out these searches yourself or follow the steps below to get to formulate the right search request. In this exercise we assume you use the public Vonk server, but you may use any FHIR server you want. Note however that not all search functionalities may be implemented in all FHIR servers.

1. Retrieve the entire health record for a specific Patient

  1. There are two ways to do this. Try them now if you don't need any hints.
  2. Remember the chapter about compartment searches? Let's try this.
  3. There's also an operation available which you could use. Check out if it returns the same results as the previous step.

2. Retrieve all observations from patients suffering from a specific condition, e.g. a malignant lung tumor

  1. The easiest way to produce this request is to split it into smaller, simpler requests first. You can either try this out for yourself first or continue with the steps given below.
  2. First try to retrieve all conditions with SNOMED code 21119008 (in real-life more codes might be needed, but for this exercise you can assume that this is the only code used for a malignant lung tumor).
  3. Next try to retrieve all patients suffering from these conditions. Hint: you will need to use reverse chaining.
  4. Now comes the hardest step. You will need to combine chaining and reversed chaining in one request. Search for all observations and add a chained search parameter to filter on patients meeting the search criteria.
  5. Stop reading if you don't want to see the answer.
  6. Still having trouble? Try out the following request: GET http://fire.ly/Observation?patient._has:Condition:patient:code=http://snomed.info/sct|21119008

3. Retrieve all appointments of patients from a specific general practitioner

  1. Like the previous assignment, let's start with a simple request. Quit reading if you don't need any help.
  2. First, filter all patients on a specific general practitioner based on the ID.
  3. Next, try to retrieve all encounters on patients meeting the search criteria.
  4. Just one more hint: you will need to do some chaining again.
  5. Stop reading if you don't want to see the answer.
  6. The following request should work: GET http://fire.ly/Encounter?patient.general-practitioner=1 (try out a different ID or add data to the server if you don't get any results).

4. Retrieve all observations that are either final or recently measured

  1. Define the search parameters that are required for this search request.
  2. To retrieve recent observations use a recent datetime, let's say two weeks ago.
  3. Use the correct prefix to filter on datetimes equal or above the given datetime.
  4. Write out the search request and try it out.
  5. Check if it gives the expected results and/or read further to check the answer.
  6. Based on a filter, the request should be something like: GET http://fire.ly/Observation?_filter=status eq final or (date=ge2018-01-01T00:00.000+02:00)

5. Retrieve all observations after a specific date with a specific code

  1. Find out which composite search parameters you need for this search request.
  2. Pick any code, e.g. 150456 and date, e.g. 2010-01-01
  3. Run the search request.
  4. The search request should be something like: http://fire.ly/Observation?code-value-date=code$150456,date$ge2010-01-01T00:00.000+02:00
  5. Try out different values for code and date if the search request doesn't return any values.

Feedback


We are always looking for ways to improve our products. The Profiling Academy was built using our own IG-editor in Simplifier. If you have any feedback on this module or on our Profiling Academy in general, please leave a comment in the Issue Tracker of the project.


Get in touch


Start profiling

Most modules end with an exercise. Use Forge to start profiling yourself. Just contact us at simplifier@fire.ly if you need any help.

Learn more

Follow one of our predefined or tailor-made courses. We will make sure you know FHIR inside-out.

Need help or advice?

Let us assist you with your FHIR use case. Visit our company website to know more about our services or get into contact with Rien Wertheim right away.