Filtering with q
q is the query parameter that picks entities by what their attributes hold, such as every vehicle faster than 50, every building whose air quality reads exactly 6, or every sensor in Madrid. It runs on its own or beside a type, an id list or a geoquery, and the same language filters subscriptions, temporal reads and snapshot purges.
This guide walks the language from a first comparison to hops across relationships. Every operator, every value shape and the exact refusal rules are in Query language, and this page is the road in.
In Rust the expression is built with q::attr(...) rather than written as text, so a bad attribute name or operand is reported by build() before any request leaves the machine.
A first query
A term names an attribute, an operator and an operand. This one asks for vehicles whose speed is greater than 50.
- http
- curl
- rust
- python
GET /ngsi-ld/v1/entities?type=Vehicle&q=speed>50
Accept: application/json
curl -i 'http://localhost:1056/ngsi-ld/v1/entities?type=Vehicle&q=speed>50' \
-H 'Accept: application/json'
let page = client
.entities()
.query()
.entity_type("Vehicle")
.q(q::attr("speed").gt(50))
.send()
.await?;
response = requests.get(
f"{BASE}/entities",
params={
"type": "Vehicle",
"q": "speed>50",
},
headers={"Accept": "application/json"},
)
assert response.status_code == 200
HTTP/1.1 200 OK
Content-Type: application/json
[
{
"id": "urn:ngsi-ld:Vehicle:fast",
"type": "Vehicle",
"speed": {
"type": "Property",
"value": 80
}
}
]
Of two vehicles, only the one at 80 is answered, and the one at 20 fails the test. A term with no operator at all tests that the attribute exists, so q=speed selects every entity that has one, whatever the value.
Terms combine. A ; means both must hold, a | means either, and parentheses group them, as in q=(speed>50|rpm>3000);brandName=="Mercedes". With count=true the answer carries an NGSILD-Results-Count header saying how many entities match in total, which is how you size a dashboard badge before paging.
Strings, numbers and times
Strings go in double quotes, as in q=brandName=="Mercedes". Numbers are written bare and compared by their exact decimal value, so 1.0 equals 1.00 and a value beyond the eighteenth decimal is still told apart. true and false are bare too, after == or != only.
A date-time is written bare, and compared as an instant against attributes declared as times. observedAt is the usual target, reached as a sub-attribute of its attribute.
- http
- curl
- rust
- python
GET /ngsi-ld/v1/entities?type=Building&q=temperature.observedAt>2024-03-01T00:00:00Z;temperature.observedAt<2024-09-01T00:00:00Z
Accept: application/json
curl -i 'http://localhost:1056/ngsi-ld/v1/entities?type=Building&q=temperature.observedAt>2024-03-01T00:00:00Z;temperature.observedAt<2024-09-01T00:00:00Z' \
-H 'Accept: application/json'
let from: DateTime = "2024-03-01T00:00:00Z".parse()?;
let until: DateTime = "2024-09-01T00:00:00Z".parse()?;
let page = client
.entities()
.query()
.entity_type("Building")
.q(q::attr("temperature")
.sub_attr("observedAt")
.gt(from)
.and(q::attr("temperature").sub_attr("observedAt").lt(until)))
.send()
.await?;
response = requests.get(
f"{BASE}/entities",
params={
"type": "Building",
"q": "temperature.observedAt>2024-03-01T00:00:00Z;temperature.observedAt<2024-09-01T00:00:00Z",
},
headers={"Accept": "application/json"},
)
assert response.status_code == 200
HTTP/1.1 200 OK
Content-Type: application/json
[
{
"id": "urn:ngsi-ld:Building:observed-hot",
"type": "Building",
"temperature": {
"type": "Property",
"value": 20,
"observedAt": "2024-06-01T00:00:00Z"
}
}
]
The January reading fails the window. Quote the same instant and it becomes an ordinary string, matched only against values stored as strings, so q=temperature.observedAt=="2024-06-01T00:00:00Z" selects nothing here. That one pair of quotes is the most common mistake in the language.
Ranges and lists
A range matches everything between two bounds, both included.
- http
- curl
- rust
- python
GET /ngsi-ld/v1/entities?type=Vehicle&q=speed==10..50
curl -i 'http://localhost:1056/ngsi-ld/v1/entities?type=Vehicle&q=speed==10..50'
let page = client
.entities()
.query()
.entity_type("Vehicle")
.q(q::attr("speed").within(10, 50))
.send()
.await?;
response = requests.get(
f"{BASE}/entities",
params={
"type": "Vehicle",
"q": "speed==10..50",
},
)
assert response.status_code == 200
A list matches any of its members, as in q=colour=="black","red". Negated, != with a range holds for every other value, and with a list for none of them. Ranges and lists only follow == and !=.
Reaching inside an attribute
A dot walks into sub-attributes, so temperature.accuracy<0.2 tests the accuracy of the temperature. Square brackets reach into a structured value by its member names.
- http
- curl
- rust
- python
GET /ngsi-ld/v1/entities?type=Sensor&q=address[city]=="Madrid"
Accept: application/json
curl -i -g 'http://localhost:1056/ngsi-ld/v1/entities?type=Sensor&q=address[city]=="Madrid"' \
-H 'Accept: application/json'
let page = client
.entities()
.query()
.entity_type("Sensor")
.q(q::attr("address").member("city").eq("Madrid"))
.send()
.await?;
response = requests.get(
f"{BASE}/entities",
params={
"type": "Sensor",
"q": 'address[city]=="Madrid"',
},
headers={"Accept": "application/json"},
)
assert response.status_code == 200
HTTP/1.1 200 OK
Content-Type: application/json
[
{
"id": "urn:ngsi-ld:Sensor:1",
"type": "Sensor",
"address": {
"type": "Property",
"value": {
"city": "Madrid",
"street": "Gran Via"
}
}
}
]
The sensor in Avila has an address too, but its city reads differently, so it fails. Member names inside the brackets are expanded through your @context like attribute names, and a path can chain, as in sensor.rawdata[airquality.particulate], which goes through a sub-attribute and then two levels into the value.
Following relationships
A term in braces looks at the entity a Relationship points at, and tests it there.
- http
- curl
- rust
- python
GET /ngsi-ld/v1/entities?type=Car&q=isParkedIn{category}=="commercial"&join=inline&joinLevel=1
Accept: application/json
curl -i -g 'http://localhost:1056/ngsi-ld/v1/entities?type=Car&q=isParkedIn{category}=="commercial"&join=inline&joinLevel=1' \
-H 'Accept: application/json'
let page = client
.entities()
.query()
.entity_type("Car")
.q(q::attr("isParkedIn").linked("category").eq("commercial"))
.join(Join::Inline)
.join_level(NonZeroU32::MIN)
.send()
.await?;
response = requests.get(
f"{BASE}/entities",
params={
"type": "Car",
"q": 'isParkedIn{category}=="commercial"',
"join": "inline",
"joinLevel": "1",
},
headers={"Accept": "application/json"},
)
assert response.status_code == 200
HTTP/1.1 200 OK
Content-Type: application/json
[
{
"id": "urn:ngsi-ld:Car:c1",
"type": "Car",
"isParkedIn": {
"type": "Relationship",
"object": "urn:ngsi-ld:Building:commercial"
}
}
]
The comparison is answered by the building, and the car is what comes back. join=inline asks the broker to look across the relationship at all, and joinLevel says how many hops it may follow. Hops nest, so the cars parked in the northern district are q=isParkedIn{inDistrict{zone}}=="north"&joinLevel=2. A hop with a comparison always needs the join parameter, and a hop without one tests that the linked entity has the attribute.
Writing q in a URL
The examples here are left unencoded so they stay readable. In a real request, percent-encode the expression, namely the quotes, the >, the | and the ;. The Rust client and requests do this for you. A long expression can also travel in a body, with POST /ngsi-ld/v1/entityOperations/query, which carries the same q plus the other parameters as a JSON document.
Where the same language runs
Subscriptions carry a q, checked when the subscription is written and applied to every change. Temporal queries use q to pick the entities whose history is answered. A snapshot purge takes a q over the snapshots' own members (see Snapshots), and a forwarded query carries its q to the context source, spelled in that source's @context.
What gets refused
Each of these answers 400 BadRequestData.
- A bare word, such as
status==active. Quote it asstatus=="active". true,falseor a URI after an ordering operator.- A time operand with a numeric offset or a seventh fractional digit.
- A path deeper than 32 levels.
- A hop without
join.
The full list, with the reasons, is under What gets refused.