Annotated template for advanced queries

It took me four days to piece together all the information scattered around so I am posting an annotated version of my code for reference.

  • This example searches for blocks with a given property and then sorts the results according
    to some other properties.
  • The data is sorted according to two different properties, one in a reversed order.
  • The custom :view template is a simple ordered list to show that you do not always have to make tables.
  • The code is as concise and readable as it can get.
#+BEGIN_QUERY
{
 ; The `:title` key defines the heading for the output.
 ; In this case, `[:h2 ...]` creates a second-level heading, and `[:i ...]` italicizes "English."
 ; If you don't specify a title, you'll get an empty line instead.
 ; Note how adjacent strings are concatenated.
 :title [:h2 "My " [:i "English"] " Books"
         ]

 ; The `:query` key contains the main logic to retrieve data.
 ; `:find` specifies what to retrieve; you can use `(pull ?b [*])` to pull _all_ the properties of `?b`.
 ; `?b` is an arbitrary name; you could just as easily name it `?whatever`.
 :query [:find (pull ?b [*])
 ; `:where` defines the condition(s) for selecting entities.
         :where
 ; In this case, we use a Logseq-specific shorthand to get all entities with the `:language` block property set to "English."
         (property ?b :language "English")
         ]

 ; `:result-transform` allows you to modify the query results before displaying them.
 :result-transform (fn [results]
 ; Here, we sort the results twice:
 ; First, by the `:year` property in reverse order (`>` reverses the order).
 ; Then, by the `:title` property.
 ; Note that sorting rules must be written inside out, i.e., in reverse order.
 ; `->>` is called a "thread-last macro." It converts nested function calls into a linear flow of function calls, improving readability.
                     (->> results
                       (sort-by (fn [result]
                                  (get-in result [:block/properties :title])) )  ; `get-in` is used to get the values out of nested maps.
                       (sort-by (fn [result]
                                  (get-in result [:block/properties :year])) >)
                       ))

 ; `:group-by-page? false` makes it easier to handle the results in a for loop.
 :group-by-page? false

 ; The `:view` key defines how the results will be displayed.
 :view (fn [results]
 ; In this case, we create an ordered list, but you can just as easily create tables or anything you want.
 ; First we write the code we only need once.
         [:ol
 ; Now we can initiate a for loop for our results.
          (for [result results]
 ; We can simplify our lives by saving everything we need into variables first.
          (let [properties (:block/properties result)
                title (:title properties)
                year (:year properties)]
; Now we can write the rest of the template in a more readable manner.
            [:li [:i title] " was released in " year "."]
            ))
; Now we are out of the loop and can add some more code here if needed.          
          ]
         )
 }
#+END_QUERY

Here is a picture to show what information the example code takes in and how it renders the results.