Execute multiple query based on multiple condition in cypher / apoc - neo4j

In cypher or APOC, Is there a way to execute multiple query based on multiple condition.
I need something similar this APOC
CALL apoc.do.case([condition, query, condition, query, …​], elseQuery:'',
params:{}) yield value
But here as soon as we met 1st true condition it skip all further condition and query. I want to execute all those query where my condition is true.
In simple word , I am looking for something similar to java case statement (without break; between case)
Update
I ran following query to use multiple apoc.do.when but it seems only my second apoc.do.when is not executing:
CREATE (y:EVENT { _id: 1, localComponentID:'l1', externalComponentID:'e1'}) with y
call apoc.do.when(exists(y.localComponentID),"MATCH(e:EVENT) where
e.localComponentID = lcl and e._id <> y._id with y,e limit 1 create (y)-
[r:LOCAL_LINK]->(e)",'',{y:y,lcl:y.localComponentID}) YIELD value WITH value AS ignored, y
call apoc.do.when(exists(y.externalComponentID),"MATCH(e:EVENT) where
e.externalComponentID = ext and e._id <> y._id with y,e limit 1 create (y)-
[r:EXTERNAL_LINK]->(e)",'',{y:y, ext:y.externalComponentID}) YIELD value
WITH value AS ignored return ignored
If I run above query two time with _id = 1 in first run and _id=2 in second run, I expect two EVENT connected with LOCAL_LINK and EXTERNAL_LINK. But I am only getting LOCAL_LINK between them not the EXTERNAL_LINK. I am not sure what I am doing wrong.
Note : I am using limit 1 because In case of multiple match I just want to create LINK with one node.
Update 2
Got it working , In my sample query I was that not returning y from first apoc.do.when
Here is the updated query which works:
CREATE (y:EVENT { _id: 1, localComponentID:'l1', externalComponentID:'e1'}) with y
call apoc.do.when(exists(y.localComponentID),"MATCH(e:EVENT) where
e.localComponentID = lcl and e._id <> y._id with y,e limit 1
create (y)-[r:LOCAL_LINK]->(e) RETURN y",'',
{y:y,lcl:y.localComponentID}) YIELD value WITH value AS ignored, y
call apoc.do.when(exists(y.externalComponentID),"MATCH(e:EVENT) where
e.externalComponentID = ext and e._id <> y._id with y,e limit 1
create (y)-[r:EXTERNAL_LINK]->(e)",'',{y:y, ext:y.externalComponentID})
YIELD value
WITH value AS ignored return ignored

You can just call the APOC function apoc.do.when for each condition/query pair (with an empty string as the else argument).
For example:
CALL apoc.do.when(<condition1>, <query1>, '', {}) YIELD value
WITH value AS ignored
CALL apoc.do.when(<condition2>, <query2>, '', {}) YIELD value
WITH value AS ignored
.
.
.
Since your comments indicate your queries are all write-only, the above example assigns the return values to an ignored variable (that you can ignore).

Related

Neo4J Cypher - Find integer attributes

Background
I have nodes which have been recording a specific attribute as LONG and I want them to be recorded as INT - I have amended my system's input code to now record them as INT, however I am looking to check Neo4J for anomalies.
The Data
I now have a mix of data, where some have been created as "int" some as STR and some are even null (which is ok in my data-structure)
CREATE (n:logs{value: "example", records: "30"})
, (n:logs{value: "example", records: 30})
, (n:logs{value: "example", records: null})
RETURN n
The Problem
I tried to verify if the system is now writing the "records" attribute as INT rather than string, so I tried the following query;
WITH datetime("2021-06-18T06:00:00").epochMillis AS threshold
MATCH(n:logs)
WHERE n.records <> tointeger(n.records)
COUNT(n)
This returns 1 - my NULL record, for some reason. But NOT the STR, as I would have expected.
I then try the following (18th June being the date of the update to my input system);
WITH datetime("2021-06-18T06:00:00").epochMillis AS threshold
MATCH(n:logs)
WHERE n.records = tointeger(n.records)
COUNT(n)
And this returns 0 - which again, I am not sure why.
So my question is, what am I doing wrong and how can I get to where I need to go. Which is ultimately to;
Check the system is saving as INT going forwards
Change all the pre-existing records from STR to INT.
I assume for #2 is will be something like
MATCH(n:logs)
WHERE n.records <> tointeger(n.records)
SET n.records = tointeger(n.records)
Additional Info
Community edition
Version X.x.x
No addins
Your query to update to integer is correct.
To clarify your assumptions, please run below queries in your neo4j desktop or browser and you will see what is going on.
1. RETURN toInteger("2021-06-18T06:00:00")
2. RETURN toInteger(null)
Both of them will return NULL. It means your claim below is NOT true
This returns 1 - my NULL record, for some reason. But NOT the
STR, as I would have expected.
You did return 1 but is it the string value of records ('30') rather than the NULL value.
Then when you run your query below
WHERE n.records = tointeger(n.records)
And this returns 0 - which again, I am not sure why.
It is because the integer value of a string date type is also NULL. Thus it will return no matching record.
If you want to count all nodes with non-integer attribute, including nulls then you can run below query.
MATCH(n:logs)
WHERE n.records <> tointeger(n.records) OR n.records is null
RETURN count(distinct n) as cnt
Result: 2
Remember, if n.records = "2021-06-18T06:00:00" then you need to convert it to an epoch value (the integer value of time in seconds or millis since 1/1/1970). If not, then tointeger(n.records) is null and will not match in your query.
Do something like below:
MATCH (n:logs)
WHERE tointeger(n.records) is null
SET n.records = datetime(n.records).epochMillis
RETURN n
Then do your original query (this is correct!), to clean up other non-integer values.
MATCH(n:logs)
WHERE n.records <> tointeger(n.records)
SET n.records = tointeger(n.records)

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.

ANY function returns nothing when the database is empty

I have a cypher query which is looking to see if an account with a certain email address already exists in the database. To do this I use the built in ANY function:
MATCH (a:Account)
RETURN ANY(x IN a.email WHERE x='test#test.test') AS exists
since this is a development database the test records are sometimes deleted when a significant change to the data structure is made resulting in the database being empty. The problem I'm having is that the query shown above returns neither true or false instead it just returns nothing when the database is empty. This is problematic since this query is called by a php script using the graph-aware library and when I run the getRecord() it returns an error because the function returned nothing.
Is there a way to make the ANY function return false if the database is empty.
1) The cypher-query can be simplified:
MATCH (a:Account) WHERE 'test#test.test' IN a.email
RETURN COUNT(a) > 0 AS exists
2) Use the driver features:
$result = $client->run('
MATCH (a:Account) WHERE \'test#test.test\' IN a.email
RETURN COUNT(a) > 0 AS exists
')
var_dump($result->size() && $result->getRecord())
// or .
var_dump($result->firstRecordOrDefault(false))

Neo4j APOC remove all triggers

I'd like to implement Cypher query and using APOC functions remove all of the existing triggers:
I'm trying the following query:
CALL apoc.trigger.list() yield name
CALL apoc.trigger.remove(name) yield name, installed
but it fails with the following error:
Neo.ClientError.Statement.SyntaxError: Query cannot conclude with CALL
(must be RETURN or an update clause) (line 1, column 37 (offset: 36))
"CALL apoc.trigger.list() yield name CALL apoc.trigger.remove(name)
yield name, installed" ^
How to properly implement this query ?
As the error says, a query cannot end with a CALL (unless the CALL is the only statement in the query). It needs either a write operation (MERGE, CREATE, SET, REMOVE, DELETE) or a return.
You can add RETURN name, installed at the end, if you want to return the values yielded by the call. Otherwise, if you really don't care about what is returned, RETURN DISTINCT true ought to do the trick.
Oh, and you may want to alias name in one of your YIELDs or the other, as you may get an error of a variable name conflict.

SET in combination with CASE statement in cypher

I am tryin to set two different relationship properties to a count, with a case construct depending on the value of another relationship property. There is a console at http://console.neo4j.org/?id=rt1ld5
the cnt column contains the number of times r.value occurs. The two first rows of the initial query in the console indicate that the term "Car" is linked to 1 document that is considered relevant, and to two documents that are considered not relevant.
I want to SET a property on the [:INTEREST] relation between (user) and (term) with two properties, indicating how many times an interest is linked to a document that is considered relevant or not. So for (John)-[r:INTEREST]->(Car) I want r.poscnt=1 and r.negcnt=2
I.m struggling with the CASE construct. I tried various ways, this was the closest I got.
MATCH (u:user)-[int:INTEREST]->(t:term)<-[:ISABOUT]-(d:doc)<- [r:RELEVANCE]-(u)
WITH int, t.name, r.value, count(*) AS cnt
CASE
WHEN r.value=1 THEN SET int.poscnt=cnt
WHEN r.value=-1 THEN SET int.negcnt=cnt
END
But it's returning an error
Error: Invalid input 'A': expected 'r/R' (line 3, column 2)
"CASE"
^
This did it! Also see console at http://console.neo4j.org/?id=rq2i7j
MATCH (u:user)-[int:INTEREST]->(t:term)<-[:ISABOUT]-(d:doc)<-[r:RELEVANCE]-(u)
WITH int, t,
SUM(CASE WHEN r.value= 1 THEN 1 ELSE 0 END ) AS poscnt,
SUM(CASE WHEN r.value= -1 THEN 1 ELSE 0 END ) AS negcnt
SET int.pos=poscnt,int.neg=negcnt
RETURN t.name,int.pos,int.neg
Is it important for you to keep positive and negative count separate? It seems you could have a score property summing positive and negative values.
MATCH (u:user)-[int:INTEREST]->()<-[:ISABOUT]-()<-[r:RELEVANCE]-(u)
SET int.score = SUM(r.value)
RETURN t.name, int.score
You already seem to have found a working solution but I'll add a note about CASE as I understand it. While CASE provides branching, I think it's correct to say that it is an expression and not a statement. It resembles a ternary operator more than a conditional statement.
As the expression
a > b ? x : y;
is resolved to a value, either x or y, that can be used in a statement, so also
CASE WHEN a > b THEN x ELSE y END
resolves to a value. You can then assign this value
result = CASE WHEN a > b THEN x ELSE y END
Your original query used the CASE expression like a conditional statement
CASE WHEN a > b THEN result = x ELSE result = y END
which resembles if-else
if a > b { result = x; } else { result = y; }
Someone may want to correct the terminology, the point is that in your working query you correctly let CASE resolve to a value to be used by SUM rather than put a conditional assignment inside CASE.

Resources