読者です 読者をやめる 読者になる 読者になる

日曜プログラミング

休日趣味でやってるプログラミング関連記事をダラダラと

datomic チュートリアルを試す(3) - 実体の属性値をクエリで取る

前回、datomic でのデータ定義の考え方と実体の属性値をどうやって取得するかまでやってみた。記事はこちら。
datomic チュートリアルを試す(1) - 導入・DB 作成・最初のクエリ実行 - 日曜プログラミング

今回公式チュートリアルで該当する原文項目は以下。

  • Querying for an attribute's value
  • Querying for multiple attributes' values
  • Querying by attribute values

前回見た人は途中こう思ったかもしれない。「あれ、クエリは?」と。
自分もそう思ってたら次の項目だったw

属性値をクエリで取る(Querying for an attribute's value)

前回やったコミュニティ名一覧を取るコードを再掲する。

(map #(->> (first %) (d/entity (d/db cn)) :community/name) result)

これとほぼ同等の事を行うクエリと実行結果。

> (d/q '[:find ?c ?n :where [?c :community/name ?n]] (d/db cn)))

#{[17592186045470 "Beach Drive Blog"]
[17592186045603 "KOMO Communities - View Ridge"]
  ; ... 省略 ...
[17592186045575 "Jefferson Park Alliance"]
[17592186045472 "Beacon Hill Alliance of Neighbors"]
}

最初にやった :community/name を持つ entity 全てを取るクエリ。

(d/q '[:find ?c :where [?c :community/name]] (d/db cn))

最初のクエリから変わっているのは ?n が妙な所に入ったと言うだけ。

多分これの違いはもう少しコード寄りの言葉で理解した方が良さそうなので書く。

  • 最初のクエリ(?c だけの方)
    • 「属性で :community/name を持つ実体 ?c を探せ」
  • 次のクエリ(?n がついた方)
    • 「属性で :community/name の値が ?n である実体 ?c を探せ」

?の後に続くのはいわゆるクエリ変数のようなもので、どうもこの ? の個数分結果として返されるようだ。多分何もバインドされないと全部返すような動きになるんだろう。
この結果なら map last してやればコミュニティ名だけ抜き出せる。

複数の属性値をクエリで取る(Querying for multiple attributes' values)

こうする。

; コミュニティ名と URL を取得する
> (d/q '[:find ?n ?u
         :where
         [?c :community/name ?n]
         [?c :community/url ?u]]
       (d/db cn))
#{["Chinatown/International District" "http://www.cidbia.org/"]
  ["All About Belltown" "http://www.belltown.org/"]
  ; ... 省略 ...
  ["Magnuson Environmental Stewardship Alliance" "http://mesaseattle.org/"]}

変数らしき ? を増やしゃいいみたい。
ついでに一つ前で entity id 要らないよなあと思ってたがこう書けば特に entity id は出ないみたい。

もういっちょ。これが暗黙の JOIN の例なのかな。

; コミュニティ "belltown" のカテゴリを取得する
> (d/q '[:find ?e ?c
         :where
         [?e :community/name "belltown"]
         [?e :community/category ?c]]
       (d/db cn))
#{[17592186045476 "events"] [17592186045476 "news"]}

ここでまた entity id を取るようにしてるが、その id が同じなのに注目。
:community/name が "belltown" の実体に属してる category が返されている事が分かる。

属性値「で」クエリを実行する(Querying by attribute values)

SQL で言う WHERE field_name = 'value' みたいなの。

; コミュニティのタイプが twitter なコミュニティ名を取得する
> (d/q '[:find ?n
         :where
         [?c :community/name ?n]
         [?c :community/type :community.type/twitter]]
       (d/db cn))
#{["Discover SLU"]
  ["Fremont Universe"]
  ["Columbia Citizens"]
  ["Magnolia Voice"]
  ["Maple Leaf Life"]
  ["MyWallingford"]}

さしずめ :community/type がフィールド名で :community.type/twitter が条件として探す値ってところ。どちらも Clojure 構文上ではキーワードだが、前者は datomic の属性名であり、後者は固定の値であるのに注意するくらいかな。

参照関係にあるもののクエリ(Querying across references)

例として、地域(region)を条件にして該当するコミュニティ全てを探すにはどうすれば良いか?と言うのが例題として出されてる。

しかし、データ定義を見返すと地域は region エンティティに :district/region 属性があるだけで community エンティティにはない。

だが、:community/neighborhood 属性は neighborhood エンティティを参照しており、更にその :neighborhood/district 属性は district エンティティを参照している。で、district エンティティには :district/region 属性がある。

なので、こう書くと取得可能。

; 北西(:region/ne)にあるコミュニティ名を取得する
> (d/q '[:find ?c_name
         :where
         [?c :community/name ?c_name]
         [?c :community/neighborhood ?n]
         [?n :neighborhood/district ?d]
         [?d :district/region :region/ne]]
       (d/db cn))
#{["KOMO Communities - U-District"]
  ["Maple Leaf Community Council"]
  ["KOMO Communities - View Ridge"]
  ["Hawthorne Hills Community Website"] 
  ...}

上記は北西のみだったが、コミュニティ毎の方角を全部出ししたい場合はこう。

; コミュニティの方角全部出す(?)
> (d/q '[:find ?c_name ?r_name
         :where
         [?c :community/name ?c_name]
         [?c :community/neighborhood ?n]
         [?n :neighborhood/district ?d]
         [?d :district/region ?r]
         [?r :db/ident ?r_name]]
     (d/db cn))
#{["Highland Park Action Committee" :region/sw]
  ["Broadview Community Council" :region/sw]
  ["Georgetown Seattle" :region/s]
  ...}

コメントに(?)を付けているのはそもそも方角ってどこ基準なんだろうとふと思ったから。ま、それはさておき次へ行く。

あれ、:db/ident って挟まないとダメ?と思いこうしてみたら、

; これは ?r が方角のエンティティそのものを返すのでちょっと違う
(d/q '[:find ?c_name ?r
       :where
       [?c :community/name ?c_name]
       [?c :community/neighborhood ?n]
       [?n :neighborhood/district ?d]
       [?d :district/region ?r]]
     (d/db cn))
{["Georgetown Art Center" 17592186045433]
  ["KOMO Communities - Green Lake" 17592186045434]
  ...}

説明としてはサンプル内のコメント通り。

今のところは参照をたぐり寄せるような JOIN はちょっと面白いかもしれないと思ったくらいかな。

今日はここまで。