ternary operator/default value in neo4j cypher - neo4j

I need to implement something of a ternary operator that can help me return some default values from cypher query itself.
Scenario is -
if an employee's city is Delhi, return 5 else return 10
Something like a ternary operator.
start employee = node(5)
return employee.city == 'DELHI' ? 5 : 10 as val;
I tried things like
start employee = node(5)
return coalesce (employee.city == 'DELHI', 5)
but no luck.
Is there a way to implement such a scenario in neo4j be it Cypher or Traversal.

Unfortunately it is not supported out of the box but here is a hack to do it, using filter, head and collection literals.
The idea is to have a two element list and a filter expression that becomes true for the first element for your "true-branch" and alternatively true for the second element in the list whic represents the value of your false-branch.
see this console example: http://console.neo4j.org/r/6tig7g
start n=node(5) return head(filter( a in [5,10] : n.city = 'DELHI' OR a = 10))
so generally:
head(filter( a in [true-result,false-result] : CONDITION OR a = false-result))

I know this is a really old question, but since Google landed me here and I have an answer (Cypher in Neo4J 3.5+):
MATCH (employee:Employee)
RETURN
CASE employee.city WHEN "DELHI" THEN 5 ELSE 10 END AS val

Related

Is it possible in Neo4j in the RETURN clause to aggregate boolean values by true or false?

Ok, I'm trying to figure out if there is a query that can give me something similar to the following:
MATCH (practiceTest:PracticeTest)
RETURN count(distinct practiceTest.passed===true), count(distinct practiceTest.passed===false)
I'm aware that this query does not work, but I'm curious if there is a way to do something similar to this, without having to write the following:
MATCH (practiceTest:PracticeTest)
WHERE practiceTest.passed = true
RETURN count(distinct practiceTest);
MATCH (practiceTest:PracticeTest)
WHERE practiceTest.passed = false
RETURN count(distinct practiceTest);
Thank you!
another approach
MATCH (p:PracticeTest)
WITH COLLECT(p.passed) AS results
RETURN SIZE([result IN results WHERE result=true]) AS num_passed,
SIZE([result IN results WHERE result=false]) AS num_failed
You could count the result of a CASE statement when a practiceTest is true and then count again when the reulst is false.
MATCH (p:PracticeTest)
RETURN count(CASE WHEN p.passed = true THEN 1 END) AS num_true, count(CASE WHEN p.passed = false THEN 1 END) AS num_false
The following query is pretty efficient in time and space. It only makes one aggregating function invocation, creates only one list, and only makes one pass through that list:
MATCH (p:PracticeTest)
WITH COLLECT(p.passed) AS data
WITH data, REDUCE(ps = 0, x IN data | CASE WHEN x THEN ps+1 ELSE ps END) AS passes
RETURN passes, SIZE(data)-passes AS fails
NOTE: This query does not count p nodes that have no passed property. Also, the passed value must be a boolean.
Returning counts of boolean values
If all you want to do is return the count of how many passed or failed, you could use the following:
MATCH (practiceTest:PracticeTest)
RETURN practiceTest.passed AS PassedTest,
count(practiceTest) AS Frequency
which will return something like
╒════════════╤═══════════╕
│"PassedTest"│"Frequency"│
╞════════════╪═══════════╡
│true │17682 │
├────────────┼───────────┤
│false │27152 │
└────────────┴───────────┘
Converting boolean to 1/0
If you wanted to convert True/False into 1/0 as some of the replies suggested, you can convert the boolean to an integer.
In Neo4j v4.3+, the toInteger() function converts a boolean to 1/0. If you're on an earlier version, you can use apoc.convert.toInteger(), but be aware that function is deprecated and will be removed in 5.0.

How to add to an existing value in a map in Cypher?

I want to replace the value of the 'Amount' key in a map (literal) with the sum of the existing 'Amount' value plus the new 'Amount' value such where both the 'type' and 'Price' match. The structure I have so far is:
WITH [{type:1, Orders:[{Price:10,Amount:100},{Price:11,Amount:200},{Price:12,Amount:300}]},
{type:2, Orders:[{Price:10,Amount:100},{Price:11,Amount:200},{Price:12,Amount:300}]},
{type:3, Orders:[{Price:10,Amount:100},{Price:11,Amount:200},{Price:12,Amount:300}]}] as ExistingOrders,
{type:2, Order:{Price:11,Amount:50}} as NewOrder
(I'm trying to get it to:)
RETURN [{type:1, Orders:[{Price:10,Amount:100},{Price:11,Amount:200},{Price:12,Amount:300}]},
{type:2, Orders:[{Price:10,Amount:100},{Price:11,Amount:250},{Price:12,Amount:300}]},
{type:3, Orders:[{Price:10,Amount:100},{Price:11,Amount:200},{Price:12,Amount:300}]}] as CombinedOrders
If there is no existing NewOrder.type and NewOrder.Price then it should obviously insert the new record rather than add it together.
Sorry, this is possibly really straight forward, but I'm not very good at this yet.
thanks
Edit:
I should add, that I have been able to get this working for a simpler map structure as such:
WITH [{type:1, Amount:100},{type:2, Amount:200},{type:3, Amount:300}] as ExistingOrders,
{type:2, Amount:50} as NewValue
RETURN reduce(map=filter(p in ExistingOrders where not p.type=NewValue.type),x in [(filter(p2 in ExistingOrders where p2.type=NewValue.type)[0])]|CASE x WHEN null THEN NewValue ELSE {type:x.type,Amount:x.Amount+NewValue.Amount} END+map) as CombinedOrders
But I'm struggling I think because of the Orders[array] in my first example.
I believe you are just trying to update the value of the appropriate Amount in ExistingOrders.
The following query is legal Cypher, and should normally work:
WITH ExistingOrders, NewOrder, [x IN ExistingOrders WHERE x.type = NewOrder.type | x.Orders] AS eo
FOREACH (y IN eo |
SET y.Amount = y.Amount + CASE WHEN y.Price = NewOrder.Order.Price THEN NewOrder.Order.Amount ELSE 0 END
)
However, the above query produces a (somewhat) funny ThisShouldNotHappenError error with the message:
Developer: Stefan claims that: This should be a node or a relationship
What the message is trying to say (in obtuse fashion) is that you are not using the neo4j DB in the right way. Your properties are way too complicated, and should be separated out into nodes and relationships.
So, I will a proposed data model that does just that. Here is how you can create nodes and relationships that represent the same data as ExistingOrders:
CREATE (t1:Type {id:1}), (t2:Type {id:2}), (t3:Type {id:3}),
(t1)-[:HAS_ORDER]->(:Order {Price:10,Amount:100}),
(t1)-[:HAS_ORDER]->(:Order {Price:11,Amount:200}),
(t1)-[:HAS_ORDER]->(:Order {Price:12,Amount:300}),
(t2)-[:HAS_ORDER]->(:Order {Price:10,Amount:100}),
(t2)-[:HAS_ORDER]->(:Order {Price:11,Amount:200}),
(t2)-[:HAS_ORDER]->(:Order {Price:12,Amount:300}),
(t3)-[:HAS_ORDER]->(:Order {Price:10,Amount:100}),
(t3)-[:HAS_ORDER]->(:Order {Price:11,Amount:200}),
(t3)-[:HAS_ORDER]->(:Order {Price:12,Amount:300});
And here is a query that will update the correct Amount:
WITH {type:2, Order:{Price:11,Amount:50}} as NewOrder
MATCH (t:Type)-[:HAS_ORDER]->(o:Order)
WHERE t.id = NewOrder.type AND o.Price = NewOrder.Order.Price
SET o.Amount = o.Amount + NewOrder.Order.Amount
RETURN t.id, o.Price, o.Amount;
There's two parts to your question - one with a simple answer, and a second part that doesn't make sense. Let me take the simple one first!
As far as I can tell, it seems you're asking how to concatenate a new map on to a collection of maps. So, how to add a new item in an array. Just use + like this simple example:
return [{item:1}, {item:2}] + [{item:3}];
Note that the single item we're adding at the end isn't a map, but a collection with only one item.
So for your query:
RETURN [
{type:1, Orders:[{Price:10,Amount:100},
{Price:11,Amount:200},
{Price:12,Amount:300}]},
{type:2, Orders:[{Price:10,Amount:100},
{Price:11,Amount:**250**},
{Price:12,Amount:300}]}]
+
[{type:3, Orders:[{Price:10,Amount:100},
{Price:11,Amount:200},{Price:12,Amount:300}]}]
as **CombinedOrders**
Should do the trick.
Or you could maybe do it a bit cleaner, like this:
WITH [{type:1, Orders:[{Price:10,Amount:100},{Price:11,Amount:200},{Price:12,Amount:300}]},
{type:2, Orders:[{Price:10,Amount:100},{Price:11,Amount:200},{Price:12,Amount:300}]},
{type:3, Orders:[{Price:10,Amount:100},{Price:11,Amount:200},{Price:12,Amount:300}]}] as ExistingOrders,
{type:2, Order:{Price:11,Amount:50}} as NewOrder
RETURN ExistingOrders + [NewOrder];
OK now for the part that doesn't make sense. In your example, it looks like you want to modify the map inside of the collection. But you have two {type:2} maps in there, and you're looking to merge them into something with one resulting {type:3} map in the output that you're asking for. If you need to deconflict map entries and change what the map entry ought to be, it might be that cypher isn't your best choice for that kind of query.
I figured it out:
WITH [{type:1, Orders:[{Price:10,Amount:100},{Price:11,Amount:200},Price:12,Amount:300}]},{type:2, Orders:[{Price:10,Amount:100},{Price:11,Amount:200},{Price:12,Amount:300}]},{type:3, Orders:[{Price:10,Amount:100},{Price:11,Amount:200},{Price:12,Amount:300}]}] as ExistingOrders,{type:2, Orders:[{Price:11,Amount:50}]} as NewOrder
RETURN
reduce(map=filter(p in ExistingOrders where not p.type=NewOrder.type),
x in [(filter(p2 in ExistingOrders where p2.type=NewOrder.type)[0])]|
CASE x
WHEN null THEN NewOrder
ELSE {type:x.type, Orders:[
reduce(map2=filter(p3 in x.Orders where not (p3.Price=(NewOrder.Orders[0]).Price)),
x2 in [filter(p4 in x.Orders where p4.Price=(NewOrder.Orders[0]).Price)[0]]|
CASE x2
WHEN null THEN NewOrder.Orders[0]
ELSE {Price:x2.Price, Amount:x2.Amount+(NewOrder.Orders[0]).Amount}
END+map2 )]} END+map) as CombinedOrders
...using nested Reduce functions.
So, to start with it combines a list of orders without matching type, with a list of those orders (actually, just one) with a matching type. For those latter ExistingOrders (with type that matches the NewOrder) it does a similar thing with Price in the nested reduce function and combines non-matching Prices with matching Prices, adding the Amount in the latter case.

What are collections in Cypher / Neo4J?

I do not really understand what is the difference of collections from other type of output in Cypher. Can somebody explain this to me, please?
For instance the query
match (c:Context) where c.name="health" or c.name="opinion" return collect(c);
returns 1 row, while the query
match (c:Context) where c.name="health" or c.name="opinion" return c;
returns 6 rows (I have 6 nodes in my database that match the criteria).
This seems to be the only difference.
So then, is it just about the way the data is represented? Or is there some sort of advantage to use collections?
Thank you for your help!
Collections return the entities in an array, instead of an individual "row" for each result.
The benefit of this is, for example: I want to get all addresses associated to a contact.
Match (c:Contact)-[:AddressRelation]->(a:Address)
return c,collect(a)
This would return a group of addresses for each contact, whereas without collect, it would return duplicate contact items (one for each address they have)
Collect returns something like this:
row = { name:"fred" } , [{address1},{address2},...]
Without collect:
row = { name:"fred"} , {address1}
row = { name:"fred"} , {address2}
...etc.
There are a lot of other things you can do, like return a property in an array, loop through each node in a foreach loop, etc.

Stop erlang qlc query at first matching entry

How do I write a qlc query that traverses table until it finds the first matching entry? For instance, this query returns all entries in the table that match criteria:
qlc:q([E#stuff.data || E <- mnesia:table(stuff), E#stuff.type == 123]).
How to modify this or the qlc:e call to stop and return only the first matching entry? The motivation for this is performance - I'm not interested in all entries, just want to take a look at how entries of specific type look like.
You need to use a qlc cursor and qlc:next_answers/2, take a look at the example in qlc:cursor/1.
It should look something like
QH=qlc:q([E#stuff.data || E <- mnesia:table(stuff), E#stuff.type == 123]).
QC = qlc:cursor(QH).
Result=qlc:next_answers(QC, 1). % Only return 1 answer
qlc:delete_cursor(QC).

Lambda expression in WHERE clause not working as expected

I'm new to Neo4j and trying to do a simple Cypher query using a lambda expression in the where clause but for some reason I can't seem to figure out why this isn't working.
Looks like:
class HealthNode {
public string Name{get;set;}
//Other Stuff
}
string Name = "Foobar";
var query = client
.Cypher
.Start(new { n = Neo4jClient.Cypher.All.Nodes })
.Where((HealthNode n) => n.Name == Name)
.Return<HealthNode>("n");
If I dump the Text and Parameters I'm getting:
START n=node(*)
WHERE (n.Name! = {p0})
RETURN n
//P0 Foobar
When I execute this, I of course get:
Cypher does not support != for inequality comparisons. Use <> instead
Why in the world is an extra Exclamation point to the name of the variable?
The ! means that the result will be false if the property doesn't exist. So, if you have more than one type in the graph, and that other type doesn't have a 'Name' property, neo4j won't bother matching.
See Neo4J Documentation for more info.
As to getting the != warning, are you changing the query at all when you paste it? Reformatting it? As I get the same warning if I do:
WHERE (n.Name != {p0})
but don't get any warning, and a successful completion if I use:
WHERE (n.Name! = {p0})
I think I found the cause of the problem here:
There was a change made to the 2.0 parser that implements NULL IF by default (instead of returning an error on a missing property) and removes the ! and ? operators since they no longer do anything.
neo4j pull request 1014
I suspect this will break a lot of things and not just Neo4J Client.
Fixed in Neo4jClient 1.0.0.625 and above, when talking to Neo2j 2.0.

Resources