Lesson 4: How to Search Your Notes Using Query Filters and Links

Congratulations! If you’ve come this far and understand most from the previous lessons, you’ve nearly mastered Logseq queries.

In this lesson, we’ll take the pseudocode from the previous lesson and turn them into proper Logseq queries. As you’ll see, the syntax for Logseq queries is close to what we’ve written before.

But before we start applying our knowledge of Boolean logic, we first need to tell Logseq we want to run a query.

How to start a Logseq query

To tell Logseq you want to search using a query, you type /query and hit the Enter key. This will give you the following macro:

{{query }}

If you’re familiar with programming, you can think of a macro as a function. Macros are also part of other data environments, like Microsoft Excel.

To keep things simple, remember this: a macro is a short computer script that enables you to record a series of actions.

Macros like queries enable us to automate our search operations. That’s why we write query code instead of clicking through a visual menu of filter options. By writing down the steps we want Logseq to take whenever we open a query, we can run it indefinitely without any further actions needed on our part.

Now that we know how to initiate a query, it’s time to use our knowledge of Boolean logic to tell Logseq what to search for.

Boolean operators = Logseq filters

The Boolean operators (AND, OR, NOT) from the previous lesson are called filters in Logseq. This is because we use them to filter the query results to show only what we’re looking for.

Apart from the basic boolean filters, there are more filters in Logseq: between, page, property, page-property, page-tags, task, priority, sort-by, among others. We’ll cover these filters in tomorrow’s lesson.

In this lesson, we’ll start easy by just using the three Boolean filters we already know.

The Boolean operators in Logseq

Let’s stay with yesterday’s Shakespeare theme.

If we wanted to find all branches that mention Shakespeare, Hamlet, Julius Caesar, and Macbeth, our pseudocode would look like this:

[[Shakespeare]] and [[Hamlet]] and [[Julius Caesar]] and [[Macbeth]]

(to see the full query, scroll to the right)

That’s a lot of ANDs! Luckily in Logseq, we just put the Boolean value at the start of a list of values (a sequence, get it?). Here’s what the pseudocode looks like in proper Logseq query syntax:

{{query (and [[Shakespeare]] [[Hamlet]] [[Julius Caesar]] [[Macbeth]]) }}

(to see the full query, scroll to the right)

As you can see, we wrap each list of search terms between parentheses. This tells Logseq what values belong together and in what order it should search for them.

This order of operations enables us to take the output of one query and use it as input for another query (the outer parentheses). This is what we ended with in yesterday’s lesson, and what we’ll end with in today’s lesson (see the next heading).

Like we did with the AND filter, we can use the OR and NOT filters similarly.

The query below looks for all the blocks that contain at least one of the links in the list:

{{query (or [[Shakespeare]] [[Hamlet]] [[Julius Caesar]] [[Macbeth]]) }}

(to see the full query, scroll to the right)

The query below looks for all the blocks that contain none of the links in the list:

{{query (not [[Shakespeare]] [[Hamlet]] [[Julius Caesar]] [[Macbeth]]) }}

(to see the full query, scroll to the right)

Now that we can work with the basic Boolean operators in Logseq, let’s combine them to fine-tune our search results.

Combining Logseq filters

By now, we’ve spent quite a bit of time with Boolean logic, so we’ll dive directly into the examples.

First, we want to see all blocks and branches that mention both Ghosts and Shakespeare:

{{query (and [[Ghosts]] [[Shakespeare]]) }}

But, we don’t want to see any blocks/branches that mention Hamlet:

{{query (and [[Ghosts]] [[Shakespeare]] (not [[Hamlet]])) }}

Here’s our first combined query! Can you see what steps Logseq takes?

First, this query grabs all blocks that do not contain the value [[Hamlet]]. Next, it filters out any blocks or branches that do not contain the values [[Ghosts]] and [[Shakespeare]].

Finally, we don’t want to see any blocks talking about Julius Caesar. The simplest way (without creating an additional order of operations) would be to write the query like this:

{{query (and [[Ghosts]] [[Shakespeare]] (not [[Hamlet]]) (not [[Julius Caesar]])) }}

(to see the full query, scroll to the right)

However, we can also create another level of nesting (order of operations). We can tell Logseq to first look for any blocks that contain the values [[Hamlet]]or[[Julius Caesar]], filter those out, and then look for the values we do want to see. This is the resulting query:

{{query (and [[Ghosts]] [[Shakespeare]] (not (or [[Hamlet]] [[Julius Caesar]]))) }}

(to see the full query, scroll to the right)

With nested queries like the one above, it’s essential to keep track of the number of opened parentheses. A common bug is that a parenthesis is missing at the end of the query. So, always look first at the number of parentheses when a query is not working as expected.

Another common bug you might run into is that the query results are incomplete (not showing all of the expected results). This happens when Logseq has not yet indexed a newly created or changed block. You can force indexation by clicking on your graph name in the left sidebar and the Re-index option.

For today, this is probably enough new information to make your head spin. Tomorrow we’ll dive deep into what other filters we can use in Logseq.

Let’s finish with some practice!

Exercise

For each specification below, write the corresponding query. The list of values is already broken up for you, so you only need to place them in the correct order of operations. You can find the answers below the exercise.

Remember to keep track of the parentheses! One trick is to count the filters you’ve used and ensure you have as many closing parentheses in your query.

After you’ve finished the exercise, spend some time writing queries in your graph. Post your questions in this thread if you run into unexpected results.

  1. Search for all blocks that mention either Ghosts or Julius Caesar.
  2. Search for all blocks that mention both Ghosts and Shakespeare.
  3. Search for all blocks that mention Shakespeare but not Romeo and Juliet.
  4. Search for all blocks that mention Ghosts and Shakespeare but not Hamlet.
  5. Search for all blocks that mention Ghosts but not Shakespeare, Hamlet, Julius Caesar, or Macbeth.
Show answers
  1. Search for all blocks that mention either Ghosts or Julius Caesar:
{{query (or [[Ghosts]] [[Julius Caesar]]) }}
  1. Search for all blocks that mention both Ghosts and Shakespeare:
{{query (and [[Ghosts]] [[Shakespeare]]) }}
  1. Search for all blocks that mention Shakespeare but not Romeo and Juliet:
{{query (and [[Shakespeare]] (not [[Romeo and Juliet]])) }}
  1. Search for all blocks that mention Ghosts and Shakespeare but not Hamlet:
{{query (and [[Ghosts]] [[Shakespeare]] (not [[Hamlet]])) }}
  1. Search for all blocks that mention Ghosts but not Shakespeare, Hamlet, Julius Caesar, or Macbeth:
{{query (and [[Ghosts]] (not (or [[Shakespeare]] [[Hamlet]] [[Julius Caesar]] [[Macbeth]]))) }}
3 Likes

Search for all blocks that mention either Ghosts or Julius Caesar

{{query (or [[Ghosts]] [[Julius Caesar]] ) }}

Search for all blocks that mention both Ghosts and Shakespeare.

{{query (and [[Ghosts]] [[Shakespeare]] ) }}

Search for all blocks that mention Shakespeare but not Romeo and Juliet.

{{query (and [[Shakespeare]] (not [[Romeo and Juliet]] )) }}

Search for all blocks that mention Ghosts and Shakespeare but not Hamlet.

{{query (and [[Ghosts]] [[Shakespeare]] (not [[Hamlet]] )) }}

Search for all blocks that mention Ghosts but not Shakespeare, Hamlet, Julius Caesar, or Macbeth.

{{query (and [[Ghosts]] (not (or [[Shakespeare]] [[Hamlet]] [[Julius Caesar]] [[Macbeth]] ))) }}

1 Like
  1. {{query (OR [[Ghost]] [[Julius Caisar]])}}
  2. {{query (AND [[Ghost]] [[Shakespeare]])}}
  3. {{query (AND [[Shakespeare]] (NOT [[Romeo and Juliet]]))}}
  4. {{query (AND [[Ghosts]] [[Shakespeare]] (NOT [[Hamlet]]))}}
  5. {{query (AND [[Ghosts]] (NOT (OR [[Shakespeare]] [[Hamlet]] [[Julius Caesar]] [[Macbeth]])))}}
1 Like
  1. Search for all blocks that mention either Ghosts or Julius Caesar.

{{query (or [[Ghosts]] [[Julius Caesar]]) }}

  1. Search for all blocks that mention both Ghosts and Shakespeare.

{{query (and [[Ghosts]] [[Julius Caesar]]) }}

  1. Search for all blocks that mention Shakespeare but not Romeo and Juliet.

{{query (and [[Shakespeare]] (not [[Romeo and Juliet]])) }}

  1. Search for all blocks that mention Ghosts and Shakespeare but not Hamlet.

{{query (and [[Ghosts]] [[Shakespeare]] (not [[Hamlet]])) }}

  1. Search for all blocks that mention Ghosts but not Shakespeare, Hamlet, Julius Caesar, or Macbeth.

{{query (and [[Ghosts]] (not (or [[Shakespeare]] [[Hamlet]] [[Macbeth]]))) }}

I came up with identical answers to what is posted as the correct answers.
But then I experimented with actual queries, and now I am very puzzled.

I set up a bunch of test blocks with tags, so that I could experiment with any possible combination (limited to a parent level, and not getting into child block inclusion yet). I used six different tags, because that’s how many are in today’s exercise; I have 63 blocks - one for each possible combination of tags.

I ran each of the queries in the exercise, and they all worked as I would expect, except for number 5. (After messing with it for an hour or so, I went so far as to copy and paste the answer shown here - several different times - just to make sure I wasn’t missing something. I changed nothing in it except the words inside the tags, and still got the same results as I had when I wrote the query out myself). I tried changing spacing, and figured out eventually that the only place Logseq is picky about whitespace is at the beginning of the query, where there must be no spaces between the brackets and “query.”

When I run this query:
{{query (and [[ttag1]] (not (or [[ttag2]] [[ttag3]] [[ttag4]] [[ttag5]]))) }}
I get back all blocks that contain tag 1 except for where tags 2, 3, 4 and 5 are all missing.

This is what I did to step through it:
{{query ( or [[ttag2]] [[ttag3]] [[ttag4]] [[ttag5]] ) }}
It works as expected. The only blocks not returned are 1, 6, and 16.

{{query [[ttag1]] }}
{{query ( and [[ttag1]] ) }}
Both work as expected; returns everything with a 1.

{{query ( and [[ttag1]] ( or [[ttag2]] [[ttag3]] [[ttag4]] [[ttag5]] ) ) }}
It works as expected; returns everything with a 1 except 1 and 16.

{{query ( and [[ttag1]] ( not ( or [[ttag2]] [[ttag3]] [[ttag4]] [[ttag5]] ) ) ) }}
As before, it does not work as expected; it is returning everything that contains a 1, except for when all of the “not” tags are present. It’s doing what I would expect from:
{{query ( and [[ttag1]] ( not ( and [[ttag2]] [[ttag3]] [[ttag4]] [[ttag5]] ) ) ) }}
which is working as expected, but that’s puzzling because “or” and “and” in this context should most definitely not be interchangeable.

Even though I didn’t think it would make a difference, I then ran this query:
{{query (and (not (or [[ttag2]] [[ttag3]] [[ttag4]] [[tag5]] )) [[ttag1]] ) }}
which I would have expected to give me the same results as the first one. That’s not what it did. It returned all blocks with a 1.

This is what worked:
{{query ( and [[ttag1]] ( not [[ttag2]] ) ( not [[ttag3]] ) ( not [[ttag4]] ) ( not [[ttag5]] ) ) }}

I understand why it works, but I don’t understand why the shorter version didn’t.
I am so confused. What am I missing?

3 Likes

Thanks for your in-depth reply @LShark. There are some bugs in the query engine that @tienson is looking into. Your issues seem to be related to what he is already aware of, but it helps that you’re reporting them.

2 Likes
  1. Search for all blocks that mention either Ghosts or Julius Caesar.
    {{query (or [[Ghosts]] [[Julius Caesar]]) }}

  2. Search for all blocks that mention both Ghosts and Shakespeare.
    {{query (and [[Ghosts]] [[Julius Caesar]]) }}

  3. Search for all blocks that mention Shakespeare but not Romeo and Juliet.
    {{query (and [[Shakespeare]]) (not [[Romeo and Juliet]])) }}

  4. Search for all blocks that mention Ghosts and Shakespeare but not Hamlet.
    {{query ((and [[Ghosts]] [[Shakespeare]]) (not [[Hamlet]])) }}

  5. Search for all blocks that mention Ghosts but not Shakespeare, Hamlet, Julius Caesar, or Macbeth.
    {{query (and [[Ghosts]] (not (or [[Shakespeare]] [[Hamlet]] [[Julius Caesar]] [[Macbeth]]))) }}

  • 1.Search for all blocks that mention either Ghosts or Julius Caesar.
    • {{query (or [[Ghosts]] [[Julius Caesar]] ) }}
  • 2.Search for all blocks that mention both Ghosts and Shakespeare.
    • {{query (and [[Ghosts]] [[Shakespeare]] ) }}
  • 3.Search for all blocks that mention Shakespeare but not Romeo and Juliet.
    • {{query (and [[Shakespeare]] (not [[Romeo and Juliet]]) )}}
  • 4.Search for all blocks that mention Ghosts and Shakespeare but not Hamlet.
    • {{query (and [[Ghosts]] [[Shakespeare]] (not [[Hamlet]]) ) }}
  • 5.Search for all blocks that mention Ghosts but not Shakespeare, Hamlet, Julius Caesar, or Macbeth.
    • {{query (and [[Ghosts]] (not(or [[Shakespeare]] [[Hamlet]] [[Julius Caesar]] [[Macbeth]]))) }}
  1. Search for all blocks that mention either Ghosts or Julius Caesar.
    {{query (OR [[Ghosts]] [[Julius Caesar]]) }}
  2. Search for all blocks that mention both Ghosts and Shakespeare.
    {{query (AND [[Ghosts]] [[Julius Caesar]]) }}
  3. Search for all blocks that mention Shakespeare but not Romeo and Juliet.
    {{query (AND [[Shakespeare]] (NOT [[Romeo and Juliet]])) }}
  4. Search for all blocks that mention Ghosts and Shakespeare but not Hamlet.
    {{query (AND [[Ghosts]] [[Shakespeare]] (NOT [[Hamlet]])) }}
  5. Search for all blocks that mention Ghosts but not Shakespeare, Hamlet, Julius Caesar, or Macbeth.
    {{query (AND [[Ghosts]] (NOT (or [[Shakespeare]] [[Hamlet]] [[Julius Caesar]] [[Macbeth]]))) }}

After doing the lesson and getting the right answers, I was very excited because I thought I was finally understanding boolean logic. After reading your reply, I went to my sprint graph, input some test data, and tried to duplicate what I (thought) I had learned in the lesson, and discovered that I don’t understand them any more now, than before I studied this lesson.

I’m going to take the weekend to dig deeper into where my logic is failing me.

I thank you for sharing this, because it showed me where I need more work!

1 Like

What queries did you write and what did you see (if you can, share a (redacted) screenshot)? Maybe you ran into a bug. In that case, your understanding should be fine but it doesn’t give you the results because of things going awry under the hood in Logseq.

1 Like

@Ramses,

I had deleted my test data I was using last night, so just now created some new test data to try to reproduce what I was running into. To my great delight, the new test data is working exactly as expected!!! I must have just been overly tired when I tried this last night.

I’ll keep practicing over the weekend recreating examples from yesterday’s lessons, and then trying to apply them to my own graph data and templates. I will also keep in mind that there are bug[s].

This makes me so VERY happy!!! I’ve always had a problem with logical thinking, Boolean or otherwise, despite my love for logic problem type puzzles; I just don’t think like a computer, no matter how hard I’ve tried to learn in the past. This is a huge break-through for me, and I’m very encouraged!

Thank you!!!

2 Likes
  • Search for all blocks that mention either Ghosts or Julius Caesar.
    • {{query (or [[Ghosts]] [[Julius Caesar]])}}
  • Search for all blocks that mention both Ghosts and Shakespeare.
    • {{query (and [[Ghosts]] [[Julius Caesar]])}}
  • Search for all blocks that mention Shakespeare but not Romeo and Juliet.
    • {{query [[Shakespeare]] (not [[Romeo and Juliet]])}}
  • Search for all blocks that mention Ghosts and Shakespeare but not Hamlet.
    • {{query (and [[Shakespeare]] [[Ghosts]] (not [[Hamlet]]))}}
  • Search for all blocks that mention Ghosts but not Shakespeare, Hamlet, Julius Caesar, or Macbeth.
    • {{query [[Ghosts]] (not (or [[Shakespeare]] [[Hamlet]] [[Julius Caesar]] [[Macbeth]]))}}

The last one got me, clever! Had to test it on my own kb to figure out why it wasn’t working: needs (or) inside the (not)

1 Like

Search for all blocks that mention either Ghosts or Julius Caesar.
{{query (OR [[Ghosts]] [[Julius Caesar]])}}
Search for all blocks that mention both Ghosts and Shakespeare.
{{query (AND [[Ghosts]] [[Shakespeare]])}}
Search for all blocks that mention Shakespeare but not Romeo and Juliet.
{{query ([[Shakespeare]] (NOT [[OR [[Romeo]] [[Juliet]]))}}
Search for all blocks that mention Ghosts and Shakespeare but not Hamlet.
{{query (AND [[Ghosts]] [[Shakespeare]](NOT [[Hamlet]]))}}
Search for all blocks that mention Ghosts but not Shakespeare, Hamlet, Julius Caesar, or Macbeth.
{{query ([[Ghosts]] (NOT (OR [[Shakespeare]] [[Hamlet]] [[Julius Caesar]] [[Macbeth]])))}}

1 Like

I’ve quite familiar with using boolean logic. Is there an add-on for Logseq to have user-friendly query, using dropdown lists of links or tags to create a query in a graphical way, instead of typing in the code?

Unfortunately not yet. It’s still my dream to either build or sponsor something like @dvargas92495’s Query Builder plugin for Roam.

2 Likes

Thanks Ramses, that would be really great, and open up Logseq for many more people to use it more effectively. Hopefully. But in the meantime old fashioned boolean expressions will do the same job.

Patience young grasshopper :cricket:

2 Likes

This one was a real eyeopener for me! Running behind the pack but getting a lot from the sprint right now. Perfect pacing, @Ramses !

2 Likes