1

I'm starting to use neo4j. In my graph database I've the nodes Person (look at "John" below), with the labels: Name (string), Food (positive integer). Each Person is connected with other Person through the relationship isFriendTo, that has a value. I use the graph DB only to find the shortest weighted path between two person.

Also, every day I check every node in the graph, and if the food go under value of 100, I take some actions.

Now, after some improvements, the property Food is no more enough for my project. So I have to split this in three other more specific property (positive integer): Vegetables, Meat and Cereals. If the sum of both three go under 100, I've to take the same actions as before. My old situation was as "John", the only options to which I can evolve my design is to "Fred" or "Paul"?

enter image description here

In which way can I design this? Should I use in addition to neo4j something like MongoDB to represent hierarchy?

Removing the property Food and add the three new properties seems like a bad practices to me. I've to save elsewhere that this 3 means "food"... and what about if in the future will I add others types of foods? Where do I store the knowledge that the value 100 to check, must come from the sum of Meat, Vegetables and Cereals? Having something like this, will solve my doubt because I can sum all items inside food:

{
  "name": "Lucas",
  "food": {
    "meat": 40,
    "vegetables": 30,
    "cereals": 0
  }
}

(I don't need to traverse the connections from Food and Vegetables to Person. Just need to check that the sum of Meat, Veg. and Cereals is lesser or greater 100.)

Accollativo
  • 1,537
  • 4
  • 32
  • 56

2 Answers2

2

It seems that you are confusing the terms label and property.

According to your diagram, Person seems to be the label shared by all your nodes, and Name/Food//Meat/Vegetables/Cererals seem to be the names of node properties.

If my understanding is correct, then there are many approaches to handling multiple food type amounts, and getting a total per person. Below are a couple of examples.

  1. Here is one approach. You could introduce the Food label for unique food type nodes:

    (:Food {type: 'Meat'}), (:Food {type: 'Vegetable'}), etc.
    

    and each Person node can have a HAS_FOOD relationship (with an amount property) to each relevant Food node (instead of storing the food type properties internally):

    (john:Person {Name: 'John'})-[:HAS_FOOD {amount: 140}]->(meat:Food {type: 'Meat'})
    

    With this data model, to find all Persons with more than 100 units of food:

    MATCH (p:Person)-[r:HAS_FOOD]->()
    WITH p, SUM(r.amount) AS total
    WHERE total > 100
    RETURN p;
    
  2. Here is another approach (which will likely result in faster searches). Since a neo4j property cannot have a map value (contrary to what you show in the JSON near the bottom of your question), each Person node could have amount and food arrays, like this:

    (:Person {Name: 'Fred', amount: [50, 100], food: ['Meat','Vegetables']})
    

    With this data model, to find all Persons with more than 100 units of food:

    MATCH (p:Person)
    WHERE REDUCE(s = 0, a IN p.amount | s + a) > 100
    RETURN p;
    

    [UPDATE]

    However, doing food processing (nice pun, here), with second approach can be more cumbersome and less efficient. For example, this is one way to get the amount of Meat for Fred:

    MATCH (p:Person {Name: 'Fred'})
    RETURN [i IN RANGE(0, SIZE(p.food)-1) WHERE p.food[i] = 'Meat' | p.amount[i]][0] AS meatAmt;
    

    And, to set the amount of Meat for Fred to 123:

    MATCH (p:Person {Name: 'Fred'})
    SET p.amount = [i IN RANGE(0, SIZE(p.food)-1) | CASE WHEN p.food[i] = 'Meat' THEN 123 ELSE p.amount[i]];
    
  3. So, here is a 3rd approach that solves your issue AND is much better for performing food processing. Each Person node could store food amounts directly as properties, like this:

    (:Person {Name: 'Fred', Meat: 50, Vegetables: 100, foodNames: ['Meat', 'Vegetables']})
    

    With this data model, the foodNames array allows you to iterate through the food properties by name. So, to find all Persons with more than 100 units of food:

    MATCH (p:Person)
    WHERE REDUCE(s = 0, n IN p.foodNames | s + p[n]) > 100
    RETURN p;
    

    And, to get the amount of Meat for Fred:

    MATCH (p:Person {Name: 'Fred'})
    RETURN p.Meat AS meatAmt;
    

    To set the amount of Meat for Fred to 123:

    MATCH (p:Person {Name: 'Fred'})
    SET p.Meat = 123;
    
cybersam
  • 63,203
  • 6
  • 53
  • 76
0

On Neo4j, labels are like tags, there is no technical hierarchy, and a node can has many labels.

But if you want to says that Food is the parent of Vegetables, Meat and Cereals from your business perspective, there is no prob. You will have a business/semantic hierarchy.

So from my POV, in your case I would only add the new labels on your nodes with the label Food

logisima
  • 7,340
  • 1
  • 18
  • 31