This was driving me crazy, and I’m so pleased with the result.
The problem: every time I switched a top-level task status to DOING, all of its subtask todos would suddenly appear in my todo queries, so that dozens of really granular tasks like “sand off rust” or “research microphone cables” would be cluttering up a list of high level tasks like “buy a house” or “career change.”
Here’s the query that solves the problem. The trick was defining a recursive rule in the inputs that checks if a block is an ancestor, then using that rule to find all blocks with no ancestors with todo markers:
#+BEGIN_QUERY
{:query [:find (pull ?b [*])
:in $ %
:where
[?b :block/marker ?marker]
[(contains? #{"TODO"} ?marker)]
;; ancestor is true when ?ancestor is an ancestor of b
(not (ancestor ?b ?ancestor)
[?ancestor :block/marker ?amarker]
[(contains? #{"TODO" "NOW" "DOING"} ?amarker)])]
:inputs
[[[[ancestor ?b ?ancestor]
[?b :block/parent ?ancestor]]
[[ancestor ?b ?ancestor]
[?child :block/parent ?ancestor]
(ancestor ?b ?child)]]]
:result-transform (fn [result]
(sort-by (fn [b]
(get b :block/priority "Z")) result))
:breadcrumb-show? false
}
#+END_QUERY
And here’s a screenshot to demonstrate the result: