Theory
A plain Basic Graph Pattern is an inner join: if any triple pattern fails, the whole row disappears. OPTIONAL turns a sub-pattern into a left outer join — the left-hand bindings survive even if the optional part doesn't match; the optional variables are simply left unbound.
SELECT ?ninja ?master WHERE {
?ninja a :Ninja .
OPTIONAL { ?master :teaches ?ninja . }
}
Every ninja is returned; ?master is bound where a teacher exists and unbound otherwise. This is how you express 'list all X, plus their Y if they have one' — the single most common real-world query shape.
Three traps that bite professionals
bound()/COALESCEfor the missing case. To detect or default an absent value, useFILTER(!bound(?master))('ninjas with no teacher') orCOALESCE(?master, :Unknown). A normalFILTER(?master = ...)drops the unbound rows you were trying to keep.- FILTER inside vs outside OPTIONAL. A
FILTERinside theOPTIONAL { }decides whether the optional part matches; the same filter outside runs after the left join and can delete rows you wanted to keep. These are different queries. - Nested / multiple OPTIONALs are order-sensitive. Unlike a BGP, consecutive
OPTIONALs are evaluated left to right and a later one can see variables an earlier one bound. Reordering them can change results, not just speed.