Knowledge management with neo4j

The aim of this page is to create a knowledge management software using the graph database neo4j (and its query language Cypher).

The approach is object-orientated, i.e. the nodes are classes and instances.

Class nodes are violet. Instance nodes are brown. Subclass arrows are red. Instance arrows are gray.

Knowledge is represented by questions and answers.
Questions come from classes, and they are shown in blue. Answers typically come from instances, and they are shown in green.

Cypher basics

// print all
MATCH (n) RETURN n;

// delete all
MATCH (n) DETACH DELETE n;

## Q&A as edges

[edit | edit source]

Here questions and answers are modeled as relationships, i.e. they are shown as arrows between between nodes.

The direction of these arrows is not important. They start, where the question is more likely to be asked:

* "Who is responsible for this car?"  (rather than "For which cars is this person responsible?")
* "To which country does this city belong?"  (rather than "Which cities belong to this country?")

One problem with Q&A as edges is, that the connection between them is flimsy. (See this question in the neo4j forum.)

### car, person

[edit | edit source]

There are two questions, that connect the classes car and person:

* Who is responsible for the car?  There must be two people responsible for each car. One person can be responsible for up to five cars.
* Who is the driver?  There might be no driver. If there is one, it must be exactly one person, who drives exactly one car.

The cardinalities are not yet part of the code. The aim is, that the software will highlight unanswered questions for a given node.
If the question "Who is responsible for Dodge?" were answered only by "Alice", the question for a second responsible person would still be highlighted.
(The question for the driver would be shown less prominently, because a driver is not required.)

diagram

| browser version

create

// class nodes
CREATE (Car:Class {name:'car'})
CREATE (Person:Class {name:'person'})

// instance nodes
CREATE (Alice:Inst {name:'Alice'})
CREATE (Bob:Inst {name:'Bob'})
CREATE (Chris:Inst {name:'Chris'})
CREATE (Dodge:Inst {name:'Dodge'})

CREATE

// instance relations
(Dodge) -[:INST]-> (Car),
(Alice) -[:INST]-> (Person),
(Bob) -[:INST]-> (Person),
(Chris) -[:INST]-> (Person),

// questions
(Car) -[:QUEST {name: 'responsible'}]-> (Person),
(Car) -[:QUEST {name: 'driver'}]-> (Person),

// answers
(Dodge) -[:ANS {quest: 'responsible'}]-> (Alice),
(Dodge) -[:ANS {quest: 'responsible'}]-> (Bob),
(Dodge) -[:ANS {quest: 'driver'}]-> (Chris);

### city, country, sea

[edit | edit source]

All questions are asked by classes, and all answers are given by instances.

diagram

| browser version

create

// class nodes
CREATE (City:Class {name:'city'})
CREATE (Country:Class {name:'country'})
CREATE (Sea:Class {name:'sea'})
CREATE (PortCity:Class {name:'port city'})

// instance nodes
CREATE (LaRochelle:Inst {name:'La Rochelle'})
CREATE (Marseille:Inst {name:'Marseille'})
CREATE (France:Inst {name:'France'})
CREATE (Atlantic:Inst {name:'Atlantic'})
CREATE (Mediterranean:Inst {name:'Mediterranean'})

CREATE

// subclass relations
(PortCity) -[:SUB]-> (City),

// instance relations
(LaRochelle) -[:INST]-> (PortCity),
(Marseille) -[:INST]-> (PortCity),
(France) -[:INST]-> (Country),
(Atlantic) -[:INST]-> (Sea),
(Mediterranean) -[:INST]-> (Sea),

// questions
(City) -[:QUEST {name: 'city part of country'}]-> (Country),
(Country) -[:QUEST {name: 'country access to sea'}]-> (Sea),
(Country) -[:QUEST {name: 'country borders country'}]-> (Country),
(PortCity) -[:QUEST {name: 'port city access to sea'}]-> (Sea),

// answers
(LaRochelle) -[:ANS {quest: 'city part of country'}]-> (France),
(Marseille) -[:ANS {quest: 'city part of country'}]-> (France),
(LaRochelle) -[:ANS {quest: 'port city access to sea'}]-> (Atlantic),
(Marseille) -[:ANS {quest: 'port city access to sea'}]-> (Mediterranean),
(France) -[:ANS {quest: 'country access to sea'}]-> (Atlantic),
(France) -[:ANS {quest: 'country access to sea'}]-> (Mediterranean);

Now the class French port city is introduced. It answers the question asked by city. The instances inherit the answer.

diagram

| browser version

create

// class nodes
CREATE (City:Class {name:'city'})
CREATE (Country:Class {name:'country'})
CREATE (Sea:Class {name:'sea'})
CREATE (PortCity:Class {name:'port city'})
CREATE (FrenchPortCity:Class {name:'French port city'})

// instance nodes
CREATE (LaRochelle:Inst {name:'La Rochelle'})
CREATE (Marseille:Inst {name:'Marseille'})
CREATE (France:Inst {name:'France'})
CREATE (Atlantic:Inst {name:'Atlantic'})
CREATE (Mediterranean:Inst {name:'Mediterranean'})

CREATE

// subclass relations
(PortCity) -[:SUB]-> (City),
(FrenchPortCity) -[:SUB]-> (PortCity),

// instance relations
(LaRochelle) -[:INST]-> (FrenchPortCity),
(Marseille) -[:INST]-> (FrenchPortCity),
(France) -[:INST]-> (Country),
(Atlantic) -[:INST]-> (Sea),
(Mediterranean) -[:INST]-> (Sea),

// questions
(City) -[:QUEST {name: 'city part of country'}]-> (Country),
(Country) -[:QUEST {name: 'country access to sea'}]-> (Sea),
(Country) -[:QUEST {name: 'country borders country'}]-> (Country),
(PortCity) -[:QUEST {name: 'port city access to sea'}]-> (Sea),

// answers
(FrenchPortCity) -[:ANS {quest: 'city part of country'}]-> (France),
(LaRochelle) -[:ANS {quest: 'port city access to sea'}]-> (Atlantic),
(Marseille) -[:ANS {quest: 'port city access to sea'}]-> (Mediterranean),
(France) -[:ANS {quest: 'country access to sea'}]-> (Atlantic),
(France) -[:ANS {quest: 'country access to sea'}]-> (Mediterranean);

#### queries

[edit | edit source]

questions

| only ancestors (without questions)

MATCH
(inst:Inst {name: 'Marseille'})
-[:INST]->
(parent:Class)
-[:SUB*0..]->
(ancestor:Class)
RETURN ancestor;

╒═══════════════════════════════════╕
│ancestor                           │
╞═══════════════════════════════════╡
│(:Class {name: "French port city"})│
├───────────────────────────────────┤
│(:Class {name: "port city"})       │
├───────────────────────────────────┤
│(:Class {name: "city"})            │
└───────────────────────────────────┘

MATCH
(inst:Inst {name: 'Marseille'})
-[:INST]->
(parent:Class)
-[:SUB*0..]->
(ancestor:Class)
-[quest:QUEST]-
(:Class)
RETURN quest.name;

╒═════════════════════════╕
│quest.name               │
╞═════════════════════════╡
│"port city access to sea"│
├─────────────────────────┤
│"city part of country"   │
└─────────────────────────┘

answers

| only self and ancestors (without answers)

MATCH
(inst:Inst {name: 'Marseille'})
-[:INST*0..1]->
(selfOrParent)
-[:SUB*0..]->
(selfOrAncestor)
RETURN selfOrAncestor;

╒═══════════════════════════════════╕
│selfOrAncestor                     │
╞═══════════════════════════════════╡
│(:Inst {name: "Marseille"})        │
├───────────────────────────────────┤
│(:Class {name: "French port city"})│
├───────────────────────────────────┤
│(:Class {name: "port city"})       │
├───────────────────────────────────┤
│(:Class {name: "city"})            │
└───────────────────────────────────┘

MATCH
(inst:Inst {name: 'Marseille'})
-[:INST*0..1]->
(selfOrParent)
-[:SUB*0..]->
(selfOrAncestor)
-[ans:ANS]-
(ansInst:Inst)
RETURN ans.quest, ansInst.name;

╒═════════════════════════╤═══════════════╕
│ans.quest                │ansInst.name   │
╞═════════════════════════╪═══════════════╡
│"port city access to sea"│"Mediterranean"│
├─────────────────────────┼───────────────┤
│"city part of country"   │"France"       │
└─────────────────────────┴───────────────┘

## Q&A as nodes

[edit | edit source]

The Q&A are reified into nodes. This allows an explicit relationship between them, shown by yellow arrows.
This will also allow questions connected to more than two classes - or to only one.

### city, country, sea

[edit | edit source]

questions only

| diagram

create

// class nodes

CREATE (City:Class {name:'city'})
CREATE (Country:Class {name:'country'})
CREATE (Sea:Class {name:'sea'})
CREATE (PortCity:Class {name:'port city'})

// instance nodes

CREATE (LaRochelle:Inst {name:'La Rochelle'})
CREATE (Marseille:Inst {name:'Marseille'})

CREATE (France:Inst {name:'France'})

CREATE (Atlantic:Inst {name:'Atlantic'})
CREATE (Mediterranean:Inst {name:'Mediterranean'})

// subclass relations

CREATE (PortCity) -[:SUB]-> (City)

// instance relations

CREATE (LaRochelle) -[:INST]-> (PortCity)
CREATE (Marseille) -[:INST]-> (PortCity)

CREATE (France) -[:INST]-> (Country)

CREATE (Atlantic) -[:INST]-> (Sea)
CREATE (Mediterranean) -[:INST]-> (Sea)

/////////////////////////////////////////////////////////////////////////////

// question nodes

CREATE (CityPartCountry:Quest {name:'city part of country'})
CREATE (CountryAccessSea:Quest {name:'country access to sea'})
CREATE (CountryBordersCountry:Quest {name:'country borders country'})
CREATE (PortCityAccessSea:Quest {name:'port city access to sea'})

// question relations

CREATE (CityPartCountry) -[:QUEST {name: 'city'}]-> (City)
CREATE (CityPartCountry) -[:QUEST {name: 'country'}]-> (Country)

CREATE (CountryAccessSea) -[:QUEST {name: 'country'}]-> (Country)
CREATE (CountryAccessSea) -[:QUEST {name: 'sea'}]-> (Sea)

CREATE (CountryBordersCountry) -[:QUEST {name: 'country'}]-> (Country)
CREATE (CountryBordersCountry) -[:QUEST {name: 'country'}]-> (Country)

CREATE (PortCityAccessSea) -[:QUEST {name: 'port city'}]-> (PortCity)
CREATE (PortCityAccessSea) -[:QUEST {name: 'sea'}]-> (Sea)

questions and answers

| diagram

create

The class, instance and question nodes have been created with the query above.
This one fetches the instance and question nodes with `MATCH`, and creates everything related to the answers.

// instance nodes

MATCH (LaRochelle:Inst {name:'La Rochelle'})
MATCH (Marseille:Inst {name:'Marseille'})

MATCH (France:Inst {name:'France'})

MATCH (Atlantic:Inst {name:'Atlantic'})
MATCH (Mediterranean:Inst {name:'Mediterranean'})

// question nodes

MATCH (CityPartCountry:Quest {name:'city part of country'})
MATCH (CountryAccessSea:Quest {name:'country access to sea'})
MATCH (CountryBordersCountry:Quest {name:'country borders country'})
MATCH (PortCityAccessSea:Quest {name:'port city access to sea'})

/////////////////////////////////////////////////////////////////////////////

// answer nodes

CREATE (LaRochellePartFrance:Ans)
CREATE (MarseillePartFrance:Ans)

CREATE (LaRochelleAccessAtlantic:Ans)
CREATE (MarseilleAccessMediterranean:Ans)

CREATE (FranceAccessAtlantic:Ans)
CREATE (FranceAccessMediterranean:Ans)

// answer question relations

CREATE (LaRochellePartFrance) -[:AQ]-> (CityPartCountry)
CREATE (MarseillePartFrance) -[:AQ]-> (CityPartCountry)

CREATE (FranceAccessAtlantic) -[:AQ]-> (CountryAccessSea)
CREATE (FranceAccessMediterranean) -[:AQ]-> (CountryAccessSea)

CREATE (LaRochelleAccessAtlantic) -[:AQ]-> (PortCityAccessSea)
CREATE (MarseilleAccessMediterranean) -[:AQ]-> (PortCityAccessSea)

// answer relations

CREATE (LaRochellePartFrance) -[:ANS {name: 'city'}]-> (LaRochelle)
CREATE (LaRochellePartFrance) -[:ANS {name: 'country'}]-> (France)

CREATE (MarseillePartFrance) -[:ANS {name: 'city'}]-> (Marseille)
CREATE (MarseillePartFrance) -[:ANS {name: 'country'}]-> (France)

CREATE (FranceAccessAtlantic) -[:ANS {name: 'country'}]-> (France)
CREATE (FranceAccessAtlantic) -[:ANS {name: 'sea'}]-> (Atlantic)

CREATE (FranceAccessMediterranean) -[:ANS {name: 'country'}]-> (France)
CREATE (FranceAccessMediterranean) -[:ANS {name: 'sea'}]-> (Mediterranean)

CREATE (LaRochelleAccessAtlantic) -[:ANS {name: 'port city'}]-> (LaRochelle)
CREATE (LaRochelleAccessAtlantic) -[:ANS {name: 'sea'}]-> (Atlantic)

CREATE (MarseilleAccessMediterranean) -[:ANS {name: 'port city'}]-> (Marseille)
CREATE (MarseilleAccessMediterranean) -[:ANS {name: 'sea'}]-> (Mediterranean)

And here the subclass French port city is added:

diagram

create

// class nodes

CREATE (City:Class {name:'city'})
CREATE (Country:Class {name:'country'})
CREATE (Sea:Class {name:'sea'})
CREATE (PortCity:Class {name:'port city'})
CREATE (FrenchPortCity:Class {name:'French port city'})

// instance nodes

CREATE (LaRochelle:Inst {name:'La Rochelle'})
CREATE (Marseille:Inst {name:'Marseille'})

CREATE (France:Inst {name:'France'})

CREATE (Atlantic:Inst {name:'Atlantic'})
CREATE (Mediterranean:Inst {name:'Mediterranean'})

// subclass relations

CREATE (PortCity) -[:SUB]-> (City)
CREATE (FrenchPortCity) -[:SUB]-> (PortCity)

// instance relations

CREATE (LaRochelle) -[:INST]-> (FrenchPortCity)
CREATE (Marseille) -[:INST]-> (FrenchPortCity)

CREATE (France) -[:INST]-> (Country)

CREATE (Atlantic) -[:INST]-> (Sea)
CREATE (Mediterranean) -[:INST]-> (Sea)

/////////////////////////////////////////////////////////////////////////////

// question nodes

CREATE (CityPartCountry:Quest {name:'city part of country'})
CREATE (CountryAccessSea:Quest {name:'country access to sea'})
CREATE (CountryBordersCountry:Quest {name:'country borders country'})
CREATE (PortCityAccessSea:Quest {name:'port city access to sea'})

// answer nodes

CREATE (FrenchPortCityPartFrance:Ans)

CREATE (LaRochelleAccessAtlantic:Ans)
CREATE (MarseilleAccessMediterranean:Ans)

CREATE (FranceAccessAtlantic:Ans)
CREATE (FranceAccessMediterranean:Ans)

// question relations

CREATE (CityPartCountry) -[:QUEST {name: 'city'}]-> (City)
CREATE (CityPartCountry) -[:QUEST {name: 'country'}]-> (Country)

CREATE (CountryAccessSea) -[:QUEST {name: 'country'}]-> (Country)
CREATE (CountryAccessSea) -[:QUEST {name: 'sea'}]-> (Sea)

CREATE (CountryBordersCountry) -[:QUEST {name: 'country'}]-> (Country)
CREATE (CountryBordersCountry) -[:QUEST {name: 'country'}]-> (Country)

CREATE (PortCityAccessSea) -[:QUEST {name: 'port city'}]-> (PortCity)
CREATE (PortCityAccessSea) -[:QUEST {name: 'sea'}]-> (Sea)

// answer question relations

CREATE (FrenchPortCityPartFrance) -[:AQ]-> (CityPartCountry)

CREATE (FranceAccessAtlantic) -[:AQ]-> (CountryAccessSea)
CREATE (FranceAccessMediterranean) -[:AQ]-> (CountryAccessSea)

CREATE (LaRochelleAccessAtlantic) -[:AQ]-> (PortCityAccessSea)
CREATE (MarseilleAccessMediterranean) -[:AQ]-> (PortCityAccessSea)

// answer relations

CREATE (FrenchPortCityPartFrance) -[:ANS {name: 'city'}]-> (FrenchPortCity)
CREATE (FrenchPortCityPartFrance) -[:ANS {name: 'country'}]-> (France)

CREATE (FranceAccessAtlantic) -[:ANS {name: 'country'}]-> (France)
CREATE (FranceAccessAtlantic) -[:ANS {name: 'sea'}]-> (Atlantic)

CREATE (FranceAccessMediterranean) -[:ANS {name: 'country'}]-> (France)
CREATE (FranceAccessMediterranean) -[:ANS {name: 'sea'}]-> (Mediterranean)

CREATE (LaRochelleAccessAtlantic) -[:ANS {name: 'port city'}]-> (LaRochelle)
CREATE (LaRochelleAccessAtlantic) -[:ANS {name: 'sea'}]-> (Atlantic)

CREATE (MarseilleAccessMediterranean) -[:ANS {name: 'port city'}]-> (Marseille)
CREATE (MarseilleAccessMediterranean) -[:ANS {name: 'sea'}]-> (Mediterranean)

As above, the queries show the Q&A for Marseille:

questions

MATCH
(inst:Inst {name: 'Marseille'})
-[:INST]->
(parent:Class)
-[:SUB*0..]->
(ancestor:Class)
<-[:QUEST]-
(quest:Quest)
RETURN quest.name;

╒═════════════════════════╕
│quest.name               │
╞═════════════════════════╡
│"port city access to sea"│
├─────────────────────────┤
│"city part of country"   │
└─────────────────────────┘

answers

MATCH
(inst:Inst {name: 'Marseille'})
-[:INST*0..1]->
(selfOrParent)
-[:SUB*0..]->
(selfOrAncestor)
<-[:ANS]-
(ansNode:Ans),
(ansNode) -[:ANS]-> (farNode),
(ansNode) -[:AQ]-> (questNode:Quest)
RETURN questNode.name, farNode.name;

╒═════════════════════════╤═══════════════╕
│questNode.name           │farNode.name   │
╞═════════════════════════╪═══════════════╡
│"port city access to sea"│"Mediterranean"│
├─────────────────────────┼───────────────┤
│"city part of country"   │"France"       │
└─────────────────────────┴───────────────┘

### car, person

[edit | edit source]

In this example the cardninalities are added to the code.

diagram

create

// class nodes

CREATE (Car:Class {name:'car'})
CREATE (Person:Class {name:'person'})

// instance nodes

CREATE (Alice:Inst {name:'Alice'})
CREATE (Bob:Inst {name:'Bob'})
CREATE (Chris:Inst {name:'Chris'})
CREATE (Dodge:Inst {name:'Dodge'})

// instance relations

CREATE (Dodge) -[:INST]-> (Car)
CREATE (Alice) -[:INST]-> (Person)
CREATE (Bob) -[:INST]-> (Person)
CREATE (Chris) -[:INST]-> (Person)

/////////////////////////////////////////////////////////////////////////////

// question nodes

CREATE (Responsible:Quest {name:'responsible for car'})
CREATE (Driver:Quest {name:'driver of car'})

// answer nodes

CREATE (AliceResponsibleDodge:Ans)
CREATE (BobResponsibleDodge:Ans)
CREATE (ChrisDriverDodge:Ans)

// question relations

CREATE (Responsible) -[:QUEST {name: 'person', long_name: '2 people', cardinality: '2'}]-> (Person)
CREATE (Responsible) -[:QUEST {name: 'car', long_name: '0..5 cars', cardinality: '0..5'}]-> (Car)

CREATE (Driver) -[:QUEST {name: 'person', long_name: '0..1 person', cardinality: '0..1'}]-> (Person)
CREATE (Driver) -[:QUEST {name: 'car', long_name: '0..1 car', cardinality: '0..1'}]-> (Car)

// answer question relations

CREATE (AliceResponsibleDodge) -[:AQ]-> (Responsible)
CREATE (BobResponsibleDodge) -[:AQ]-> (Responsible)
CREATE (ChrisDriverDodge) -[:AQ]-> (Driver)

// answer relations

CREATE (AliceResponsibleDodge) -[:ANS {name: 'person'}]-> (Alice)
CREATE (AliceResponsibleDodge) -[:ANS {name: 'car'}]-> (Dodge)

CREATE (BobResponsibleDodge) -[:ANS {name: 'person'}]-> (Bob)
CREATE (BobResponsibleDodge) -[:ANS {name: 'car'}]-> (Dodge)

CREATE (ChrisDriverDodge) -[:ANS {name: 'person'}]-> (Chris)
CREATE (ChrisDriverDodge) -[:ANS {name: 'car'}]-> (Dodge)

The query for the questions contains the cardinalities.  (That for the answers has not changed.)

questions from car

MATCH
(inst:Inst {name: 'Dodge'})
-[:INST]->
(parent:Class)
-[:SUB*0..]->
(ancestor:Class)
<-[:QUEST]-
(quest:Quest)
-[farQuestRel:QUEST]->
(:Class)
RETURN quest.name AS question, farQuestRel.name AS class, farQuestRel.cardinality AS cardinality;

╒═════════════════════╤════════╤═══════════╕
│question             │class   │cardinality│
╞═════════════════════╪════════╪═══════════╡
│"driver of car"      │"person"│"0..1"     │
├─────────────────────┼────────┼───────────┤
│"responsible for car"│"person"│2          │
└─────────────────────┴────────┴───────────┘

questions from person

The questions are the same for any person. Replace `Dodge` in the query above e.g. with `Alice`.

╒═════════════════════╤═════╤═══════════╕
│question             │class│cardinality│
╞═════════════════════╪═════╪═══════════╡
│"responsible for car"│"car"│"0..5"     │
├─────────────────────┼─────┼───────────┤
│"driver of car"      │"car"│"0..1"     │
└─────────────────────┴─────┴───────────┘

answers

The answer query has not changed. It is like that for Marseille shown above.

| Dodge  | Alice or Bob  | Chris

|
|

╒═════════════════════╤════════════╕
│questNode.name       │farNode.name│
╞═════════════════════╪════════════╡
│"responsible for car"│"Alice"     │
├─────────────────────┼────────────┤
│"responsible for car"│"Bob"       │
├─────────────────────┼────────────┤
│"driver of car"      │"Chris"     │
└─────────────────────┴────────────┘

╒═════════════════════╤════════════╕
│questNode.name       │farNode.name│
╞═════════════════════╪════════════╡
│"responsible for car"│"Dodge"     │
└─────────────────────┴────────────┘

╒═══════════════╤════════════╕
│questNode.name │farNode.name│
╞═══════════════╪════════════╡
│"driver of car"│"Dodge"     │
└───────────────┴────────────┘