HDSI Query Syntax
Both the HDSI and AMI use a general purpose query syntax which provide methods for matching records within the database on those interfaces. The HDSI query syntax is mapped to the RIM objects (wire format) returned by those APIs.
The HDSI query syntax is translated from HTTP query headers to LINQ expression trees and then used within the SanteDB iCDR and dCDR instances where needed (running business rules, filtering lists, querying against the database).

Syntax

When passing HDSI queries over HTTP you must URL encode them. The examples here are not URL encoded and only suitable in matching and care planning rules.

Property Paths

In general an HDSI query is executed using parameters found in the object which is being queried. For example, an HDSI query for all patients named TEST would be:
1
name.component.value=TEST
Copied!
This query is translated into a .NET expression tree roughly equivalent to
1
o => o.Names.Any(name => name.Component.Any(component => component.Value == "TEST"))
Copied!
The properties are traversed using dotted notation matching the property names in the returned object as illustrated below.

And/Or Semantics

By default all filters passed in an HDSI query string are AND unless the property path is the same, in which case multiples are treated as OR. For example, to find patients with any name component of either JOHN or SMITH
1
name.component.value=JOHN&name.component.value=SMITH
Copied!
However, if guards are applied, we can execute given name JOHN and family name SMITH
1
name.component[Given].value=JOHN&name.component[Family].value=SMITH
Copied!
If we wanted to query for given name of JOHN or JOHNNY and family name of SMITH
1
name.component[Given].value=JOHN&name.component[Given].value=JOHNNY&name.component[Family].value=SMITH
Copied!

Operators

Operators allow for the filtering of values based on equality, negation, etc. The operators for HDSI query syntax are listed below:
Operation
Operator
Example
Equal
=
name.component.value=JOHN
Not Equal
=!
name.component.value=!JOHN
Less Than
=<
dateOfBirth=<2020-01-01
Less Than or Equal
=<=
dateOfBirth=<=2020-01-01
Greater Than
=>
dateOfBirth=>2020-01-01
Greater Than or Equal
=>=
dateOfBirth=>=2020-01-01
About Equal (Pattern)
=~
name.component.value=~JO*
Starts With
=^
name.component.value=^JO
Ends With
=$
name.componentvalue=$HN

Collection Guards

By default any filter which is applied to a property which is a collection (identifiers, name, addresses, etc.) is filtered as ANY. The following example illustrates a match where any identifier of a patient equals 123-234-234:
1
identifier.value=123-234-234
Copied!
In order to specify a particular instance on a traversal a guard is applied, guards are applied using square brackets and the code mnemonic of the classifier you want to guard on. For example, if we wanted only patients having an SSN of 123-234-234:
1
identifier[SSN].value=123-234-234
Copied!
Classifiers on properties will depend on the type of property being filtered, a common list of properties and their classifiers are included below.
Type
Classifier
Example
Addresses
Address Use
address[HomeAddress].component.value=ON
Names
Name Use
name[Legal].component.value=SMITH
Identifiers
Identifier Assigner
identifier[SSN].value=123-123-123
Telecoms
Telecom Use
telecom[WorkPlace].value=304-043-2039
Relationships
Relationship Type
relationship[Mother].target=UUID
Participations
Participation Role
participation[RecordTarget].player=UUID
Name / Address Component
Component Type
name.component[Given].value=JOHN
If using multiple classifiers you can either represent them individually
1
name[Legal].component.value=SMITH&name[OfficialRecord].component.value=SMITH
Copied!
However this is not recommended as the query builder will create two clauses, the query above roughly translates to:
1
o => o.Names.Where(
2
guard => guard.NameUse.Mnemonic == "Legal"
3
).Any(
4
name => name.Component.Any(
5
component => component.Value == "SMITH"))
6
|| o.Names.Where(
7
guard => guard.NameUse.Mnemonic == "OfficialRecord")
8
.Any(
9
name => name.Component.Any(
10
component => component.Value == "SMITH"))
Copied!
Instead you can combine the guards:
1
name[Legal|OfficialRecord].component.value=SMITH
Copied!
Which translates to a reduced LINQ expression:
1
o => o.Names.Where(
2
guard => guard.NameUse.Mnemonic == "Legal"
3
|| guard.NameUse.Mnemonic == "OfficialRecord")
4
.Any(
5
name => name.Component.Any(
6
component => component.Value == "SMITH"))
Copied!

Casting

Sometimes an occasion arises where you wish to execute a sub-filter on a property which is of the wrong type. For example, if we wanted to filter for patients who have Mother with a date of birth before 1960, we would expect this query to work:
1
relationship[Mother].target.dateOfBirth=<1960
Copied!
However, looking at the traversal for target on the EntityRelationship class, the type is of Entity rather than a Person. Entity does not have a dateOfBirth , we need to tell the query engine that we want to further restrict the target to be a Person, this is done with the CAST operator:
1
relationship[Mother].[email protected].dateOfBirth=<1960
Copied!

Coalesce

Sometimes a traversal path may not have a value, this can cause issues when the iCDR attempts to execute filters against in-memory objects such as in the care planner or matching engine. In these scenarios you can use the coalesce operator (also known as the "Elvis" operator). This operator does null-safe traversal, for example, to match a patient with a gender code which may or may not be present:
1
genderConcept?.mnemonic=Male
Copied!

Variables

Variables are either defined by the user (for example, in the data retention service) or by the host context (such as $index in the CDSS for repeated actions, or $input in the matcher for the inbound record).
Variables may be referenced as the value, for example, if a $mothersBirth variable was defined in a retention policy, it could be used in the filter as:
1
relationship[Mother][email protected]=<$mothersBirth
Copied!
Variables can also be used as the root of another HDSI expression, for example, comparing the city in which an $input patient lives.
1
address.component[City].value=$input.address.component[City].value
Copied!

Extension Functions

The default matching operations may be extended via SanteDB's query extension methods. These methods allow for custom matching parameters. These extension functions are enumerated below (with a discussion on which plugins must be enabled to activate them).
Functions are applied in the format:
1
property=:(extension|parameter1,parameter2)value
Copied!

Date Difference

The date difference function is enabled on PostgreSQL and FirebirdSQL ORM providers and require no additional configuration.
1
:(date_diff|otherDate)value
Copied!
Parameter
Opt
Description
otherDate
Required
The other date to compare to
(return)
The difference between the property and the otherDate
For example, to filter for patients born within 3 years of 1990
1
dateOfBirth=:(date_diff|1990)<3y
Copied!

Date Component Extract

The date extract component extracts a part of the date compares with the property.
1
:(date_extract|month/year/day/week/hour/minute)value
Copied!
Parameter
Opt
Description
datePart
Required
The part of the date to extract (year, month, day, etc.)
(return)
The extracted part of the date
For example, to filter for patients born during the month of May
1
dateOfBirth=:(date_extract|month)5
Copied!

Substring

Extracts a portion of a string and matches it with the provided value.
1
:(substr|start,[length])value
Copied!
Parameter
Opt
Description
start
Required
The starting position in the string
length
Optional
The length to extract
(return)
The extracted part of the string
For example, to filter for patients who have an identifier where the first 5 digits match 12345.
1
identifier.value=:(substr|0,5)12345
Copied!

Approximate Match

Approximate matching is enabled when the SanteDB matcher plugin is enabled in the configuration for the dCDR or iCDR. The approximate matching function will use a combination of pattern, phonetic, and string difference functions to determine matching.
1
:(approx|otherString)
Copied!
Parameter
Opt
Description
otherString
Required
The other string to compare approximation to
(return)
True if the server determines the property is approximately the same as otherString
For example, to filter for patients who have a name which sounds like, is about the same as, or minor typo's from JIHN (i.e. match JOHN, JOHNNY, JON, etc.).
1
name.component.value=:(approx|JIHN)
Copied!

Sounds Like

Uses the configured phonetic algorithm to determine whether the supplied string sounds like the stored property value.
1
:(soundslike|otherString,[algorithm])
Copied!
Parameter
Opt
Description
otherString
Required
The other string to compare
algorithm
Optional
Dictates the algorithm to use (soundex, metaphone, dmetaphone)
(return)
True if the server determines the property sounds like otherString
For example, to filter for patients who have a name which sounds like Tyler (i.e. match Tyler, Tiler, etc.)
1
name.component.value=:(soundexlike|TYLER)
Copied!

Phonetic Difference

The phonetic difference function is used to compare the difference in phonetic codes between two values. This function by default uses the SOUNDEX algorithm and then performs a LEVENSHTEIN function against the result.
1
:(phonetic_diff|otherString,[algorithm])value
Copied!
Parameter
Opt
Description
otherString
Required
The other string to compare
algorithm
Optional
Dictates the algorithm to use (soundex, metaphone, dmetaphone)
(return)
The difference (0 - 4) in soundex codes

Performance Tips

When writing your HDSI queries, you can ensure that you have higher performance by structuring your queries in a particular way. Some recommendations:
  1. 1.
    Use Guards: Whenever possible use guard conditions, this reduces the records to be filtered by the database system and (if you've partitioned tables) can dictate particular partitions to use.
  2. 2.
    Combine Guard Conditions: You can combine guard conditions as illustrated above, this ensures that results are filtered with one EXISTS condition in the database rather than multiple
  3. 3.
    Special Indexing: You can (and should) apply additional indexing to your SanteDB database to take advantage of common functions and queries. For example, if you often use the SOUNDEX algorithm to search for addresses, you might want to create an index like: CREATE INDEX addr_cmp_val_soundex_idx ON addr_cmp_val_tbl(SOUNDEX(val));
  4. 4.
    Partition Tables: You can partition collection tables based on their classifiers , this can make it easier for the database system to filter data based on classifiers. There is an partitioning script in the SQL\ directory of your iCDR installation folder. This sets up partitions for the entity relationship and act participations.
Last modified 30d ago