Как построить запрос, который точно совпадает с вектором ссылок в DataScript?
Настройка Рассмотрим следующую базу данных фильмов и актеров DataScript с данными, украденными из http://www.learndatalogtoday.org/: следующий код можно выполнить в REPL JVM/Clojure или REPL ClojureScript, если project.clj
содержит [datascript "0.15.0"]
как зависимость.
(ns user
(:require [datascript.core :as d]))
(def data
[["First Blood" ["Sylvester Stallone" "Brian Dennehy" "Richard Crenna"]]
["Terminator 2: Judgment Day" ["Linda Hamilton" "Arnold Schwarzenegger" "Edward Furlong" "Robert Patrick"]]
["The Terminator" ["Arnold Schwarzenegger" "Linda Hamilton" "Michael Biehn"]]
["Rambo III" ["Richard Crenna" "Sylvester Stallone" "Marc de Jonge"]]
["Predator 2" ["Gary Busey" "Danny Glover" "Ruben Blades"]]
["Lethal Weapon" ["Gary Busey" "Mel Gibson" "Danny Glover"]]
["Lethal Weapon 2" ["Mel Gibson" "Joe Pesci" "Danny Glover"]]
["Lethal Weapon 3" ["Joe Pesci" "Danny Glover" "Mel Gibson"]]
["Alien" ["Tom Skerritt" "Veronica Cartwright" "Sigourney Weaver"]]
["Aliens" ["Carrie Henn" "Sigourney Weaver" "Michael Biehn"]]
["Die Hard" ["Alan Rickman" "Bruce Willis" "Alexander Godunov"]]
["Rambo: First Blood Part II" ["Richard Crenna" "Sylvester Stallone" "Charles Napier"]]
["Commando" ["Arnold Schwarzenegger" "Alyssa Milano" "Rae Dawn Chong"]]
["Mad Max 2" ["Bruce Spence" "Mel Gibson" "Michael Preston"]]
["Mad Max" ["Joanne Samuel" "Steve Bisley" "Mel Gibson"]]
["RoboCop" ["Nancy Allen" "Peter Weller" "Ronny Cox"]]
["Braveheart" ["Sophie Marceau" "Mel Gibson"]]
["Mad Max Beyond Thunderdome" ["Mel Gibson" "Tina Turner"]]
["Predator" ["Carl Weathers" "Elpidia Carrillo" "Arnold Schwarzenegger"]]
["Terminator 3: Rise of the Machines" ["Nick Stahl" "Arnold Schwarzenegger" "Claire Danes"]]])
(def conn (d/create-conn {:film/cast {:db/valueType :db.type/ref
:db/cardinality :db.cardinality/many}
:film/name {:db/unique :db.unique/identity
:db/cardinality :db.cardinality/one}
:actor/name {:db/unique :db.unique/identity
:db/cardinality :db.cardinality/one}}))
(def all-datoms (mapcat (fn [[film actors]]
(into [{:film/name film}]
(map #(hash-map :actor/name %) actors)))
data))
(def all-relations (mapv (fn [[film actors]]
{:db/id [:film/name film]
:film/cast (mapv #(vector :actor/name %) actors)}) data))
(d/transact! conn all-datoms)
(d/transact! conn all-relations)
Описание В двух словах, в этой базе данных есть два типа сущностей - фильмы и актеры (слово, предназначенное для того, чтобы быть не сгенерированным) - и три вида данных:
- кинематографическая сущность:
:film/name
(уникальная строка) - кинематографическая сущность:
:film/cast
(несколько ссылок) - субъект субъекта:
:actor/name
(уникальная строка)
Вопрос: Я хотел бы построить запрос, который спрашивает: какие фильмы имеют эти N
актеры, и эти N
одни актеры, появившиеся как единственные звезды, для N>=2?
Например, в RoboCop снимались Нэнси Аллен, Питер Веллер, Ронни Кокс, но ни в одном фильме не снимались только первые два из них, Аллен и Веллер. Поэтому я ожидаю, что следующий запрос выдаст пустой набор:
(d/q '[:find ?film-name
:where
[?film :film/name ?film-name]
[?film :film/cast ?actor-1]
[?film :film/cast ?actor-2]
[?actor-1 :actor/name "Nancy Allen"]
[?actor-2 :actor/name "Peter Weller"]]
@conn)
; => #{["RoboCop"]}
Тем не менее, запрос некорректен, потому что я не знаю, как выразить, что в любых матчах должны исключаться любые актеры, которые не являются Алленом или Веллером - опять же, я хочу найти фильмы, где только Аллен и Веллер сотрудничали без каких-либо других актеров, поэтому Я хочу адаптировать приведенный выше запрос для создания пустого набора. Как я могу настроить этот запрос, чтобы обеспечить выполнение этого требования?
2 ответа
Поскольку DataScript не имеет отрицания (по состоянию на май 2016 года), я не верю, что это возможно с одним статическим запросом в "чистом" Datalog.
Мой путь будет:
- построить запрос программно, чтобы добавить N предложений, которые утверждают, что приведение должно содержать N актеров
- Добавьте функцию предиката, которая, учитывая фильм, базу данных и набор идентификаторов актеров, использует индекс EAVT, чтобы найти, есть ли в каждом фильме актер, которого нет в наборе.
Вот базовая реализация
(defn only-those-actors? [db movie actors]
(->> (datoms db :eavt movie :film/cast) seq
(every? (fn [[_ _ actor]]
(contains? actors actor)))
))
(defn find-movies-with-exact-cast [db actors-names]
(let [actors (set (d/q '[:find [?actor ...] :in $ [?name ...] ?only-those-actors :where
[?actor :actor/name ?name]]
db actors-names))
query {:find '[[?movie ...]]
:in '[$ ?actors ?db]
:where
(concat
(for [actor actors]
['?movie :film/cast actor])
[['(only-those-actors? ?db ?movie ?actors)]])}]
(d/q query db actors db only-those-actors?)))
Вы можете использовать предикат весело и d/entity
вместе для фильтрации данных по :film/cast
поле сущности. Этот подход выглядит намного проще, пока Datascript не поддерживает отрицание (не оператор и т. Д.).
Посмотри на ряд (= a (:age (d/entity db e))
в тестовом случае Datascript здесь
[{:db/id 1 :name "Ivan" :age 10}
{:db/id 2 :name "Ivan" :age 20}
{:db/id 3 :name "Oleg" :age 10}
{:db/id 4 :name "Oleg" :age 20}]
...
(let [pred (fn [db e a]
(= a (:age (d/entity db e))))]
(is (= (q/q '[:find ?e
:in $ ?pred
:where [?e :age ?a]
[(?pred $ ?e 10)]]
db pred)
#{[1] [3]})))))
В вашем случае тело предиката может выглядеть примерно так
(clojure.set/subset? actors (:film/cast (d/entity db e))
Что касается производительности, то d/entity
вызов быстрый, потому что это поиск по индексу.