{"action":"created","issue":{"url":"https://api.github.com/repos/neo4j/neo4j/issues/11778","repository_url":"https://api.github.com/repos/neo4j/neo4j","labels_url":"https://api.github.com/repos/neo4j/neo4j/issues/11778/labels{/name}","comments_url":"https://api.github.com/repos/neo4j/neo4j/issues/11778/comments","events_url":"https://api.github.com/repos/neo4j/neo4j/issues/11778/events","html_url":"https://github.com/neo4j/neo4j/issues/11778","id":323231826,"number":11778,"title":"MERGE does not match on unique node","user":{"login":"mzoll","id":13104819,"avatar_url":"https://avatars1.githubusercontent.com/u/13104819?v=4","gravatar_id":"","url":"https://api.github.com/users/mzoll","html_url":"https://github.com/mzoll","followers_url":"https://api.github.com/users/mzoll/followers","following_url":"https://api.github.com/users/mzoll/following{/other_user}","gists_url":"https://api.github.com/users/mzoll/gists{/gist_id}","starred_url":"https://api.github.com/users/mzoll/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mzoll/subscriptions","organizations_url":"https://api.github.com/users/mzoll/orgs","repos_url":"https://api.github.com/users/mzoll/repos","events_url":"https://api.github.com/users/mzoll/events{/privacy}","received_events_url":"https://api.github.com/users/mzoll/received_events","type":"User","site_admin":false},"labels":[{"id":647067906,"url":"https://api.github.com/repos/neo4j/neo4j/labels/team-cypher","name":"team-cypher","color":"cccccc","default":false}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":2,"created_at":"2018-05-15T14:01:46Z","updated_at":"2018-05-21T09:09:21Z","closed_at":null,"author_association":"NONE","body":"Hej, I am quite new to Neo4j and Cypher, but I think I stumbled about a bug in the interpretation of my Cypher statement, which kind of does not behave as anticipated or documented.\r\n\r\nIssue\r\n-----\r\nWith the following statement i create a hierarchical structure of `Users-[:OPEN]->Sessions-[:GEN]->Clicks-[:ON]->Pages`, of Web-clicks:\r\n\r\n```cypher\r\nLOAD CSV WITH HEADERS FROM \"file:///clicks.csv\" AS row\r\n \r\n //--- merge User\r\nMERGE (u:User {userkey: toString(row.UserKey) })\r\nON CREATE SET u.created = row.LogTime\r\n\r\n//--- merge session\r\nWITH u, row\r\nMERGE (u)-[:OPEN]->(s:Session {sessionkey: row.SessionKey, userkey: row.UserKey})<-[:REF]-(r:Referer)\r\nON CREATE SET s.created = row.LogTime \r\n\r\n//--- create click\r\nWITH s, row\r\nCREATE (s)-[:GEN]->(c:Click {requestid: row.RequestId,\r\n  url: row.URL,\r\n  time: row.LogTime,\r\n  userkey: row.UserKey,\r\n  sessionkey: row.SessionKey})\r\n\r\n//--- merge page  \r\nWITH c, row\r\nMERGE (c)-[:ON]->(p:Page { domainpath: split(split(split(row.URL, '://')[1], '#')[0], '?')[0] } )\r\n````\r\n\r\nHere `Page` should be a trunkated page-URL, where protocol and parameters are stripped away (i.e. 'http://www.abc.com/aaa?b=123#456' is stripped to 'www.abc.com/aaa'). Also explicitly many Clicks should be able to point to the same `Page`; so `Page` can be seen as unique nodes only different by their property domain-path, which we can express with a Constraint\r\n\r\n```cypher\r\nCREATE CONSTRAINT ON (p:Page) ASSERT (p.domainpath) IS NODE KEY;\r\n```\r\n\r\nNow executing the above statement will fail if my dataset, on any click which repeats an domain-path which has been used in a previus clikc. The error being:\r\n\"Neo.ClientError.Schema.ConstraintValidationFailed: Node(737) already exists with label `Page` and property `domainpath` = 'www.abc.com/aaa'\"\r\n\r\nTo my understanding of the way `MERGE` works, which is \"use node(s) if match by label and properties exists and so complete the path, else create\", this should not happen!\r\n\r\nWorkaround\r\n--------------\r\nTo verify the issue, one can substitute the `//---merge page` section by\r\n\r\n```cypher\r\nWITH c, row\r\nMERGE (p:Page { domainpath: split(split(split(row.URL, '://')[1], '#')[0], '?')[0] } )\r\nWITH c, p\r\nCREATE (p)<-[:ON]-(c)\r\n```\r\n\r\nwhich correctly executes.\r\n\r\nVersions \r\n----------\r\nNeo4j: Version: | 3.3.5 Edition: | Enterprise\r\nOS: Windows 10 Professional\r\nInterface: Neo4j Browser\r\n\r\n\r\n"},"comment":{"url":"https://api.github.com/repos/neo4j/neo4j/issues/comments/390598061","html_url":"https://github.com/neo4j/neo4j/issues/11778#issuecomment-390598061","issue_url":"https://api.github.com/repos/neo4j/neo4j/issues/11778","id":390598061,"user":{"login":"InverseFalcon","id":8800736,"avatar_url":"https://avatars2.githubusercontent.com/u/8800736?v=4","gravatar_id":"","url":"https://api.github.com/users/InverseFalcon","html_url":"https://github.com/InverseFalcon","followers_url":"https://api.github.com/users/InverseFalcon/followers","following_url":"https://api.github.com/users/InverseFalcon/following{/other_user}","gists_url":"https://api.github.com/users/InverseFalcon/gists{/gist_id}","starred_url":"https://api.github.com/users/InverseFalcon/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/InverseFalcon/subscriptions","organizations_url":"https://api.github.com/users/InverseFalcon/orgs","repos_url":"https://api.github.com/users/InverseFalcon/repos","events_url":"https://api.github.com/users/InverseFalcon/events{/privacy}","received_events_url":"https://api.github.com/users/InverseFalcon/received_events","type":"User","site_admin":false},"created_at":"2018-05-21T09:09:21Z","updated_at":"2018-05-21T09:09:21Z","author_association":"NONE","body":"@mzoll No, it would not. There needs to be a way to differentiate when the user wants a new node merged in vs reusing of an existing node.\r\n\r\nConsider a graph of `:Student` nodes that can have `:ReportCard` nodes for a given term (in this case we'll model this with a `term` String property on the `:ReportCard` node).\r\n\r\nSay that we know that all `:Student` nodes are in the graph, and we're in the process of adding grades to a student's report card for the current term, where the grades for classes are modeled as properties on a `:ReportCard` node, but as there is no set ordering as to when teachers add these grades, we cannot be sure that the `:ReportCard` node for the given term for the given student already exists, so we need to MERGE in this node (where it will match to it if it exists, and otherwise create it), and then add the relevant class grade property.\r\n\r\nOur query would look something like this:\r\n\r\n```\r\nMATCH (s:Student {id:12345})\r\nMERGE (s)-[:HAS_REPORT_CARD]->(r:ReportCard {term:6789})\r\nSET ... //however grades are set\r\n```\r\n\r\nThis works because if there is no such `:ReportCard` linked to the given student like this, it will create it, not attempt to find that node in the graph.\r\n\r\nHere's a query that would behave the way you are expecting the MERGE to behave. That is, under the hood it would behave the same way as this query:\r\n\r\n```\r\nMATCH (s:Student {id:12345})\r\nMERGE (r:ReportCard {term:6789})\r\nMERGE (s)-[:HAS_REPORT_CARD]->(r)\r\nSET ... //however grades are set\r\n```\r\n\r\nThe problem with this behavior is that we no longer have a way of merging in a single unique node per student. Instead it would find all `:ReportCard` nodes in the graph for the given term and link them. \r\n\r\nSo on the first run, no such `:ReportCard` node would exist, so it would be created. So first run, for the first student, fine.\r\n\r\nBut for each subsequent student, it would be auto-completing to the single :ReportCard node in the graph for the given term and use that, and MERGE the relationship. The final result would be that every student in the school would be pointing to the single one `:ReportCard` node in the graph, and it would be a huge mess as all professors would be attempting to add grades to this one node, overwriting values, and no student would have their own personal `:ReportCard` node for the term, as there would be no possible way with Cypher to do such a thing.\r\n\r\nThis is why the behavior is the way it is currently. While it may not be immediately intuitive to you, it provides flexibility in how we add elements into the graph, and what constrains the uniqueness of the node (whether it's globally unique in the graph, or unique with respect to the nodes it is connected to).\r\n\r\nThe link I provided earlier has other such examples that are worth rereading."}}
{"actor":{"display_login":"InverseFalcon"}}