2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Logseqのクエリで複合検索や逆順検索をする

Last updated at Posted at 2024-10-31

先に結論

TODOの最新30を取得し、優先度 (A,B,C) をアルファベット順、日付を降順(新しい順)にする例。

(記事の後半に別解も紹介。)

#+BEGIN_QUERY
{:title "TODO (直近30)"
 :query (todo TODO)
 :result-transform (fn [result]
  (let [rev-pri (fn [p] (case p "A" "Z" "B" "Y" "C" "X" "Z" "B" "A"))]
   (->> result
    (sort-by (fn [r]
        [
          (rev-pri (get r :block/priority "Z"))
          (get (get r :block/page) :block/journal-day 19000101)
        ]
     ))
    reverse
    (take 30)
    ;; 日付デバッグ用
    ;; (map (fn [m] (update m :block/properties
    ;;   (fn [u]
    ;;      (assoc u
    ;;           :journal-day (get (get m :block/page) :block/journal-day)
    ;;       )
    ;;   ))))
    )))
 :breadcrumb-show? false
}
#+END_QUERY

解説

まず、->> はThread-Lastマクロと呼ばれていて、関数を順番に実行して適用するという意味。Thread-Firstマクロ (->) との違いは、引数が関数の最後に渡されるか最初に渡されるかの違い。

わかりやすくいえば次のように置き換えられる。

(->> result fn_a fn_b fn_c)
; 上と下は同じ意味
(fn_c (fn_b (fn_a result)))

もう少し具体的な例をClojureDocから引用しておく。

;; An example of using the "thread-last" macro to get
;; the sum of the first 10 even squares.
user=> (->> (range)
            (map #(* % %))
            (filter even?)
            (take 10)
            (reduce +))
1140

;; This expands to:
user=> (reduce +
               (take 10
                     (filter even?
                             (map #(* % %)
                                  (range)))))
1140

これを踏まえると、:result-transform の中身は次のように分けて考えられる。

(fn [result]
  ; 先に使いたい関数を定義
  (let [rev-pri (fn [p] (case p "A" "Z" "B" "Y" "C" "X" "Z" "B" "A"))]
   ; 得られた結果を、
   (->> result
    ; まずソートして
    (sort-by (fn [r]
        [
          (rev-pri (get r :block/priority "Z"))
          (get (get r :block/page) :block/journal-day 19000101)
        ]
     ))
    ; 逆順にして
    reverse
    ; 30個とってきて、
    (take 30)
    ; ついでに表示データに :journal-day を加える (※デバッグ用で、必須ではない。)
    (map (fn [m] (update m :block/properties
      (fn [u]
         (assoc u
              :journal-day (get (get m :block/page) :block/journal-day)
          )
      ))))
    )))

わかりやすく、ソートの部分だけを取り出しておく。

(sort-by (fn [r]
    [
      (rev-pri (get r :block/priority "Z"))
      (get (get r :block/page) :block/journal-day 19000101)
    ]
 ))

ここでは、複数のソート条件を配列で渡している。(ちなみにget関数の最後の引数にあるZ19000101は、フォールバックというもので、見つからなかったときにその文字列で間に合わせるというやつ。)

ちなみに、sort-byreverseを2回やってしまうと、結果全体が新規に書き換わってしまうのでうまくいかない。Logseqフォーラムにもあるように、juxt等も同様の理由で今回は役に立たない。

そこで、ここでは作為的に、rev-priなる、優先順位のアルファベットを逆転させる関数を先にletで定義して、A->Z, B->Y... と先に置き換えておくことで、逆順ソートを実現している。

Thread-Lastマクロの最後で、結果を逆転=reverseしているので、最終結果としては、優先度 (A,B,C) がアルファベット順、日付が降順(新しい順) になるようにソートされる。

なお、今回は「優先度」を配列の先に置いているので、「優先度」が優先されてソートされるが、逆にすれば日付が優先される。これは例えばDONEなどの終わったものを順に表示させるときに良いと思う。

ちなみに今回はsort-byに配列を渡したけれど、配列ではなくて文字列結合(str)でも可。こちらのほうが内部で何が起きているかわかりやすいと思う。

別解

journal-day20241001のような数値で返ってくることを活かして、

(sort-by (fn [r]
    [
      (get r :block/priority "Z")
      (- (get (get r :block/page) :block/journal-day 19000101))
    ]
 ))

として reverse を取り除いても良い。

ただこの方法はマイナス値として扱えるような数値 (つまり整数) が来た場合に限る。

余談

複合検索とか、逆順検索みたいなのは、標準機能としてあってくれてもいいかなーと若干思う。

ただ、アルファベットを数値にする方法がわかれば1、例えば日付の逆順なんかはマイナス値にすれば良いので、より汎用的な方法はありそう。(ただ、数値とかアルファベット1文字とかじゃない、2024-10-01 みたいな非数値の文字列が現れたときはたぶん悩む。)

(追記)LogseqフォーラムにFeature Requestも一応出してみた。もしかしたらより良い方法があって教えてもらえるかも。2

  1. cljsなのでint関数は使えないし、Logseqは使える関数が制限されている様子。「優先度」(A,B,C) のように特定の文字列であれば、数値に変換できる範囲に変換してからread-stringする等もできる…?

  2. ただ、執筆時点現在LogseqはDB版という新しい機構に開発リソースを割いている様子なので、もしそちらが公開されればクエリなどの方法も刷新されそう。その際はこの議論は古くなってしまう可能性も。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?