Статьи

Neo4j: моделирование подтипов

Вопрос, который иногда возникает при обсуждении моделирования графовых данных, заключается в том, как вы будете моделировать суб / супертипы .

По моему опыту, есть две причины, почему мы можем захотеть сделать это:

  • Чтобы убедиться, что определенные свойства существуют на битах данных
  • Чтобы написать детализацию запросов на основе этих типов

В настоящее время первый не встроен в Neo4j, и вы сможете достичь этого только путем подключения некоторого кода в обработчике события транзакции перед фиксацией, поэтому мы сосредоточимся на последнем.

Типичный пример, используемый для демонстрации того, как проектировать подтипы, — это царство животных, и мне удалось найти набор данных из Луизвилля, Службы животных Кентукки, который мы можем использовать.

В этом случае подтипы используются для представления типа животного, группы пород и породы. Затем у нас также есть «реальные данные» с точки зрения реальных собак, находящихся под присмотром животных.

В результате мы получаем два графика в одном — модель и метамодель:

2014 10 20 22 32 31

Запрос шифра для создания этого графа выглядит так:

LOAD CSV WITH HEADERS FROM "file:/Users/markneedham/projects/neo4j-subtypes/data/dogs.csv" AS line
MERGE (animalType:AnimalType {name: "Dog"})
MERGE (breedGroup:BreedGroup {name: line.BreedGroup})
MERGE (breed:Breed {name: line.PrimaryBreed})
MERGE (animal:Animal {id: line.TagIdentity, primaryColour: line.PrimaryColour, size: line.Size})
 
MERGE (animalType)<-[:PARENT]-(breedGroup)
MERGE (breedGroup)<-[:PARENT]-(breed)
MERGE (breed)<-[:PARENT]-(animal)

Затем мы могли бы написать простой запрос, чтобы узнать, сколько у нас собак:

MATCH (animalType:AnimalType)<-[:PARENT*]-(animal)
RETURN animalType, COUNT(*) AS animals
ORDER BY animals DESC

==> +--------------------------------+
==> | animalType           | animals |
==> +--------------------------------+
==> | Node[89]{name:"Dog"} | 131     |
==> +--------------------------------+
==> 1 row

Или мы могли бы написать немного более сложный запрос, чтобы найти количество животных на каждом уровне нашей иерархии типов:

MATCH path = (animalType:AnimalType)<-[:PARENT]-(breedGroup)<-[:PARENT*]-(animal)
RETURN [node IN nodes(path) | node.name][..-1] AS breed, COUNT(*) AS animals
ORDER BY animals DESC
LIMIT 5

==> +-----------------------------------------------------+
==> | breed                                     | animals |
==> +-----------------------------------------------------+
==> | ["Dog","SETTER/RETRIEVE","LABRADOR RETR"] | 15      |
==> | ["Dog","SETTER/RETRIEVE","GOLDEN RETR"]   | 13      |
==> | ["Dog","POODLE","POODLE MIN"]             | 10      |
==> | ["Dog","TERRIER","MIN PINSCHER"]          | 9       |
==> | ["Dog","SHEPHERD","WELSH CORGI CAR"]      | 6       |
==> +-----------------------------------------------------+
==> 5 rows

Затем мы могли бы решить добавить подграф упражнения, который указывает, сколько упражнений требует каждый тип собаки:

MATCH (breedGroup:BreedGroup)
WHERE breedGroup.name IN ["SETTER/RETRIEVE", "POODLE"]
MERGE (exercise:Exercise {type: "2 hours hard exercise"})
MERGE (exercise)<-[:REQUIRES_EXERCISE]-(breedGroup);

MATCH (breedGroup:BreedGroup)
WHERE breedGroup.name IN ["TERRIER", "SHEPHERD"]
MERGE (exercise:Exercise {type: "1 hour gentle exercise"})
MERGE (exercise)<-[:REQUIRES_EXERCISE]-(breedGroup);

Затем мы можем запросить это, чтобы выяснить, какие собаки должны выйти на 2 часа тяжелых упражнений:

MATCH (exercise:Exercise {type: "2 hours hard exercise"})<-[:REQUIRES_EXERCISE]-()<-[:PARENT*]-(dog)
WHERE NOT (dog)<-[:PARENT]-()
RETURN dog
LIMIT 10

==> +-----------------------------------------------------------+
==> | dog                                                       |
==> +-----------------------------------------------------------+
==> | Node[541]{id:"664427",primaryColour:"BLACK",size:"SMALL"} |
==> | Node[542]{id:"543787",primaryColour:"BLACK",size:"SMALL"} |
==> | Node[543]{id:"584021",primaryColour:"BLACK",size:"SMALL"} |
==> | Node[544]{id:"584022",primaryColour:"BLACK",size:"SMALL"} |
==> | Node[545]{id:"664430",primaryColour:"BLACK",size:"SMALL"} |
==> | Node[546]{id:"535176",primaryColour:"BLACK",size:"SMALL"} |
==> | Node[567]{id:"613557",primaryColour:"WHITE",size:"SMALL"} |
==> | Node[568]{id:"531376",primaryColour:"WHITE",size:"SMALL"} |
==> | Node[569]{id:"613567",primaryColour:"WHITE",size:"SMALL"} |
==> | Node[570]{id:"531379",primaryColour:"WHITE",size:"SMALL"} |
==> +-----------------------------------------------------------+
==> 10 rows

В этом запросе мы удостоверились, что вернули только собак, а не породы, проверив, что нет никаких РОДИЛЬНЫХ отношений. В качестве альтернативы мы могли бы отфильтровать на этикетке животных

MATCH (exercise:Exercise {type: "2 hours hard exercise"})<-[:REQUIRES_EXERCISE]-()<-[:PARENT*]-(dog:Animal)
RETURN dog
LIMIT 10

или если бы мы хотели выводить собак только для упражнений, возможно, у нас была бы метка Dog на соответствующих узлах.

Людям часто любопытно, почему у меток нет супер / подтипов между ними, но я склонен использовать метки для простой категоризации — что-нибудь более сложное, и мы можем также использовать встроенную мощь графовой модели!

Код на GitHub , если вы захотите играть с ним.