Patterns

OPTIONAL, FILTER, BIND, UNION, and compound patterns.

Basic triple patterns require all patterns to match. Real scripts need more flexibility: optional data, conditions, computed values, alternatives.

The examples on this page assume the following prefixes:

PREFIX tasks:  <https://example.org/spec/tasks#>
PREFIX people: <https://example.org/people/>
PREFIX xsd:    <http://www.w3.org/2001/XMLSchema#>

OPTIONAL

Include results even when some data is missing:

PREFIX tasks: <https://example.org/spec/tasks#>

SELECT ?task ?title ?description WHERE {
    ?task a tasks:Task ;
          tasks:title ?title .
    OPTIONAL {
        ?task tasks:description ?description .
    }
}

Tasks without a description still appear in results. ?description is unbound for those rows.

OPTIONAL blocks can contain multiple patterns. All patterns in the block must match, or the entire block is skipped:

PREFIX tasks:  <https://example.org/spec/tasks#>
PREFIX people: <https://example.org/people/>

OPTIONAL {
    ?task tasks:assignee ?person .
    ?person people:email ?email .
}

If the task has an assignee but that person has no email, neither ?person nor ?email is bound.

FILTER

Keep only results that meet a condition:

PREFIX tasks: <https://example.org/spec/tasks#>

SELECT ?task ?title WHERE {
    ?task a tasks:Task ;
          tasks:title ?title ;
          tasks:priority ?priority .
    FILTER(?priority = "high" || ?priority = "critical")
}

Common filter expressions:

PREFIX tasks: <https://example.org/spec/tasks#>
PREFIX xsd:   <http://www.w3.org/2001/XMLSchema#>

FILTER(?count > 10)                     # Numeric comparison
FILTER(?status != tasks:Completed)       # Not equal
FILTER(CONTAINS(?title, "Q3"))          # String contains
FILTER(STRSTARTS(?code, "TASK-"))       # Starts with
FILTER(BOUND(?description))             # Variable is bound
FILTER(!BOUND(?assignee))               # Variable is NOT bound
FILTER(REGEX(?name, "^alice", "i"))     # Regex (case insensitive)
FILTER(?date > "2025-01-01"^^xsd:date)  # Date comparison

BIND

Compute a new value and assign it to a variable:

PREFIX tasks: <https://example.org/spec/tasks#>

SELECT ?task ?label WHERE {
    ?task a tasks:Task ;
          tasks:title ?title ;
          tasks:status ?status .
    BIND(CONCAT(?title, " [", STR(?status), "]") AS ?label)
}

BIND is evaluated for each result row. The variable must not already be bound.

Common BIND patterns:

BIND(NOW() AS ?timestamp)                          # Current time
BIND(URI(CONCAT("urn:task:", ?id)) AS ?taskUri)    # Construct a URI
BIND(COALESCE(?nickname, ?name) AS ?displayName)   # First non-null
BIND(IF(?priority = "high", 1, 0) AS ?isUrgent)   # Conditional
BIND(STRLEN(?description) AS ?length)              # String length

VALUES

Provide inline data to match against:

PREFIX tasks: <https://example.org/spec/tasks#>

SELECT ?task ?title WHERE {
    VALUES ?status { tasks:Open tasks:InProgress }
    ?task a tasks:Task ;
          tasks:title ?title ;
          tasks:status ?status .
}

This finds tasks with status Open or InProgress. VALUES works like an inline table.

UNION

Match alternative patterns:

PREFIX tasks: <https://example.org/spec/tasks#>

SELECT ?item ?title WHERE {
    {
        ?item a tasks:Task ;
              tasks:title ?title .
    } UNION {
        ?item a tasks:BugReport ;
              tasks:title ?title .
    }
}

Results include matches from either branch. In practice, if BugReport is a subclass of Task, you'd just select Task and inference handles the rest.

Nested Patterns

Patterns compose. OPTIONAL blocks can contain FILTERs, BINDs can use OPTIONAL variables, etc.:

PREFIX tasks:  <https://example.org/spec/tasks#>
PREFIX people: <https://example.org/people/>

SELECT ?task ?title ?assigneeName WHERE {
    ?task a tasks:Task ;
          tasks:title ?title .
    OPTIONAL {
        ?task tasks:assignee ?person .
        ?person people:name ?assigneeName .
        FILTER(?assigneeName != "")
    }
    FILTER(BOUND(?title))
}

See Also

On this page