Kiran
Kiran

Reputation: 3047

How use Cypher with Merge to create a unique sub graph path

in Neo4j 2.0 M06 I understand that CREATE UNIQUE is depreciated and replaced with MERGE and MATCH instead, but I am finding it hard to see how this can be used to create a unique path.

as an example, I want to create a

MERGE root-[:HAS_CALENDER]->(cal:Calender{name:'Booking'})-[:HAS_YEAR]->(year:Year{value:2013})-[:HAS_MONTH]-(month:Month{value:'January'})-[:HAS_DAY]->(day:Day{value:1})
ON CREATE cal
SET cal.created = timestamp()
ON CREATE year
SET year.created = timestamp()
ON CREATE month
SET month.created = timestamp()
ON CREATE day
SET day.created = timestamp()

intention is that when I try to add a new days to my calender, it should only create the year, and month when it does not exist else just add to the existing path. Now when i run the query, i get an STATEMENT_EXECUTION_ERROR

MERGE only supports single node patterns

should I be executing multiple statements here to achieve this. So the question is what's the best way in Neo4j to handle cases like this?

Edit

I did change my approach a bit and now even after making multiple calls, I think my merge is happening at a label level and not trying to restrict to the start node I provide as a result I am ending up with nodes that are shared across years and month which is not what I was expecting

enter image description here

I would really appreciate if some one can suggest me how to get a proper graph like below

enter image description here

my c# code is somewhat like this:

var qry = GraphClient.Cypher
            .Merge("(cal:CalendarType{ Name: {calName}})")
            .OnCreate("cal").Set("cal = {newCal}")
            .With("cal")
            .Start(new { root = GraphClient.RootNode})
            .CreateUnique("(root)-[:HAS_CALENDAR]->(cal)")
            .WithParams(new { calName = newCalender.Name, newCal = newCalender })
            .Return(cal => cal.Node<CalenderType>());
        var calNode = qry.Results.Single();


        var newYear = new Year { Name = date.Year.ToString(), Value = date.Year }.RunEntityHousekeeping();

        var qryYr = GraphClient.Cypher
            .Merge("(year:Year{ Value: {yr}})")
            .OnCreate("year").Set("year = {newYear}")
            .With("year")
            .Start(new { calNode })
            .CreateUnique("(calNode)-[:HAS_YEAR]->(year)")
            .WithParams(new { yr = newYear.Value, newYear = newYear })
            .Return(year => year.Node<Year>());
        var yearNode = qryYr.Results.Single();


        var newMonth = new Month { Name = date.Month.ToString(), Value = date.Month }.RunEntityHousekeeping();
        var qryMonth = GraphClient.Cypher
            .Merge("(mon:Month{ Value: {mnVal}})")
            .OnCreate("mon").Set("mon = {newMonth}")
            .With("mon")
            .Start(new { yearNode })
            .CreateUnique("(yearNode)-[:HAS_MONTH]->(mon)")
            .WithParams(new { mnVal = newMonth.Value, newMonth = newMonth })
            .Return(mon => mon.Node<Month>());
        var monthNode = qryMonth.Results.Single();


        var newDay = new Day { Name = date.Day.ToString(), Value = date.Day, Date = date.Date }.RunEntityHousekeeping();
        var qryDay = GraphClient.Cypher
            .Merge("(day:Day{ Value: {mnVal}})")
            .OnCreate("day").Set("day = {newDay}")
            .With("day")
            .Start(new { monthNode })
            .CreateUnique("(monthNode)-[:HAS_DAY]->(day)")
            .WithParams(new { mnVal = newDay.Value, newDay = newDay })
            .Return(day => day.Node<Day>());
        var dayNode = qryDay.Results.Single();

Regards Kiran

Upvotes: 0

Views: 3630

Answers (3)

cod3monk3y
cod3monk3y

Reputation: 9843

This code allows you to create calendar graphs on demand upon creation of an event for a specific day. You'll want to modify it to allow events on multiple days, but it seems more like your issue is creating unique paths, right? And you'd probably want to modify this to use parameters in your language of choice.

First I create the root:

CREATE (r:Root {id:'root'})

Then use this reusable MERGE query to successively match or create subgraphs for the calendar. I pass along the root so I can display the graph at the end:

MATCH (r:Root)
MERGE r-[:HAS_CAL]->(cal:Calendar {id:'General'})
WITH r,cal MERGE (cal)-[:HAS_YEAR]->(y:Year {id:2011})
WITH r,y MERGE (y)-[:HAS_MONTH]->(m:Month {id:'Jan'})
WITH r,m MERGE (m)-[:HAS_DAY]->(d:Day {id:1})
CREATE d-[:SCHEDULED_EVENT]->(e:Event {id:'ev3', t:timestamp()})
RETURN r-[*1..5]-()

Creates a graph like this when called multiple times:

enter image description here

Does this help?

Upvotes: 0

jjaderberg
jjaderberg

Reputation: 9952

It seems to me the picture of what you want your graph to look like has the order imposed by relationships, but your code models the order with nodes. If you want that graph, you will need to use relationship types like [2010], [2011] instead of a pattern like [HAS_YEAR]->({value:2010}).

Another way to say the same thing: you are trying to constitute uniqueness for a node intrinsically, by a combination of label and property, e.g. (unique:Day {value:4}). Assuming you have the relevant constraints, this would be database wide uniqueness, so only one fourth-day-of-the-month for all the months to share. What you want is extrinsic local uniqueness, uniqueness established and extended transitively by a hierarchy of relationships. Uniqueness for a node is then not in its internal properties but in its external 'position' or 'order' in relation to its parent. The locally unique pattern (month)-[:locally_unique_rel]->(day) is made unique for a wider scope when the month is made unique, and the month is made unique, not by property and label, but extrinsically by its 'order' or 'position' under its year. Hence the transitivity. I think this is a strength of modeling with graphs, among other things it allows you to continue to partition your structure. If for instance you want to split some of your days into AM and PM or into hours, you can easily do so.

So, in your graph, [HAS_DAY] makes all days equally related to their month, and cannot therefore be used to differentiate between them. You have solved this locally under a month, since the property value differentiates, but since the fourth-day-of-the-month in

(november)-[:HAS_DAY]->(4th)` and `(december)-[:HAS_DAY]->(4th)

are not distinct by property value or label, they are the same node in your graph. Locally, under a month say, unique nodes can be achieved equally with

[11]->()-[4]->(unique1), [11]->()-[5]->(unique2)

and

[HAS_MONTH]->({value:11})-[HAS_DAY]->(unique1 {value:4}), 
[HAS_MONTH]->({value:11})-[HAS_DAY]->(unique2 {value:5})  

The difference is that with the former extrinsic local uniqueness, you have the benefit of transitivity. Since the months are unique in a year, as (november) in [11]->(november) is locally unique, therefore the days of November are also unique in that year - the (fourth) node is distinct between

[11]->(november)-[4]->(fourth)

and

[12]-(december)->[4]->(fourth)

What this amounts to is transferring more of your semantic model to your relationships, leaving the nodes for storing data. The node identifiers in the picture you posted are only pedagogical, replacing them with x,y,z or empty parentheses would perhaps better reveal the structure or scaffolding of the graph.

If you want to keep the relationship types intact, adding an ordering property to each relationship to create a pattern like (november)-[:HAS_DAY {order:4}]->(4th) will also work. This may be less performant for querying, but you may have other concerns that make it worth it.

Upvotes: 0

Tatham Oddie
Tatham Oddie

Reputation: 4290

Nowhere on the documentation page does it say that CREATE UNIQUE has been deprecated.

MERGE is just a new approach that's available to you. It enables some new scenarios (matching based on labels, and ON CREATE and ON MATCH triggers) but also does not cover more complex scenarios (more than a single node).

It sounds like you're already familiar with CREATE UNIQUE. For now, I think you should still be using that.

Upvotes: 2

Related Questions