# Completion Queries
Completion queries are sent to the backend to retrieve the completion suggestions.
These queries are individual for each knowledgebase.
The user has to define a query template for each type of online-completion.
## Completion Query Anatomy
Each completion query result **MUST** contain the following variables:
| `?qls_entity` | RDF term, value to be completed | \<book_1\> |
| `?qls_label` | representation of completion item | book title |
| `?qls_alias` | description of the completion item | Book from author ... |
Optionally, you can include `?qls_count` to provide a relevance score (e.g., occurrence count) for sorting results.
## Query Types
There are five completion query types that can be configured:
| `subjectCompletion` | Find subject entities |
| `predicateCompletionContextSensitive` | Find predicates using surrounding query context |
| `predicateCompletionContextInsensitive` | Find predicates without using context |
| `objectCompletionContextSensitive` | Find objects using surrounding query context |
| `objectCompletionContextInsensitive` | Find objects without using context |
| `valuesCompletionContextSensitive` | Find VALUES entries using surrounding context |
| `valuesCompletionContextInsensitive` | Find VALUES entries without using context |
Additionally, `hover` queries can be configured to fetch entity information for tooltips. These are not completion queries and have different result variable requirements (see [Hover Query](#hover-query)).
### Context-Sensitive vs Context-Insensitive
- **Context-sensitive** queries use surrounding triple patterns to narrow results. For example, if you're completing an object where the predicate is `rdf:type`, the query can use that constraint to return only classes.
- **Context-insensitive** queries provide broader fallback results when context isn't available or useful. These are simpler queries that match based on the search term alone.
## Template Context
Each template has the following variables available:
| `prefixes` | list | PREFIX declarations from the document and configuration | `[("rdfs", "http://www.w3.org/2000/01/rdf-schema#"), ("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#")]` |
| `subject` | string | Subject of the current triple | `"?sub"` or `"<http://example.org/entity>"` |
| `local_context` | string | Triple pattern for the completion location | `"?sub ?qls_entity []"` |
| `context` | string | Constraining triples from the query | `"?sub rdf:type <Thing> . ?sub <n> 42"` |
| `search_term` | string | Partial text the user is typing | `"boo"` (when typing "book") |
| `search_term_uncompressed` | string | Expanded IRI if user typed a prefixed name | `"http://www.wikidata.org/prop/direct/P"` (when typing `wdt:P`) |
| `limit` | int | Maximum results (from settings) | `50` |
| `offset` | int | Pagination offset | `0` |
| `entity` | string | The entity being hovered (hover queries only) | `"<http://example.org/entity>"` |
## Templating Engine
The templates are rendered by [Tera](https://keats.github.io/tera/docs), a templating engine.
It provides:
- [Control structures](https://keats.github.io/tera/docs/#control-structures), like **for** and **if**
- [Data manipulation](https://keats.github.io/tera/docs/#manipulating-data), like **filters**, **tests** and **functions**
## Custom Tests
Tests can be used against an expression to check some condition.
There are many [built-in tests](https://keats.github.io/tera/docs/#built-in-tests) but also some custom SPARQL-specific ones:
### variable
Takes a string and checks if it's a SPARQL variable.
**Example**:
```tera
{% if subject is variable %}
Subject is a variable
{% else %}
Subject is not a variable
{% endif %}
```
### containing
Takes a string and checks if it contains a given substring.
**Example**:
```tera
{% if context is containing("rdf:type") %}
Context includes a type constraint
{% endif %}
```
## Example Queries
Below are simplified, generic examples for each query type. These can be adapted to your specific knowledgebase.
### Subject Completion
```sparql
{% include "prefix_declarations" %}
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?qls_entity ?qls_label ?qls_alias ?qls_count WHERE {
{
SELECT ?qls_entity (COUNT(*) AS ?qls_count) WHERE {
?qls_entity ?p ?o .
{% if search_term %}
?qls_entity rdfs:label ?label .
FILTER(STRSTARTS(LCASE(?label), LCASE("{{ search_term }}")))
{% endif %}
}
GROUP BY ?qls_entity
ORDER BY DESC(?qls_count)
LIMIT {{ limit }}
}
OPTIONAL { ?qls_entity rdfs:label ?qls_label }
OPTIONAL { ?qls_entity rdfs:comment ?qls_alias }
}
```
### Predicate Completion (Context-Sensitive)
```sparql
{% include "prefix_declarations" %}
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?qls_entity ?qls_label ?qls_alias ?qls_count WHERE {
{
SELECT ?qls_entity (COUNT(*) AS ?qls_count) WHERE {
{{ context }} {{ local_context }}
}
GROUP BY ?qls_entity
ORDER BY DESC(?qls_count)
LIMIT {{ limit }}
}
OPTIONAL { ?qls_entity rdfs:label ?qls_label }
OPTIONAL { ?qls_entity rdfs:comment ?qls_alias }
}
```
### Predicate Completion (Context-Insensitive)
```sparql
{% include "prefix_declarations" %}
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?qls_entity ?qls_label ?qls_alias ?qls_count WHERE {
{
SELECT ?qls_entity (COUNT(*) AS ?qls_count) WHERE {
{{ local_context }}
}
GROUP BY ?qls_entity
ORDER BY DESC(?qls_count)
LIMIT {{ limit }}
}
OPTIONAL { ?qls_entity rdfs:label ?qls_label }
OPTIONAL { ?qls_entity rdfs:comment ?qls_alias }
}
```
### Object Completion (Context-Sensitive)
```sparql
{% include "prefix_declarations" %}
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?qls_entity ?qls_label ?qls_alias ?qls_count WHERE {
{
SELECT ?qls_entity (COUNT(*) AS ?qls_count) WHERE {
{{ context }} {{ local_context }}
}
GROUP BY ?qls_entity
ORDER BY DESC(?qls_count)
LIMIT {{ limit }}
}
OPTIONAL { ?qls_entity rdfs:label ?qls_label }
OPTIONAL { ?qls_entity rdfs:comment ?qls_alias }
{% if search_term %}
}
```
### Object Completion (Context-Insensitive)
```sparql
{% include "prefix_declarations" %}
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?qls_entity ?qls_label ?qls_alias ?qls_count WHERE {
{
SELECT ?qls_entity (COUNT(*) AS ?qls_count) WHERE {
{{ local_context }}
}
GROUP BY ?qls_entity
ORDER BY DESC(?qls_count)
LIMIT {{ limit }}
}
OPTIONAL { ?qls_entity rdfs:label ?qls_label }
OPTIONAL { ?qls_entity rdfs:comment ?qls_alias }
{% if search_term %}
}
```
### VALUES Completion (Context-Sensitive)
VALUES completions trigger inside `VALUES ?var { ... }` blocks. The `local_context` is a `BIND` expression that connects the VALUES variable to `?qls_entity`:
```
BIND(?var AS ?qls_entity)
```
This allows the context-sensitive query to use surrounding triple patterns that reference the same variable to narrow results. For example, given:
```sparql
SELECT * WHERE {
?s rdf:type <Book> .
?s <title> ?title
```
The `context` will contain the connected triples `?s rdf:type <Book> . ?s <title> ?title`, and `local_context` will be `BIND(?s AS ?qls_entity)`, so the query effectively finds all `?s` that match the surrounding patterns.
```sparql
{% include "prefix_declarations" %}
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
PREFIX wikibase: <http://wikiba.se/ontology#>
SELECT ?qls_entity ?qls_label (GROUP_CONCAT(DISTINCT ?alias; SEPARATOR=", ") AS ?qls_alias) ?qls_count WHERE {
{{ context }} {{ local_context }}
?qls_entity wikibase:sitelinks ?qls_count .
OPTIONAL { ?qls_entity rdfs:label ?qls_label . FILTER(LANG(?qls_label) = "en") }
OPTIONAL { ?qls_entity skos:altLabel ?alias . FILTER(LANG(?alias) = "en") }
{% if search_term %}
}
GROUP BY ?qls_entity ?qls_label ?qls_count
ORDER BY DESC(?qls_count)
LIMIT {{ limit }}
```
### VALUES Completion (Context-Insensitive)
The context-insensitive variant uses only the `local_context` (the `BIND` expression), providing broader results when context isn't available.
```sparql
{% include "prefix_declarations" %}
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
PREFIX wikibase: <http://wikiba.se/ontology#>
SELECT ?qls_entity ?qls_label (GROUP_CONCAT(DISTINCT ?alias; SEPARATOR=", ") AS ?qls_alias) ?qls_count WHERE {
{{ local_context }}
?qls_entity wikibase:sitelinks ?qls_count .
OPTIONAL { ?qls_entity rdfs:label ?qls_label . FILTER(LANG(?qls_label) = "en") }
OPTIONAL { ?qls_entity skos:altLabel ?alias . FILTER(LANG(?alias) = "en") }
{% if search_term %}
}
GROUP BY ?qls_entity ?qls_label ?qls_count
ORDER BY DESC(?qls_count)
LIMIT {{ limit }}
```
**Note**: The `{{ context }}` variable renders to an empty string when not available, so explicit `{% if context %}` checks are only needed when you want fundamentally different query structures based on context presence.
### Hover Query
Hover queries fetch information about an entity for display in tooltips. Unlike completion queries, hover queries use the `entity` template variable and have different result variable requirements:
| `?qls_label` | Label/name of the entity |
| `?qls_alias` | Description or additional details |
```sparql
{% include "prefix_declarations" %}
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?qls_label ?qls_alias WHERE {
{{ entity }} rdfs:label ?qls_label .
OPTIONAL { {{ entity }} rdfs:comment ?qls_alias }
}
LIMIT 1
```
## Tips and Tricks
### Prefix Declarations
Include the `prefix_declarations` template to inherit prefixes from the document and configuration:
```tera
{% include "prefix_declarations" %}
```
### Search Term Filtering
- Use `search_term` for label/text matching
- Use `search_term_uncompressed` when the user types a prefixed IRI (e.g., `wdt:P31`)
- Check both for robust filtering:
```tera
{% if search_term_uncompressed %}
FILTER(STRSTARTS(STR(?qls_entity), "{{ search_term_uncompressed }}"))
{% elif search_term %}
FILTER(CONTAINS(LCASE(?qls_label), LCASE("{{ search_term }}")))
{% endif %}
```
### Adapting to Subject Type
Use the `variable` test to adapt queries based on whether the subject is bound:
```tera
{% if subject is variable %}
{# Subject is a variable like ?s, use broader matching #}
{% else %}
{# Subject is a specific IRI, narrow results to that entity #}
{% endif %}
```
### Sub-Select Queries for Performance
Sub-select queries are a powerful technique to speed up completion queries. By performing aggregation and limiting in an inner query, you reduce the number of entities that need label/detail lookups:
```sparql
SELECT ?qls_entity ?qls_label ?qls_alias ?qls_count WHERE {
{
# Inner query: find and rank entities efficiently
SELECT ?qls_entity (COUNT(*) AS ?qls_count) WHERE {
{{ context }} {{ local_context }}
}
GROUP BY ?qls_entity
ORDER BY DESC(?qls_count)
LIMIT {{ limit }}
}
# Outer query: fetch labels only for the top results
OPTIONAL { ?qls_entity rdfs:label ?qls_label }
OPTIONAL { ?qls_entity rdfs:comment ?qls_alias }
}
```
This pattern is especially effective when:
- The knowledgebase has many entities but you only need a few results
- Label lookups are expensive (e.g., federated queries or large literal indexes)
- You want to order by aggregated values like counts
### Graceful Fallbacks
Use OPTIONAL for non-critical fields:
```sparql
OPTIONAL { ?qls_entity rdfs:label ?qls_label }
OPTIONAL { ?qls_entity rdfs:comment ?qls_alias }
```
### Performance Tips
- Always use `LIMIT {{ limit }}` to cap results
- Put selective filters early in the query
- Use sub-select queries to limit results before expensive operations like label lookups