Query for co-occurences in property

Does someone know, whether it’s possible to query for pages that are co-listed in a specific property of any page/block?

So, for instance, we have a block

- block
authors:: [[Alice]], [[Bob]]

and we want to query for all pages, that occur in the authors property together with Alice (so here our output should be Bob).

As the example implicates, one could use such a query to list all co-authors :slight_smile:

My failed attempt:

#+BEGIN_QUERY
{:title "Co-Authors"
 :query [:find (pull ?b [*])
       :where
       [?h :block/properties ?prop]
       [(get ?prop :authors) ?v]
       [(contains? ?v ?b)]
       [(contains? ?v "Alice")]
]}
#+END_QUERY

As I understand, contains? does not bind the variable ?b, that’s why this doesn’t work.

Other failed attempt:

I tried to use the functions, and get at least a string with all the authors together (multiple times and with Alice included), but it didn’t work as well :sweat_smile:
I get a syntax error :frowning:

- #+BEGIN_QUERY
  {:title "Work"
   :query [:find (pull ?h [*])
         :where
         [?h :block/properties ?prop]
         [(get ?prop :authors) ?v]
         [(contains? ?v "Alice")]
  ]}
  #+END_QUERY
  - {{function (print-str (concat (fn [x] (:authors x) result)))}}

Note: the Work-query itself returns all blocks with Alice listed under authors

Obsidian Dataview Version

Don’t know, whether this helps, but here is a Dataview-js version of the wanted query:

let me = "Alice"
let relatedAuthors = dv.pages()
	.where(b => b.author)
	.where(b => b.author
		.map(x => String(x) === me)
		.includes(true))
    .author
	.map(x => String(x))
	.where(x => x !== me)
	.distinct()


dv.list(
	relatedAuthors.map(x => dv.fileLink(x, false, x))
)

Ok, so I managed to do what I tried with the function:

#+BEGIN_QUERY
{:title "Co-Authors"
  :query [:find ?authors
      :where
      [?b :block/properties ?prop]
      [(get ?prop :authors) ?authors]
      [(contains? ?authors "Alice")]
]
:view (fn [authors] [:div (for [author (flatten authors)] (str author))])}
#+END_QUERY

With this, if My data is:

image

The result is:
image

It is kind of ugly for now, buy I guess it’s possible, to improve it… And learn some clojure on the way, I guess :sweat_smile:

1 Like

One more update (sorry for ugly formatting, it’s just better readable for me…):

#+BEGIN_QUERY
  {:title "Co-Authors"
    :query [:find ?authors
        :where
        [?b :block/properties ?prop]
        [(get ?prop :authors) ?authors]
        [(contains? ?authors "Alice")]
  ]
  :view (fn [authors] 
    [:div
      (for 
        [author (set/select 
          (partial not= "Alice")
          (reduce set/union authors)
        )]
        [:a.tag.mr-1 {:data-ref (str "/page/" author)}
          (str "#" author)
        ]
      )
    ]
  )}
  #+END_QUERY

With this, the resullt looks like it should, but the created div-s don’t work like link-buttons :frowning: That’s the point where I start to not know what to do further.

How it looks:
image

They buttons even change on hover, but don’t work like text, sadly.

Just to not feel guilty about writing all this exploration here on the forum:
I’m posting it here for two reasons:

  1. So that someone else who gets to the same point can use this as a guide
  2. If someone already knows something I miss, please tell me! :smiley:

Does this query {{query (property authors Alice)}} solve it for you?

Not really, since I want to get a list with all the coauthors, each once, without the associated pages, and without Alice herself.

The following does half the job:

Result

image

We can see all the coauthors of Alice, each once and Alice herself is excluded.
If I click on either tag, it opens the corresponding page… in my default .md editor… (which is not Logseq)
Also, if the page does not exist yet, the link does nothing :confused:

To improve

  1. open the page in Logseq
  2. If it does not exist yet - create it.

Since Logseq already does this, I’m sure there already exists a way for that, but I can’t find it.

Code

#+BEGIN_QUERY
{:title "Co-Authors"
:query [:find ?authors
    :where
    [?b :block/properties ?prop]
    [(get ?prop :authors) ?authors]
    [(contains? ?authors "Alice")]
]
:view (fn [authors] 
(for 
  [author (set/select 
    (partial not= "Alice")
    (reduce set/union authors)
  )]

  [:div {:style {:display "inline"}}
    [:a.tag.mr-1 {:href (clojure.string/lower-case (str "/$Logseq/pages/" (str author ".md")))}
      (str "#" author)
    ]
  ]
)
)}
#+END_QUERY

Where $Logseq is the path to my logseq-graph folder

This is how a link to a page (named “Link Test”) shows up in the page code.

<span class="inline">
	<span data-ref="Link Test" class="page-reference">
		<span class="text-gray-500 bracket">[[</span>
		<div class="" data-tooltipped="" aria-describedby="tippy-tooltip-11" style="display: inline;">
			<a data-ref="link test" class="page-ref">Link Test</a>
		</div>
		<span class="text-gray-500 bracket">]]</span>
	</span>
</span>

Maybe replicating that and it would ‘just work’?

Thanks for your answer!
Sadly I didn’t get this to work :frowning: Tried to generate it with the :div and :a (functions?), and also to manually just insert it into the page-code in developer tools. Even tried do add to the bullet’s div’s data-refs-self value to include the linked page’s title. Doesn’t seem to work. I guess, it’s hidden somewhere deeper.

Maybe if I find more urge for this functionality, I’ll do a feature request :slight_smile:
(Mainly: clearer documentation on the query table function (https://docs.logseq.com/#/page/query%2Ftable%2Ffunction) and the possibility to return the link to a page/bullet by it’s title).

I think I got to a good enough solution! :slight_smile:

I use the kind answer of @danzu as the main query, and as a child a query table function that returns a list of all coauthors and number of co-occurrences.

Turns out the documentation for query table function was actually enough :sweat_smile:
Consider this another example if you land here while looking for it.

Test data

Test Data

Result of Query and Function

Code

Query

{{query (property authors Alice)}}

Function

As I understand, the {{ }}- stuff is inline only, so I got it only to work in the one-lined format.

In Logseq

{{function (let [authors-list (map (fn [x] (:authors x)) result)] (let [authors (set/select (partial not= "Alice") (reduce set/union authors-list))](for [author authors] (let [num (count (filter identity (map (fn [x] (contains? x author)) authors-list)))](str author ": " num "\n")))))}}

Readable Format

{{function 
  (let [authors-list (map (fn [x] (:authors x)) result)] 
    (let 
      [authors 
        (set/select 
          (partial not= "Alice") 
          (reduce set/union authors-list))]
      (for [author authors] 
        (let [num (count (filter identity (map (fn [x] (contains? x author)) authors-list)))]
          (str author ": " num)))))}}

Improvements

Of course, it would still be nice to generate the links to the other authors’ pages/blocks - but this might be a future topic.

2 Likes

And as a template :slight_smile:

Using :current-page for the query and <% current page %> for the function while handling lower-case and the “[[” “]]” -brackets.

Note that one can use this for different properties (e.g. “type” instead of “authors”) using in both the query and the function :type instead of :authors - the rest can actually stay the same (leads to the variable names to be strange though)

query-table:: true
query-properties:: [:title :authors]
template:: Person's Work
#+BEGIN_QUERY
  {:title "Work"
    :query [:find (pull ?b[*])
        :in $ ?current-page
        :where
        [?b :block/properties ?prop]
        [(get ?prop :authors) ?authors]
        [?me-low :block/name ?current-page]
        [?me-low :block/original-name ?me]
        [(contains? ?authors ?me)]]
    :inputs [:current-page]}
  #+END_QUERY

	- **Coauthors**
	  {{function (let [authors-list (map (fn [x] (:authors x)) result)] (let [authors (set/select (partial (fn [a b] (not= a (str/lower-case (str "[[" b "]]")))) (str/lower-case "<% current page %>")) (reduce set/union authors-list))](for [author authors] (let [num (count (filter identity (map (fn [x] (contains? x author)) authors-list)))](str author ": " num "\n")))))}}

Only the function, formatted by calva (Calva: Clojure & ClojureScript Interactive Programming - Visual Studio Marketplace):

(let [authors-list
      (map (fn [x] (:authors x))
           result)]
  (let [authors
        (set/select
         (partial
          (fn [a b]
            (not= a
                  (str/lower-case (str "[[" b "]]"))))
          (str/lower-case "<% current page %>"))
         (reduce set/union authors-list))]
    (for [author authors]
      (let [num
            (count
             (filter identity (map (fn [x] (contains? x author)) authors-list)))]
        (str author ": " num "\n")))))