Here’s a way to reuse the same advanced query as a global query (getting results from the whole graph) or as a page-specific query, by just changing one variable in the inputs.
Why?
Here’s my own purpose for doing this. You might find it useful, or repurpose it for some other use:
I was inspired by @alex0 's contextual sidebar topic, and wanted to have a set of queries for tasks on project pages that mirror the queries I have for my whole graph.
In my case, I have:
- a page full of graph-level task management queries
- a page full of page-level task management queries (for the sidebar, or to embed in whatever page)
A lot of these queries are identical, but their scope needs to be changed. I wanted to be able to keep one single list of the queries, and if I make an adjustment to it, the adjustment can easily apply to both versions of the query (graph-level and page-level).
I can have a page full of such queries, make changes to any of them, and when/if I want them to apply at the page level instead of the global level, just change one argument false
to true
.
Example
Starting with this query from @Siferiax in this post:
Example query: Before
#+BEGIN_QUERY
{:title ["📆 near TODOs (next 7 days, scheduled or deadline)"]
:query [:find (pull ?b [*])
:in $ ?start ?next
:where
(or
[?b :block/scheduled ?d]
[?b :block/deadline ?d]
)
[(>= ?d ?start)]
[(<= ?d ?next)]
]
:result-transform (fn [result] (sort-by (juxt (fn [d] (get d :block/deadline) ) (fn [d] (get d :block/scheduled) ) ) result))
:inputs [:today :7d-after]
:collapsed? false}
#+END_QUERY
Example query: After
#+BEGIN_QUERY
{:title ["📆 near TODOs (next 7 days, scheduled or deadline)"]
:query [:find (pull ?b [*])
:in $ ?start ?next ?inputpage ?q
:where
[?b :block/page ?p]
(or
(not [(= ?q true)])
(and [(= ?q true)]
[?p :block/name ?inputpage]))
(or
[?b :block/scheduled ?d]
[?b :block/deadline ?d]
)
[(>= ?d ?start)]
[(<= ?d ?next)]
]
:result-transform (fn [result] (sort-by (juxt (fn [d] (get d :block/deadline)) (fn [d] (get d :block/scheduled))) result))
:inputs [:today :7d-after :current-page false] ; last argument (true or false): to query all pages, use false. to query current page only, use true.
:collapsed? false}
#+END_QUERY
As it stands, the adapted query produces identical results to the first query. Now if we want it scoped to whatever page is in the main view of Logseq, we just change false
to true
in the :inputs
line:
:inputs [:today :7d-after :current-page true]
Step By Step
If you’re not adept at reading Datalog, here’s my best attempt at explaining how making these changes works, so that you can apply them to your own queries. (You may have to make adjustments depending on the details of whatever query you’re adapting):
-
First we add new inputs
:current-page false
at the end of whatever inputs are already there. So in this query, the new line will be:inputs [:today :7d-after :current-page false]
If your query doesn’t have an
:input
line, add one following the:where
line with only:inputs [:current-page false]
-
Next we need to accept the inputs, so to the
:in
line we append?inputpage ?q
::in $ ?start ?next ?inputpage ?q
If your query has rules and therefore a
%
at the end of the:in
line, keep the%
at the end.And if your query doesn’t have an
:in
line, add one with only:in $ ?inputpage ?q
-
Now we add lines to select only blocks that belong to the
:query-page
, if the last input argument istrue
, or any page, iffalse
:[?b :block/page ?p] (or (not [(= ?q true)]) (and [(= ?q true)] [?p :block/name ?inputpage]))
If the argument is not
true
, the value of?p
is basically all pages. If it istrue
,?p
is equal to the:current-page
. Thanks to @mentaloid for this improved logic over that of the original post. (This comment refers text from the original post which we’re no longer using, but it’s useful info nonetheless about using_
in queries)Some important notes:
- The example query was already set up to return blocks, and those blocks were assigned the variable
?b
: (:query [:find (pull ?b [*])
. So the line in the code above will work, because?b
already referred to the blocks we’re looking for. - The example query didn’t already have the line
[?b :block/page ?p]
that finds the pages of blocks. In fact, it didn’t even filter for pages. So we’re safe to add that line. If the query did filter for:block/page
somewhere (e.g., if it had a similar line like[?b :block/page ?page]
we’d need to check carefully how variables are being used before - If your query uses a different variable than
?b
for blocks and?p
for pages, adjust accordingly. - You’ll have to dig into the logic and figure things out if:
- your query is more complex (for example, if it uses more than one variable for pages and/or for blocks)
- it returns pages or something else instead of blocks
- Datalog just doesn’t seem friendly to you today
- The example query was already set up to return blocks, and those blocks were assigned the variable
-
Change
false
totrue
, and now the query is scoped to the current page in main view. -
You can use the page-scoped version in several ways:
- directly in a page
- as a block embed in a page
- in a page in the sidebar, while viewing the page you want results for