logseq advanced queries tutorial

Playing around with queries can be detrimental to the health of your graph as you may create blocks and pages just for testing purposes. Use a dedicated playground graph for this!
Second point, if you copy/paste queries, go over the code again, Logseq sometimes adds extra line-breaks / characters in strange places. Personally, I write my (longer) queries in vscode, and then, remove all tab-characters, and then copy the query to Logseq

In the following 9 examples we will walk through variou queries for Logseq. The tutorials are intended for beginners getting started with advanced queries in Logseq.

Example 1, find a tag

OK, let’s start. The simplest query is probably: {{query [[MyTag]]}}

To create this, shortcut /q, and then add [[MyTag]]. This will look for the tag #MyTag (everywhere).

The same query, but now written as an advanced query, would be:

1
2
3
4
5
6
7
8
9
#+BEGIN_QUERY
{:title "Find tag: MyTag"
:query [:find (pull ?b [*])
:where
[?b :block/ref-pages ?p]
[?p :block/name "MyTag"]
]
}
#+END_QUERY

Let's go over this line by line

  • Lines 2 and 8, these indicate the start and end of a code block.

    Add an advanced query with <q, and select query (fun fact: this notation comes from Emacs org-mode)

  • Lines 2 and 7, a query starts and end with curly brackets

  • Line 2 `:title “” <- a simple title field

  • Line 3 This is where the actual query starts, note the square bracket [, opening here, and closing on line 7

    So we want to find stuff, and the blocks we want to find we store in ?b. This ?b is just a variable, you could replace ?b with ?block if it would make your code more readable. Just use the same variable everywhere ๐Ÿ˜„

  • Line 4 :where starts the search parameters

  • Line 5 block/ref-pages is the reference where tags are stored

  • Line 6 block/name is not the name of the page, but off the reference from line 5. We store the name of the tags in ?p (again this could be anything, you can rename it to ?tagIamLookingFor)

Writing this shorter might be a bit simpler to understand:

[?b :block/ref-pages [:block/name "MyTag"]]

This is exactly the same thing, as lines 5 and 6

That’s all, from now on we’ll iterate on this, adding complexity

Example 2, find a tag that is also a TODO

1
2
3
4
5
6
7
8
9
#+BEGIN_QUERY
{:title "Find: MyTag"
:query [:find (pull ?b [*])
:where
[?b :block/marker "TODO"]
[?p :block/name "MyTag"]
[?b :block/ref-pages ?p]]
}
#+END_QUERY
  • New is line 5, :block-markeris where todo keywords are stored (for the curious: logseq/db_schema, line 57 or so)

  • Our search has to satisfy both line 5, a marker containing TODO, and 6 and 7, which belong together

Example 3, multiple markers / TODO states

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#+BEGIN_QUERY
{:title "Find: TODO or DOING MyTag"
:query [:find (pull ?b [*])
:where
[?b :block/marker ?marker]
[(contains? #{"TODO" "DOING"} ?marker)]

[?p :block/name "MyTag"]
[?b :block/ref-pages ?p]]
}
#+END_QUERY

Notice lines 5 and 6, that replace the single line 5 fom the previous example. We’re looking for a marker, called ?marker, and ?marker should contain either TODO or DOING

This same method you can use to look for multiple tags:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#+BEGIN_QUERY
{:title "Find: MyTag and fghij"
:query [:find (pull ?b [*])
:where

[?b :block/marker ?marker]
[(contains? #{"TODO" "DOING"} ?marker)]

[?b :block/ref-pages ?p]
[?p :block/name ?tag]
[(contains? #{"MyTag" "fghij"} ?tag)]]
}
#+END_QUERY
Notice the closing ] on line 11, it’s easy to forget these, especially as you slowly add more complexity to your searches

Example 4, search for parts of tag

Some people have complicated tag configurations, like: Topic/boats, Topic/airplanes, Topic/automobiles, to look for all those Topics at the same time, we can use (we, though, stick with our alphabet examples):

1
2
3
4
5
6
7
8
#+BEGIN_QUERY
{:query [:find (pull ?b [*])
:where
[?b :block/ref-pages ?p]
[?p :block/name ?tag]
[(clojure.string/starts-with? ?tag "ab")]]
}
#+END_QUERY

The key is off course on line 6, where we use clojure.string/starts-with?, the variable we work with is ?tag, and we filter on Topic/

For further study, these are the clojure builtins we can use in our queries: datascript/query.cljc at fork ยท logseq/datascript

Notice also that this query does not have a title, it doesn’t have to

Example 5, searching for page properties

Properties are key-value pairs that allow you to annotate a block or page:

1
2
3
4
5
6
7
8
#+BEGIN_QUERY
{:title "My examples"
:query [:find (pull ?b [*])
:where
[?b :block/properties ?p]
[(get ?p :type) ?t]
[(= "example" ?t)]]}
#+END_QUERY

There can be multiple block/properties to one block, so we have to get the one we want, in this case type. This can be any word added as a property to a page

Example 6, project tasks

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#+BEGIN_QUERY
{:title "My examples TODOs"
:query [:find (pull ?b [*])
:where
[?b :block/marker ?marker]
[(contains? #{"NOW" "DOING"} ?marker)]

[?b :block/page ?p]
[?p :block/properties ?a]
[(get ?a :type) ?t]
[(= "example" ?t)]
]
:result-transform (fn [result]
(sort-by (fn [h]
(get h :block/priority "Z")) result))
:collapsed? false}
#+END_QUERY

This query combines the TODO states from example 3, and page properties from example 5

New are lines 13-15, this snippet will sort TODOs on priority

Example 7, search Journal pages for tagged TODOs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#+BEGIN_QUERY
{:title "Tagged journal pages"
:query [:find (pull ?b [*])
:where
[?b :block/marker ?marker]
[(contains? #{"TODO" "DOING"} ?marker)]

[?b :block/page ?p]
[?p :block/journal? true]

[?b :block/ref-pages ?r]
[?r :block/name "MyTag"]
]
:result-transform (fn [result]
(sort-by (fn [h]
(get h :block/priority "Z")) result))
}
#+END_QUERY

New are the lines 8 and 9, where we ask if the page is a journal

Example 8, exclude from a query

1
2
3
4
5
6
7
8
9
#+BEGIN_QUERY
{:title "๐Ÿ“ผ Watch list - videos"
:query [:find (pull ?b [*])
:where
[?b :block/refs [:block/name "video"]]
(not [?b :block/marker ?marker] [(contains? #{"DONE"} ?marker)])
]
}
#+END_QUERY
  • Line 5 searches for the #video tag (it’s two lines in one, see QQ)

  • Line 6 and 7 look for the DONE marker, but now excludes it, by wrapping it in (not <query>). It has to be all on one line!

Example 9, comparing dates

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#+BEGIN_QUERY
{:title "โš ๏ธ OVERDUE"
:query [:find (pull ?block [*])
:in $ ?start ?today
:where
[?block :block/marker ?marker]

(or
[?block :block/scheduled ?d]
[?block :block/deadline ?d])

[(>= ?d ?start)]
[(<   ?d ?today)]

[(contains? #{"NOW" "LATER" "TODO" "DOING" "WAITING"} ?marker)]]
:inputs [:180d :today]
:result-transform  (fn [result]
(sort-by  (fn [d]
(get d :block/deadline) ) result ))
:collapsed? false}
#+END_QUERY

This is a very useful one. Let’s see what it does:

  • Line 6 gets ?marker, it can (line 15) contain NOW, LATER, TODO, etc

  • Line 8 or, means either of these shuold be true, it should either be a deadline or scheduled

  • Or is related to not from example 8, there is still a cousin called and, but that one is not used often. And is usually implied, all rules should match by default.

  • Lines 12 and 13 are new, you have to read them in the following order:

  • Line 16 offers two values, :180d and :today

  • Next is line 4, where those values are assigned to ?start and ?today (again, variables, starting with ? can be called anything, it could have been ?john and ?ringo)

  • Finally they are used on lines 12 and 13 where we compare the values of block/scheduled (line 9) to ?start and block/deadline (line 10) to ?today

  • And as a bonus, lines 17 to 19, we sort on deadlines

Further queries