Query all nodes that contain links to nodes A __and__ B #106
-
I'm very interested in being able to query my org-roam DB like with For example, I would like to do queries that return a list of all nodes that link to nodes A and B. Is something like this possible? |
Beta Was this translation helpful? Give feedback.
Replies: 4 comments 28 replies
-
Hey @wuqui Right now links are not part of So back to your specific question. This library already provides a function to find notes linked to current note - Lines 95 to 100 in d6792e9 So we can just combine it into solution thanks to existence of (let* ((node1 (org-roam-populate
(org-roam-node-create :id "feffc6de-9a35-449b-8470-e64b4f28832c")))
(backlinks1 (seq-map #'org-roam-backlink-source-node
(org-roam-backlinks-get node1)))
(node2 (org-roam-populate
(org-roam-node-create :id "c9731b65-61f8-4007-9dbf-d54056f55cc4")))
(backlinks2 (seq-map #'org-roam-backlink-source-node
(org-roam-backlinks-get node2))))
(seq-intersection
(seq-map #'org-roam-node-title backlinks1)
(seq-map #'org-roam-node-title backlinks2))) This returns the following list for me (first note is a description of wine that contains both grapes and the latter where I mention everything in one single journal note): ("Tenuta di Castellaro Nero Ossidiana 2015" "Saturday, 23 October 2021") As opposed to this list, when I combine these two lists via ("Tenuta di Castellaro Nero Ossidiana 2015" "Tenuta di Castellaro Corinto 2017" "Saturday, 23 October 2021" "Cerasuolo di Vittoria DOCG" "Frappato" "Donnafugata Tancredi 2016" "Tasca Regaleali Nero d'Avola 2017" "Firriato Santagostino Baglio Soria Nero d'Avola Syrah 2014" "COS Pithos Rosso 2010" "Gulfi NeroSanlorè 2013" "Gulfi neroBaronj 2016" "Gulfi Nerojbleo 2009" ...) As you can see, none of (vulpea-db-query
(lambda (note)
(and (seq-contains-p (vulpea-note-backlinks note)
"feffc6de-9a35-449b-8470-e64b4f28832c")
(seq-contains-p (vulpea-note-backlinks note)
"c9731b65-61f8-4007-9dbf-d54056f55cc4")))) Hope that helps. |
Beta Was this translation helpful? Give feedback.
-
Brilliant, thanks for this quick and excellent answer! How could I turn this into an (interactive) function I can use? So from a user perspective, for this to be practically useful, you would probably want to be able to call a function and then interactively select >= 1 node and get a list of notes back? I think this should be incredibly useful for lots of people in different use cases. I guess the fact that queries like this are very popular in Roam Research and logseq would be some empirical evidence that it's worth building something like this for org-roam. |
Beta Was this translation helpful? Give feedback.
-
Glad you find it useful.
What do you mean by 'back'? :) Like, in interactive use there is no point in returning anything from a function as it's interactive use. But let me show you several examples. First, here is a function that returns a list of backlinks for a given list of notes. (defun vulpea-backlinks-many (notes)
"Return notes that link to all NOTES at the same time."
(let* ((blinks-all
(emacsql-with-transaction (org-roam-db)
(seq-map
(lambda (note)
(seq-map
#'vulpea-db--from-node
(seq-map
#'org-roam-backlink-source-node
(org-roam-backlinks-get
(org-roam-populate
(org-roam-node-from-id (vulpea-note-id note)))))))
notes))))
(seq-reduce
(lambda (r e)
(seq-intersection
r e
(lambda (a b)
(string-equal (vulpea-note-id a)
(vulpea-note-id b)))))
blinks-all
(seq-uniq (apply #'append blinks-all))))) Non-interactive usage example: ELISP> (seq-map
#'vulpea-note-title
(vulpea-backlinks-many
(list
(vulpea-db-get-by-id "feffc6de-9a35-449b-8470-e64b4f28832c")
(vulpea-db-get-by-id "c9731b65-61f8-4007-9dbf-d54056f55cc4"))))
("Tenuta di Castellaro Nero Ossidiana 2015" "Saturday, 23 October 2021") Now we ca use it in many ways. For example, this will allow to interactively select several notes, and then select a title among these titles: (defun select-backlinks-many ()
"It's hard to explain."
(interactive)
(let* ((notes (vulpea-utils-collect-while
#'vulpea-select
nil
"Note" :require-match t))
(blinks (vulpea-backlinks-many notes)))
(completing-read
"Blacklink: "
(seq-map #'vulpea-note-title blinks)))) It uses a helper function from Lines 89 to 111 in d6792e9 Basically, you initially select as many notes as you wish and stop this by pressing Update You can also select and visit backlink using this function: (defun find-backlinks-many ()
"It's hard to explain."
(interactive)
(let* ((notes (vulpea-utils-collect-while
#'vulpea-select
nil
"Note" :require-match t))
(blinks (vulpea-backlinks-many notes))
(note (vulpea-select-from
"Backlink" blinks :require-match t)))
(org-roam-node-visit
(org-roam-node-from-id (vulpea-note-id note))))) Selection happens the same. Hope that helps! |
Beta Was this translation helpful? Give feedback.
-
I've been playing around with TL;DR this is a long comment, so please jump to conclusion if you wish to skip all the details (which is totally understandable). BenchmarksI am going to benchmark every implementation using the following code: (benchmark-run 100
(seq-map
#'vulpea-note-title
(vulpea-backlinks-many
(list
(vulpea-db-get-by-id "feffc6de-9a35-449b-8470-e64b4f28832c")
(vulpea-db-get-by-id "c9731b65-61f8-4007-9dbf-d54056f55cc4"))))) Maybe it's not the best way to benchmark, but it gives quite a good view on what's happening. Keep in mind that I am running on 8390 notes. Which is not super big collection, but still not the smallest one. And yes, I expect every implementation to behave like this: ELISP> (seq-map
#'vulpea-note-title
(vulpea-backlinks-many
(list
(vulpea-db-get-by-id "feffc6de-9a35-449b-8470-e64b4f28832c")
(vulpea-db-get-by-id "c9731b65-61f8-4007-9dbf-d54056f55cc4"))))
("Tenuta di Castellaro Nero Ossidiana 2015" "Saturday, 23 October 2021") Using
|
test | org-roam |
vulpea-db-query |
specialized |
---|---|---|---|
simple | 0.28974334 | 2.28282158 | 0.022780650000000002 |
popular | 1.85930086 | 2.34636907 | 1.42250805 |
big intersection | 4.53420141 | 2.27249325 | 0.46843158 |
Conclusion?
So now that we have all the data, we can draw some conclusions.
First and foremost, I am pleased that vulpea-db-query
is the most stable in terms of performance across various test cases. While it is the slowest in real-world scenario, it is also the most powerful and generic solution - you can combine many predicates into one based on id, path, level, title, aliases, tags, links, properties and meta. So IMHO, this function is great 😸
Secondly, vulpea
needs more specialized queries. As you can see, this simple solution outperforms all other. Since real world is full of cases where you need to query by one specific field only, it only makes sense to provide these specialized functions. I am thinking on including the following functions at the beginning:
vulpea-db-query-by-tags-some
- return all notes that are tagged at least by one tag from the given list of TAGS;vulpea-db-query-by-tags-every
- return all notes that are tagged by each tag from the given list of TAGS;vulpea-db-query-by-links-some
- return all notes that link to at least one note from the given list of NOTES;vulpea-db-query-by-links-every
- return all notes that link to each note from the given list of NOTES;
I will definitely come up with more functions, but this should cover so many use cases that I have or plan to have in vino.
Beta Was this translation helpful? Give feedback.
Glad you find it useful.
What do you mean by 'back'? :) Like, in interactive use there is no point in returning anything from a function as it's interactive use. But let me show you several examples.
First, here is a function that returns a list of backlinks for a given list of notes.