Fork me on GitHub
#xtdb
<
2024-03-09
>
kokonut16:03:21

I am trying to write a query, and am still learning xtdb so seeking some help. I have two kinds of entities: post and commentary. As the names suggest, commentary entity belongs to one of post entity. In other words, a post may relate to multiple commentaries. So the maps would look like

{:post/id ...
 :post/last-edited-at ...}

{:commentary/id ...
 :commentary/post-id ...
 :commentary/last-edited-at ...}
I need to sort posts by the order of last-edited-at of both post itself and the latest ones of its related commentaries. How can I do that?

Oliver Marshall11:03:37

You can do so with a subquery + using or-join to replace COALESCE, I've broken down how I got to it into steps so it's (hopefully) easier to follow:

(xt/submit-tx xtdb-node
  [[::xt/put {:xt/id :post-1 :post/last-edited-at #inst "2020"}]
   [::xt/put {:xt/id :post-2 :post/last-edited-at #inst "2021"}]
   [::xt/put {:xt/id :post-3 :post/last-edited-at #inst "2022"}]

   [::xt/put {:xt/id :comment-1 :post/id :post-1 :commentary/last-edited-at #inst "2020-01"}]
   [::xt/put {:xt/id :comment-2 :post/id :post-1 :commentary/last-edited-at #inst "2020-02"}]
   [::xt/put {:xt/id :comment-3 :post/id :post-1 :commentary/last-edited-at #inst "2023"}]

   [::xt/put {:xt/id :comment-4 :post/id :post-2 :commentary/last-edited-at #inst "2021-01"}]
   [::xt/put {:xt/id :comment-5 :post/id :post-2 :commentary/last-edited-at #inst "2021-02"}]

   [::xt/put {:xt/id :comment-6 :post/id :post-3 :commentary/last-edited-at #inst "2024"}]])

; Posts in order of `post/last-edited-at`
(xt/q (xt/db xtdb-node)
  '{:find [post-id last-edited-at]
    :where [[post-id :post/last-edited-at last-edited-at]]
    :order-by [[last-edited-at :desc]]})
; => [[:post-3 #inst "2022-01-01T00:00:00.000-00:00"]
;     [:post-2 #inst "2021-01-01T00:00:00.000-00:00"]
;     [:post-1 #inst "2020-01-01T00:00:00.000-00:00"]]

; Comments ordered by `commentary/last-edited-at`
(xt/q (xt/db xtdb-node)
  '{:find [post-id commentry-id commentry-edit]
    :where [[commentry-id :post/id post-id]
            [commentry-id :commentary/last-edited-at commentry-edit]]
    :order-by [[commentry-edit :desc]]})
; => [[:post-1 :comment-3 #inst "2023-01-01T00:00:00.000-00:00"]
;     [:post-3 :comment-6 #inst "2022-01-01T00:00:00.000-00:00"]
;     [:post-2 :comment-5 #inst "2021-02-01T00:00:00.000-00:00"]
;     [:post-2 :comment-4 #inst "2021-01-01T00:00:00.000-00:00"]
;     [:post-1 :comment-2 #inst "2020-02-01T00:00:00.000-00:00"]
;     [:post-1 :comment-1 #inst "2020-01-01T00:00:00.000-00:00"])

; Posts and their (ordered) comment-edits
(xt/q (xt/db xtdb-node)
  '{:find [post-id post-edit comments]
    :where [[post-id :post/last-edited-at post-edit]
            [(q {:find [commentry-edit]
                 :where [[commentry-id :post/id post-id]
                         [commentry-id :commentary/last-edited-at commentry-edit]]
                 :in [post-id]
                 :order-by [[commentry-edit :desc]]}
                post-id)
             comments]]
    :order-by [[post-edit :desc]]})
; => [[:post-3
;      #inst "2022-01-01T00:00:00.000-00:00"
;      ([#inst "2022-01-01T00:00:00.000-00:00"])
;     [:post-2
;      #inst "2021-01-01T00:00:00.000-00:00"
;      ([#inst "2021-02-01T00:00:00.000-00:00"]
;       [#inst "2021-01-01T00:00:00.000-00:00"])]
;     [:post-1
;      #inst "2020-01-01T00:00:00.000-00:00"
;      ([#inst "2023-01-01T00:00:00.000-00:00"]
;       [#inst "2020-02-01T00:00:00.000-00:00"]
;       [#inst "2020-01-01T00:00:00.000-00:00"])])


; Posts and their latest comment-edit
(xt/q (xt/db xtdb-node)
  '{:find [post-id (or post-edit latest-comment-edit)]
    :where [[post-id :post/last-edited-at post-edit]
            [(q {:find [commentry-edit]
                 :where [[commentry :post/id post-id]
                         [commentry :commentary/last-edited-at commentry-edit]]
                 :in [post-id]
                 :order-by [[commentry-edit :desc]]
                 :limit 1}
                post-id)
             comments]
            [(ffirst comments) latest-comment-edit]
            [(or comments) latest-comment-edit]]
    :order-by [[latest-comment-edit :desc]]})
; => [[:post-1
;      #inst "2020-01-01T00:00:00.000-00:00"
;      #inst "2023-01-01T00:00:00.000-00:00"
;     [:post-3
;      #inst "2022-01-01T00:00:00.000-00:00"
;      #inst "2022-01-01T00:00:00.000-00:00"]
;     [:post-2
;      #inst "2021-01-01T00:00:00.000-00:00"
;      #inst "2021-02-01T00:00:00.000-00:00"]]

; We need this because `max` can't compare dates
(defn max-date [a b]
  (if (>= (compare a b) 0)
    a b))

(assert (= (max-date #inst "2020" #inst "2021")
           (max-date #inst "2021" #inst "2020")))

; Posts sorted by either their latest comment-edit,
; or their edit date
(xt/q (xt/db xtdb-node)
  '{:find [post-id sort-date]
    :where [[post-id :post/last-edited-at post-edit]
            [(q {:find [commentry-edit]
                 :where [[commentry :post/id post-id]
                         [commentry :commentary/last-edited-at commentry-edit]]
                 :in [post-id]
                 :order-by [[commentry-edit :desc]]
                 :limit 1}
                post-id)
             comments]
            [(ffirst comments) latest-comment-edit]

            (or-join [; inputs
                      post-edit latest-comment-edit
                      ; output
                      sort-date]
              [(scratch/max-date post-edit latest-comment-edit)
               sort-date]
              ; The `and nil?` is because `or-join` will
              ; choose *all* matching branches
              (and [(nil? latest-comment-edit)]
                   [(identity post-edit) sort-date]))]
    :order-by [[sort-date :desc]]})
; => [[:post-3 #inst "2024-01-01T00:00:00.000-00:00"]
;     [:post-1 #inst "2023-01-01T00:00:00.000-00:00"]
;     [:post-2 #inst "2021-02-01T00:00:00.000-00:00"]]

kokonut12:03:28

Thanks so much! I will try this.