Advanced Query: Blocks excluding certain tag

Hi there :wave:

I’d like to do a query of blocks that don’t contain a certain tag, for example #mytag.

This is the query including only blocks that contain #mytag, which works great. It is also sorted by priority and doesn’t include blocks with priority C:

#+BEGIN_QUERY
  {
    :title [:h2 "TODO with #mytag"]
    :query [:find (pull ?b [*])
    :where
      [?b :block/marker ?m]
      [?b :block/ref-pages ?p]
      [?b :block/priority ?pr]
      [?p :block/name ?n]
      [(= "TODO" ?m)]
      [(contains? #{"mytag"} ?n)]
      (or
        [(= ?pr "A")]
        [(= ?pr "B")])
    ]
  :result-transform (fn [result]
  (sort-by (fn [h]
  (get h :block/priority "Z")) result))
  :collapsed? false
  }
  #+END_QUERY

Now, I want to do the opposite query: return all the blocks that don’t contain #mytag.

I’ve tried modifying the contains part with:

(not [(contains? #{"mytag"} ?n)])
[(not contains? #{"mytag"} ?n)]
[(not= ?n "mytag")]

but none of those work.

I’ve been checking out the Datomic Query docs but I couldn’t find the answer there either.

Any help is really welcomed :slightly_smiling_face:

I guess it has to be a way to do this because this simple query works fine:

{{query (and (task TODO) (or (priority B) (priority A)) (not [[read]])))}}

I wonder if there is an easy way to see that query in datalog syntax.

Ok, it is. Just go to the web (logseq.com), open the console and navigate to the page with the query. It will print out the advanced query in the console.

It turns out the correct answer is to use this:

(not [?b :block/path-refs [:block/name "mytag"]])

This is the full query, working great now:

#+BEGIN_QUERY
{
  :title [:h2 "To Do (A and B)"]
  :query [:find (pull ?b [*])
  :where
    [?b :block/uuid] 
    [?b :block/marker ?marker] 
    [?b :block/priority ?priority] 
    [(contains? #{"TODO"} ?marker)] 
    (or 
      [(contains? #{"B"} ?priority)] 
      [(contains? #{"A"} ?priority)]) 
    (not [?b :block/path-refs [:block/name "mytag"]])
  ]
:result-transform (fn [result]
(sort-by (fn [h]
(get h :block/priority "Z")) result))
:collapsed? false
}
#+END_QUERY

EDIT: It turns out it is also important to use contains? for the TODO and priority, instead of =. If not, the query does not return anything.

4 Likes

EDIT: Nevermind, I solved it. Thank you for sharing this.

For future searcher, the fix is at the bottom. You have to wrap the query.

ORIGINAL POST:

This looks very interesting. Is there an extra step to the conversion, as for me, it doesn’t work. Below is what I put. I am not editing the query at all at this stage.

My simple query is below, which displays x number of todos.

{{query (and (todo todo later) (not [[test]]) ) }}

The console log has this loaded which appears to match.

"Datascript query: " [:find (pull ?b [:db/id :block/uuid :block/type :block/left :block/format :block/refs :block/_refs :block/path-refs :block/tags :block/content :block/marker :block/priority :block/properties :block/pre-block? :block/scheduled :block/deadline :block/repeated? :block/created-at :block/updated-at :block/file :block/parent :block/heading-level {:block/page [:db/id :block/name :block/original-name :block/journal-day]} {:block/_parent ...}]) :where [?b :block/uuid] [?b :block/marker ?marker] [(contains? #{"TODO" "LATER"} ?marker)] (not [?b :block/path-refs [:block/name "test"]])]

However when I put this in as an advanced query it doesn’t load anything.

#+BEGIN_QUERY
[:find (pull ?b [:db/id :block/uuid :block/type :block/left :block/format :block/refs :block/_refs :block/path-refs :block/tags :block/content :block/marker :block/priority :block/properties :block/pre-block? :block/scheduled :block/deadline :block/repeated? :block/created-at :block/updated-at :block/file :block/parent :block/heading-level {:block/page [:db/id :block/name :block/original-name :block/journal-day]} {:block/_parent ...}]) :where [?b :block/uuid] [?b :block/marker ?marker] [(contains? #{"TODO" "LATER"} ?marker)] (not [?b :block/path-refs [:block/name "test"]])]
#+END_QUERY

I have tried to simplify it, to get a working starting point, with no luck.

#+BEGIN_QUERY
[
    :find (pull ?b [*]) 
    :where [?b :block/uuid] [?b :block/marker ?marker] [(contains? #{"TODO" "LATER"} ?marker)]
]
#+END_QUERY

RESULTING CORRECTION:

Looking at the docs, the queries are wrapped with a {:query }

#+BEGIN_QUERY
{
    :title [:h2 "Custom title"]
    :query [ :find (pull ?b [*]) 
      :where [?b :block/uuid] [?b :block/marker ?marker] [(contains? #{"TODO" "LATER"} ?marker)]]
}
#+END_QUERY

How to modify this query to exclude tag that starts with “regex” while matching another tag.

Example I need to find blocks with t.rejected while excluding blocks which refs starting with “r.”.

Example
B1 #t.rejected should match
B2 #t.rejected r.xyz should not Match

#+BEGIN_QUERY
{:title "All blocks with Rejected Tag"
:query [:find (pull ?b [*])
     :where
     [?b :block/refs ?p]
     [?p :block/name "t.rejected"] 
    (not 
       [?b :block/refs [:block/name "r.zn.b"]]
    )
]}
#+END_QUERY

Above query is able to exclude blocks with exact match “r.zn.b” but not sure how to change it to startswith using [(clojure.string/starts-with? ?name "r.")]

What would be the way to exclude multiple tags - so show all blocks but those that have “mytag” OR “anothertag” OR “thirdtag”?

This doesn’t work

  (or
  (not [?b :block/path-refs [:block/name "mytag"]])
    (not [?b :block/path-refs [:block/name "anotherday"]])
    (not [?b :block/path-refs [:block/name "thirdtag"]])

)

Coding syntax doesn’t work exactly as the human language. For what you describe, should either:

  • replace the first or with and
  • remove the three nots and use a single not to wrap the or

Hello,

I’ve got a similar advanced query where I want to exclude blocks which contain #tagOne OR #tagTwo OR #tagThree

Unfortunately I couldn’t make it work with what you proposed, I may have misunderstand though…
I tried

(and
  (not [?b :block/path-refs [:block/name "tagOne"]])
  (not [?b :block/path-refs [:block/name "tagTwo"]])
  (not [?b :block/path-refs [:block/name "tagThree"]])
)

and

(not (or
  [?b :block/path-refs [:block/name "tagOne"]]
  [?b :block/path-refs [:block/name "tagTwo"]]
  [?b :block/path-refs [:block/name "tagThree"]]
))

But none of these worked. Could you explain how to do it ?
thanks

Welcome. None of these codes work by themselves, they have to be part of a bigger code that properly incorporates them. If you share your query and some test blocks, we can make it work.

Here is my query
It is supposed to get blocks which contain tag “mytag” and which not contain tag “tag1” OR “tag2” OR “tag3” and which is not a /DONE task

#+BEGIN_QUERY
{
        :title "mytag"
        :query [
                :find (pull ?b [*])
                :where
                [?p :block/name "mytag"]
                [?b :block/refs ?p]
                (not [?b :block/marker "DONE"])
                ;; (not [?b :block/path-refs [:block/name "tag1"]]) 
                ;; (not [?b :block/path-refs [:block/name "tag2"]]) 
                ;; (not [?b :block/path-refs [:block/name "tag3"]])
                ]
        :result-transform :sort-by-priority
        :group-by-page? false
        :breadcrumb-show? false
        :collapsed? false
}
#+END_QUERY
  • Using either of:
    • #+BEGIN_QUERY
      {
          :title "mytag"
          :query [
              :find (pull ?b [*])
              :where
                  [?p :block/name "mytag"]
                  [?b :block/refs ?p]
                  (not [?b :block/marker "DONE"])
                  (not [?b :block/path-refs [:block/name "tag1"]])
                  (not [?b :block/path-refs [:block/name "tag2"]])
                  (not [?b :block/path-refs [:block/name "tag3"]])
          ]
          :result-transform :sort-by-priority
          :group-by-page? false
          :breadcrumb-show? false
          :collapsed? false
      }
      #+END_QUERY
      
      • Mind that :where acts as an implicit top-level and
    • #+BEGIN_QUERY
      {
          :title "mytag"
          :query [
              :find (pull ?b [*])
              :where
                  [?p :block/name "mytag"]
                  [?b :block/refs ?p]
                  (not (or
                      [?b :block/marker "DONE"]
                      [?b :block/path-refs [:block/name "tag1"]]
                      [?b :block/path-refs [:block/name "tag2"]]
                      [?b :block/path-refs [:block/name "tag3"]]
                  ))
          ]
          :result-transform :sort-by-priority
          :group-by-page? false
          :breadcrumb-show? false
          :collapsed? false
      }
      #+END_QUERY
      
  • I get this:
  • Mind that :block/name "tagOne" is always false.
    • For it to work, should use either:
      • :block/name "tagone"
      • :block/original-name "tagOne"
1 Like

Thanks a lot for your help!

I used your code and at first it was not working, the “mytag” query was not even displayed.

Then I noticed that I need an existing page for each “tag1”, “tag2” and “tag3” otherwise it won’t work.

Capture d’écran 2024-03-19 à 22.06.15

Here pages “tag1” and “tag2” are created because they are tagged but not “tag3”.

As soon as I add the #tag3, page “tag3” is created and query “mytask” is displayed.

Capture d’écran 2024-03-19 à 22.11.51

But if for any reason I delete the only existing #tag3, the “tag3” page is automatically deleted and the same issue is back.

Do you no a way to keep a page even if it has no references?

One obvious way is to enter some characters in the page so it is not empty and doesn’t get deleted with no references. I answered my question…

The actual issue is the condensed syntax: (not [?b :block/path-refs [:block/name "tag3"]])
The issue vanishes when replaced with an expanded version:

(not
    [?b :block/path-refs ?ref]
    [?ref :block/name "tag3"]
)
1 Like

Great, works like a charm!
Thanks again for your help :slight_smile: