Page Properties in Advanced Queries

Hello community,

I’m trying to figure out how to improve my advanced queries by considering page properties. I have a large number of pages and some page names are only used as page/block property names. Is there a way I can extract these names?

For example, I have the following properties for a page named “Paperwork”

tags:: Project
project-type:: [[Fun]]
status:: [[Completed]]
completion-date:: [[2024/04/10]]

Let’s say the page tags is only used as property names but the page completion-date might be used as a regular tag by other blocks. How do I extract tags but not completion-date? Generally, I would like to match page names that are only used as block/page property names.

Please understand we cannot search for property pages directly.
That is to say, there is no list of properties to access directly.

So there’s two options:

  1. Get all pages that are not referenced… which will potentially get more pages than just properties.
  2. Do something complex, but I just crashed my test graph :joy:

The first option would simply be:

  #+BEGIN_QUERY
  {:title "Pages that are not referenced"
   :query [:find (pull ?p [*])
    :where
     [?p :block/name _]
     (not [?b :block/refs ?p])
   ]
  }
  #+END_QUERY

Something that you may find useful, is a simple list of all the property names:

#+BEGIN_QUERY
{:title "Property names"
 :query [:find ?props
   :where
     [?p :block/name _]
     [?p :block/properties ?props]
 ]
 :result-transform (fn [props] (sort (map (fn [s] (subs (str s) 1)) (distinct (flatten (map (fn [i] (keys i)) props))))))
}
#+END_QUERY
  • To also get properties from blocks, remove line [?p :block/name _]

I fixed Logseq crashing and made something that possibly works?!
It may take a bit to execute as it gathers a large dataset and does the logic in the result-transform.
So not sure how useful it is going to be in the long run.
It also gives back any system properties.

#+BEGIN_QUERY
{:title [:b "Ongedefinieerde Properties"]
 :query [:find ?name ?prop ; return both the pages and the properties
  :keys page prop ; bind the pages and properties to a key for use in result-transform
  :where
   [?b :block/properties ?prop] ; get the properties of all blocks
   (not [(empty? ?prop)])
   [?p :block/original-name ?nm] ; get the original name of all pages
   (not [?p :block/journal? true]) ; exclude journal pages, can remove if you need to check those as well
   [(str ":" ?nm) ?name] ; change the page name to the convention of a property key
   [_ :block/refs ?p] ; check that there are references to the page
 ]
 :result-transform (fn [rows]
   (def pagelist (set (for [r rows] (get r :page) ) ) ) ; make a set for all pagenames
   (def keymap (distinct (flatten (map keys (for [r rows] (get r :prop) ) ) ) ) ) ; make a new map of only the unique property keys and flatten it so it is no longer a set in a set
   (def proplist (for [r keymap] (str r) )) ; convert the values in the set to a string
   (sort (remove pagelist proplist) ) ; subtract the pagelist from the proplist and sort the result
 )
 :view (fn [rows] (for [r rows] (str/replace (str r ". ") ":" "") ) ) ; add a . to the end of each item and remove the : from the name. This results in a single line as icon. file. etc.
}
#+END_QUERY

(PS. can change the view clause, this is taken from a query I use myself to check my properties against a list of property definitions that I made. Just tweaked it to use pages instead.)

2 Likes

Wow. Thank you again for the quick response. This is actually very close to what I’m looking for. Now, could you enlighten me on what I should do to match all pages that are not properties? Basically, I would like to exclude ?props from all page names. Thank you.

Thank you for the suggested solution. To be frank, I can follow everything until result-transform and then I’m lost :rofl: But it is just me in this learning phase. result-transform currently still seems like magic to me. Do you think what I’m trying to do might be too complicated at this moment? I would like to find all tags that are not used as page/block properties.

What follows is:

  • a combination of the above suggestions
    • The power of the community.
  • closer to what you want
    • Not sure anymore, as your later descriptions contradict the earlier ones.
  • fast by avoiding needless combinations
    • The used technique is worthy in itself.
#+BEGIN_QUERY
{:title [:b "Unreferenced property names"]
 :query [:find ?name ?prop               ; return both the pages and the properties
   :keys page prop                       ; bind them to keys for use in result-transform
   :where
     (or-join [?name ?prop]
       (and                              ; this group produces the ?prop
         [(str "") ?name]                ; empty string as switch for performance
         [?b :block/properties ?prop]    ; get all the properties
         [?b :block/name _]              ; limit to properties of pages, remove for including blocks
         (not [(empty? ?prop)])
       )
       (and                              ; this group produces the ?name
         [(set) ?prop]                   ; empty set as switch for performance
         [?p :block/original-name ?name] ; get the original name of all pages
         (not [?p :block/journal? true]) ; exclude journal pages, remove for including them
         [_ :block/refs ?p]              ; check that there are references to the page
       )
     )
 ]
 :result-transform (fn [rows]
   ;; make a set for all pagenames
   (def pagelist (set (for [r rows] (get r :page) ) ) )
   ;; make a new map of only the unique property keys as strings
   (def proplist (map (fn [s] (subs (str s) 1) ) ; drop the colon
     (distinct (flatten (map keys (for [r rows] (get r :prop) ) ) ) )
   ) )
   ;; subtract the pagelist from the proplist and sort the result
   (sort (remove pagelist proplist) )
 )
}
#+END_QUERY
2 Likes

Thank you and I apologize for the contradicting description. I thought it would be something as easy as a “not” clause but it seems like it takes more than that. I really appreciate your help.