rdf 2025-08-07

Hi everyone, I have a question about SPARQL. Is there a way to use property paths while skipping certain nodes? There's a stack overflow question which explains what I'm looking for: https://stackoverflow.com/questions/79432700/how-stop-property-path-on-a-certain-condition The idea is to trace over a graph but avoid certain paths. My current implementation is to copy over everything but the problematic node into a new rdflib graph (I'm using python and rdflib for this project) and then run a property path query over that new graph, but I think this is quite ugly, I'd prefer it if I could implement it directly in sparql. Is there a way to achieve this?

I haven't tested this, but after a few minutes thought, I wonder whether something like this might work:

sparql
Select DISTINCT ?o
Where
{
  ## traverse all path*s...
  ?s :path* ?mid.
  ?mid :path* ?o.
  ## subtract paths that have a 'boundry' node in the middle...
  MINUS
  {
    ?s :path* ?mid.
    ?mid :path* ?o.
    ?mid :value true
  }
}

I think it would work but it would be very expensive. I don't think you need to rebind the ?s and ?mid values again. You should be able to remove the ?mid values as you go down the path:

PREFIX : …
SELECT DISTINCT ?o
{
  ?s :path* ?mid MINUS {?mid :value true} .
  ?mid :path* ?o
}

Oh yeah, that's better.

I got a chance to code up your example and play with it a bit, and you're right, it's trickier than it appears. If there's a SPARQL-based solution to this, it's beyond my modest abilities. I'm starting to think this might be an inherently a matter of traversal rather than querying. I like to use this idiom for graph traversals

(defn find-island
  "Returns #{s, o, ...} for all `o`'s linked to `s` not separated by a boundary node in `g`.
  - Where
    - `g` is a graph
    - `s` is a node in `g`
    - `o` is a node in `g` s.t. s :path* o."
  [g s]
  (let [template "
Select ?o ?boundary
Where
{
  VALUES ?s { {{{ subjects }}} }
  ?s eg:path ?o MINUS {?s eg:value []}. ## check for boundary subjects
  OPTIONAL {?o eg:value ?boundary}
}
"]
    (loop [skip? #{}, acc #{s} q [s]]
      (if (empty? q)
        acc
        ;; else there's more in the queue...
        (let [sparql (prefixed
                      (render
                       template
                       {:subjects (apply
                                   str
                                   (interpose " "
                                              (map voc/as-qname
                                                   (filter (complement skip?) q))))}))
              bindings (query g sparql)
              ;; ... result will be {:o ... :boundary ...}
              non-boundaries (filter (complement :boundary) bindings)
              ] 
          (recur (reduce conj skip? q)                     ;; avoid cycles
                 (reduce conj acc (map :o non-boundaries)) ;; accumulator
                 (map :o non-boundaries)))))))             ;; next queue
I use a bit of tooling here whose function is hopefully fairly transparent.

Coding the example mapping numbers to the alphabet, (1 -> A, etc). I get these results:

> (find-island g :eg/Zero)
#{:eg/Zero}
> (find-island g :eg/D))
#{:eg/A :eg/B :eg/D}
Don't know if the performance would meet your needs, but I hope this helps. :-)

I believe this would remove the nodes where the value is true (so 3 in the picture), but it would not remove nodes "behind" that node, so lets say you start from node 1, it'll find all nodes except 3, 5, and 7. But I'd like to find just 1, 2, and 4, because the other nodes are behind a node which shouldn't be traversed.