Эквивалент предложения SQL "limit" в Datomic

Тип заголовка говорит все, но скажу, что у меня есть простой запрос:

(q '[:find ?c ?n :where [?c :my-thing/its-attribute ?n]]
   (d/db conn))

против такой схемы, как

[{:db/id (d/tempid :db.part/db)
  :db/ident :my-thing/its-attribute
  :db/valueType :db.type/string
  :db/doc "My thing attribute"
  :db/cardinality :db.cardinality/one
  :db.install/_attribute :db.part/db}]

Если запрос соответствует всем (например, 100M записей), возвращаемые результаты будут большими. Если я просто хочу несколько из них, что лучший способ сделать это?

Ответ 1

Два случайных имени с использованием rand (допустимые дубликаты) и выборки (только отдельные)

(d/q '[:find [(rand 2 ?name) (sample 2 ?name)]
       :where [_ :artist/name ?name]]
     db)

Этот пример пришел из day-of-datomic github repo.

Ответ 2

Для простых случаев "все пары атрибутов и значений, отсортированные", использование take с seek-datoms является вашим лучшим вариантом. В следующем примере используется mbrainz примерная база данных:

(def conn (d/connect "datomic:sql://mbrainz-1968-1973?jdbc:postgresql://localhost:5432/datomic?user=datomic&password=datomic"))

(->> (d/seek-datoms (d/db conn) :avet :artist/sortName "Bea")
     (take 20))

и возвращает:

(#datom[17592186050196 81 "Beach Boys, The" 13194139539089 true] #datom[17592186047857 81 "Beatles, The" 13194139536749 true] #datom[17592186048553 81 "Beau Brummels, The" 13194139537425 true] #datom[17592186049043 81 "Beaver & Krause" 13194139537919 true] #datom[17592186046205 81 "Beaver, Paul" 13194139535085 true] #datom[17592186046692 81 "Beck, Bogert & Appice" 13194139535579 true] #datom[17592186046886 81 "Beck, Jeff" 13194139535761 true] #datom[17592186047111 81 "Beck, Jeff Group" 13194139535995 true] #datom[17592186046486 81 "Bedford, David" 13194139535371 true] #datom[17592186046992 81 "Bee Gees" 13194139535865 true] #datom[17592186045876 81 "Beethoven, Ludwig van" 13194139534747 true] #datom[17592186048427 81 "Beggars Opera" 13194139537321 true] #datom[17592186047091 81 "Beginning of the End, The" 13194139535969 true] #datom[17592186045945 81 "Belafonte, Harry" 13194139534825 true] #datom[17592186047485 81 "Bell, Archie & Drells, The" 13194139536359 true] #datom[17592186045915 81 "Bell, Carey" 13194139534799 true] #datom[17592186046324 81 "Bell, Vinnie" 13194139535215 true] #datom[17592186047164 81 "Bell, William" 13194139536047 true] #datom[17592186047652 81 "Belle, Marie-Paule" 13194139536541 true] #datom[17592186046496 81 "Bellou, Sotiria" 13194139535371 true])

Конечно, вы можете сопоставить fn, который ограничивает вывод или добавляет больше атрибутов и т.д., например, в этом примере:

(let [db (d/db conn)]
  (->> (d/seek-datoms db :avet :artist/sortName "Bea")
       (take 20)
       (map #(merge {:artist/name (:v %)
                     :artist/type (-> (d/pull db [{:artist/type [:db/ident]}] (:e %))
                                      :artist/type
                                      :db/ident)}))))

Что возвращает:

({:artist/name "Beach Boys, The" :artist/type :artist.type/group} {:artist/name "Beatles, The" :artist/type :artist.type/group} {:artist/name "Beau Brummels, The" :artist/type :artist.type/group} {:artist/name "Beaver & Krause" :artist/type :artist.type/group} {:artist/name "Beaver, Paul" :artist/type :artist.type/person} {:artist/name "Beck, Bogert & Appice" :artist/type :artist.type/group} {:artist/name "Beck, Jeff" :artist/type :artist.type/person} {:artist/name "Beck, Jeff Group" :artist/type :artist.type/group} {:artist/name "Bedford, David" :artist/type :artist.type/person} {:artist/name "Bee Gees" :artist/type :artist.type/group} {:artist/name "Beethoven, Ludwig van" :artist/type :artist.type/person} {:artist/name "Beggars Opera" :artist/type :artist.type/group} {:artist/name "Beginning of the End, The" :artist/type :artist.type/group} {:artist/name "Belafonte, Harry" :artist/type :artist.type/person} {:artist/name "Bell, Archie & Drells, The" :artist/type :artist.type/group} {:artist/name "Bell, Carey" :artist/type :artist.type/person} {:artist/name "Bell, Vinnie" :artist/type :artist.type/person} {:artist/name "Bell, William" :artist/type :artist.type/person} {:artist/name "Belle, Marie-Paule" :artist/type :artist.type/person} {:artist/name "Bellou, Sotiria" :artist/type :artist.type/person})

Примечание: использовать seek-datoms или datoms с помощью :avet, этот атрибут должен быть проиндексирован.

Ответ 3

Этот ответ является компиляцией компиляции @adamneilson и комментариями к исходному вопросу. Я пытался сделать то же самое, что и OP, но не смог найти свой ответ здесь, поэтому, надеюсь, это поможет кому-то.

Мой вариант использования - вытащить 100 тыс. записей с разбиением на страницы. Невозможно было просто использовать take/drop, так как потребовалось очень много времени (десятки секунд).

Мое обходное решение состояло в том, чтобы сначала получить требуемые идентификаторы сущности, сделайте take/drop в этой коллекции, затем нарисуйте их с помощью entity. Здесь мой последний код:

(defn eid->entity
  [eid]
  (into {} (d/touch (d/entity (d/db (get-conn)) eid))))

(defn find-eids
  [attr value limit offset]
  (let [query '[:find ?eid
                :in $ ?attr ?value
                :where [?eid ?attr ?value]]
        db (d/db (get-conn))
        result (drop offset (sort (d/q query db attr value)))]
    (map first (take limit result))))

(map eid->entity (find-eids :attr-name "value" 10 10)

Это кажется чересчур неправильным для моего SQL-обученного мозга, но я считаю это дотошным способом. И это не очень медленно - около 500 мс для 100 тыс. Записей, что для меня достаточно.

Ответ 4

Пробовали ли вы использовать get-some?

От: http://docs.datomic.com/query.html

Get-некоторые

Функция get-some принимает базу данных, сущность и один или несколько один атрибут, возвращающий кортеж идентификатора объекта и значение для первого атрибута, которым обладает объект.

[(get-some $ ?person :person/customer-id :person/email) ?identifier]

- Изменить для ответа на комментарий -

Вы также можете попробовать сделать запрос, который выбирает объекты под определенным числом.

user> (defn example-take-query [n]
        (into '[:find ?e :where [?e :age ?a]]
              [[`(~'> ~n ~'?e)]]))
#'user/example-take-query
user> (example-take-query 3)
[:find ?e :where [?e :age ?a] [(> 3 ?e)]]
user> (example-take-query 10)
[:find ?e :where [?e :age ?a] [(> 10 ?e)]]

Ответ 5

Мне понадобилось некоторое время назад, чтобы собрать clojure метод MySql LIMIT для коллекции:

(defmacro limit 
"Pagination mimicking the MySql LIMIT" 
([coll start-from quantity]
    `(take ~quantity (drop ~start-from ~coll)))
([coll quantity]
    `(limit ~coll 0 ~quantity)))

Простой пример использования для repl:

user=>  (defmacro limit 
  #_=>     "Pagination mimicking the MySql LIMIT" 
  #_=>     ([coll start-from quantity]
  #_=>     `(take ~quantity (drop ~start-from ~coll)))
  #_=>     ([coll quantity]
  #_=>         `(limit ~coll 0 ~quantity)))
#'user/limit
user=> (def hundred (take 100 (iterate inc 0))) ;; define a collection
#'user/hundred
user=> (limit hundred 25) ;; get the first 25 from the collection
(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24)
user=> (limit hundred 25 25) ;; get the next 25
(25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49)

Не уверен, что он точно отвечает на ваш вопрос, но он может быть полезен.