Theory
SPARQL has two negation constructs, and choosing wrong is a real bug source.
FILTER NOT EXISTS { P } keeps a row only if pattern P has no match given the current bindings:
SELECT ?ninja WHERE {
?ninja a :Ninja .
FILTER NOT EXISTS { ?master :teaches ?ninja . }
}
MINUS { P } removes rows that share bindings with the solutions of P:
SELECT ?ninja WHERE {
?ninja a :Ninja .
MINUS { ?ninja :teaches ?anyone . }
}
The difference that matters
They diverge when the negated pattern shares no variable with the outer query:
FILTER NOT EXISTS { ?x a :Thing }evaluates the inner pattern with the current row's bindings — if any:Thingexists at all, it removes every row.MINUS { ?x a :Thing }shares no variable with the outer row, so there is nothing to subtract on — it removes nothing.
Rule of thumb: reach for FILTER NOT EXISTS by default (its meaning is compositional and predictable); use MINUS when you specifically want set-difference on shared variables and find it more readable.