Cypher Neo4J - CASE Expression with MERGE - merge

I'm trying to implement the logic in Cypher where, based on a particular condition (CASE Statement), I would create some nodes and relationships; the code is as below
MATCH (g:Game)-[:PLAYER]->(u:User)-[r1:AT]->(b1:Block)-[:NEXT]->(b2:Block)
WHERE g.game_id='G222' and u.email_id = 'xyz#example.com' and b1.block_id='16'
SET r1.status='Skipped', r1.enddate=20141225
WITH u, b2,b1, g, r1
SET b1.test = CASE b2.fork
WHEN 'y' THEN
MERGE (u)-[r2:STAGE {startdate:20141225, enddate:'99999999', status:'InProgress'}]->(b2 {fork:'fail'}) RETURN 1
ELSE
MERGE (u)-[r2:STAGE {startdate:20141225, enddate:'99999999', status:'InProgress'}]->(b2) RETURN 2
END
WITH u, g
MATCH (u)-[:TIME]->(h:Time)<-[:TIME]-(g)
SET h.after = 0
SET h.before = h.before + 1
In this query there is a merge statement within the WHEN 'y' THEN, this query throws an error:
Invalid input ']': expected whitespace or a relationship pattern (line 7, column 82)
"MERGE (u)-[r2:STAGE {startdate:20141225, enddate:'99999999', status:'InProgress'}]->(b2 {fork:'fail'}) RETURN 1"
Basically I'm trying to create a relationship based on a property i.e. a MERGE within a CASE statement, I tried different ways to get this working like doing a return so that case when returns some value etc. but nothing worked so far.
What could be the issue with this query?

To do conditional write operations you need to use the FOREACH trick. Using CASE you either return a one element array or a empty one. FOREACH iterates over the CASE expression and therefore conditionally executes the action. If you want an ELSE part as well you need to have a another FOREACH using the inverse condition in the CASE. As an example, instead of
WHEN 'y' THEN
MERGE (u)-[r2:STAGE {startdate:20141225, enddate:'99999999', status:'InProgress'}]->(b2 {fork:'fail'}) RETURN 1
ELSE
MERGE (u)-[r2:STAGE {startdate:20141225, enddate:'99999999', status:'InProgress'}]->(b2) RETURN 2
END
use
FOREACH(ignoreMe IN CASE WHEN 'y' THEN [1] ELSE [] END |
MERGE (u)-[r2:STAGE {startdate:20141225, enddate:'99999999', status:'InProgress'}]->(b2 {fork:'fail'})
)
FOREACH(ignoreMe IN CASE WHEN NOT 'y' THEN [1] ELSE [] END |
MERGE (u)-[r2:STAGE {startdate:20141225, enddate:'99999999', status:'InProgress'}]->(b2)
)
See also Mark's blog post on this.

Fixed the issue as below
WITH u, b2,b1, g, r1, CASE WHEN (b1.fork='y' and b2.fork='success') or (b1.fork='n') or (b1.fork='success') THEN ['ok'] ELSE [] END as array1
FOREACH (el1 in array1 | MERGE (u)-[r2:STAGE {startdate:20141225, enddate:99999999, status:'InProgress'}]->(b2))
i.e. used CASE WHEN to create a dummy array that in a way has dummy elements matching the count of matches and then use FOREACH to iterate through the result.
Again, thanks Stefan for the idea...
Deepesh

The APOC plugin supports Conditional Cypher Execution, which now allows us to avoid the FOREACH workaround.
For example, you can do this:
MATCH (g:Game)-[:PLAYER]->(u:User)-[r1:AT]->(b1:Block)-[:NEXT]->(b2:Block)
WHERE g.game_id='G222' AND u.email_id = 'xyz#example.com' AND b1.block_id='16'
SET r1.status='Skipped', r1.enddate=20141225
WITH u, b2, g
CALL apoc.do.when(
b2.fork = 'y',
"MERGE (u)-[:STAGE {startdate:20141225, enddate:'99999999', status:'InProgress'}]->(b2 {fork:'fail'})",
"MERGE (u)-[:STAGE {startdate:20141225, enddate:'99999999', status:'InProgress'}]->(b2)",
{u: u, b2: b2}) YIELD value
WITH u, g
MATCH (u)-[:TIME]->(h:Time)<-[:TIME]-(g)
SET h.after = 0
SET h.before = h.before + 1

Although this answer does help me, I found the syntax very hard to understand. So that's why I wrote my own answer. Here I read a tsv file and generate multiple types of edges.
LOAD CSV WITH HEADERS FROM 'file:///data.tsv' AS r FIELDTERMINATOR '\t'
WITH r.movie_id as movie_id, r.person_id as person_id, r.category as category
MATCH (p:Person {person_id:person_id})
MATCH (m:Movie {movie_id:movie_id})
FOREACH (_ IN CASE WHEN category='actress' THEN [1] ELSE [] END |
MERGE (p)-[:ACTRESS {}]->(m)
)
FOREACH (_ IN CASE WHEN category='director' THEN [1] ELSE [] END |
MERGE (p)-[:DIRECTOR {}]->(m)
)
FOREACH (_ IN CASE WHEN category='cinematographer' THEN [1] ELSE [] END |
MERGE (p)-[:CINEMATOGRAPHER {}]->(m)
)
FOREACH (_ IN CASE WHEN category='actor' THEN [1] ELSE [] END |
MERGE (p)-[:ACTOR {}]->(m)
)
Here _ is some variable which is simply not used anywhere but a necessity for the syntax of cypher

Related

How can one drop/delete columns from a KDB table in place?

Following the documentation, I tried to do the following:
t:([]a:1 2 3;b:4 5 6;c:`d`e`f) // some input table
`a`b _ t // works: delete NOT in place
(enlist `a) _ t // works: delete NOT in place
t _:`a`b // drop columns in place does not work; how to make it to work?
// 'type
// [0] t _:`a`b
Thank you very much for your help!
You should be able to use
delete a,b from `t
to delete in place (The backtick implies in place).
Alternatively, for more flexibility you could use the functional form;
![`t;();0b;`a`b]
The simplest way to achieve column deletion in place is using qSQL:
t:([]a:1 2 3;b:4 5 6;c:`d`e`f)
delete a,b from `t -- here, the backtick before t makes the change in place.
q)t
c
-
d
e
f
Michael & Kyle have covered the q-SQL options; for completeness, here are a couple of other options using _:
Using _ as in your question, you can re-assign this back to t e.g.
t:`a`b _ t
You can also use . amend with an empty list of indexes i.e. "amend entire", which can be done in-place by passing `t or not in-place by passing just t e.g.
q).[t;();`a`b _] / not in-place
c
-
d
e
f
q).[`t;();`a`b _] / in-place
`t
q)t
c
-
d
e
f

T-SQL Nested Case Statement - Else Continue Nesting Case

In SQL Server, is there a way for nested case statements to ELSE continue the nesting CASE statement?
CASE
WHEN ... then
CASE
WHEN ... THEN
ELSE **[Continue to below WHEN]** END
WHEN ... then
WHEN ... then
ELSE ... END
Wondering if I can keep the code clean by not copy/pasting the proceeding WHEN statements into the nested CASE.
Flatten the nesting. So instead of this:
CASE
WHEN A then
CASE
WHEN B THEN C
WHEN D THEN E
ELSE **[Continue to below WHEN]** END
WHEN F then G
WHEN H then I
ELSE J END
You have this:
CASE
WHEN A AND B THEN C
WHEN A AND D THEN E
WHEN F then G
WHEN H then I
ELSE J END
A CASE expression (not statement: CASE cannot branch among SQL segments, only return a value) will stop at the first match, so by flattening the nesting and using and AND to tie in the nested conditions you will get the desired behavior.

Numbers on telephone to letters. 2=ABC 3=DEF etc. Using switch case but code isnt giving desired results. How would I return the code as asked below

for var=1:length(str)
switch str(var)
case {'A':'C'}
disp('2')
case {'D':'F'}
disp('3')
case {'G':'I'}
disp('4')
case {'J':'L'}
disp('5')
case {'M':'O'}
disp('6')
case {'P' 'R' 'S'}
disp('7')
case {'T':'V'}
disp('8')
case {'W':'Y'}
disp('9')
end
I am using this code in trying to make a statement such as ('1-800-TO-WELLS') become '1-800-86-93557', but the output is "str=1-800-TO-WELLS and 7". Any amount of tips and help is very appreciated. If I haven't provided a valid enough question please leave comments so I can improve.
For strings, the cases in the switch block act like calls to strcmp, so case <caseExpression> is true if any(strcmp(<caseExpression>,<switchExpression>)).
This is important because {'P' 'R' 'S'} and {'P':'S'} do not generate the same output:
>> {'P' 'R' 'S'}
ans =
'P' 'R' 'S'
>> {'P':'S'}
ans =
'PQRS'
The first is a 1-by-3 cell array with individual characters as the elements' contents; the second is a 1-by-1 cell array with a 1-by-4 character array as the element's content. Performing a strcmp on the first one will give true if the <switchExpression> is a letter in the set while the second will only give a true if <switchExpression> is exactly 'PQRS':
>> strcmp({'P' 'R' 'S'},'S')
ans =
0 0 1
>> strcmp({'P':'S'},'S')
ans =
0
>> strcmp({'P':'S'},'P':'S')
ans =
1
So the 7 pops out because its case is the only one that gives true when given a matching character.
As a side note, you may consider using the otherwise statement to echo non-matching characters:
switch str(var)
.
.
.
otherwise
disp(str(var));
end
Troy gives a very clear answer as to why your code isn't working: {'A':'C'} results in a 1-element cell array {'ABC'}, but you want to use a 3-element cell array {'A' 'B' 'C'} since the way the switch compares case expressions will result in a match if one cell element exactly matches a given character.
As an alternative to using a for loop and switch statement, you could also do this using the ismember function and some indexing:
% Character/number map and test string:
map = ['ABCDEFGHIJKLMNOPRSTUVWXY'; '222333444555666777888999'];
str = '1-800-TO-WELLS';
% Convert test string:
[isInMap, index] = ismember(str, map(1, :));
str(isInMap) = map(2, index(isInMap));
And the result:
str =
1-800-86-93557

How to put " <" to this CASE WHEN expression?

In PostgreSQL this is a valid query:
SELECT case 2+2 when 1 then 2 else 3 end
If I put a complex subquery instead of '2+2' it still works well. But how can I change this query if i want to know if the result is smaller than a specific number?
For example this one doesn't work:
SELECT case 2+2 when > 1 then 2 else 3 end
There are two forms of the CASE statement in SQL, described in the PostgreSQL manual here.
One, which you are using, compares a particular expression to a series of values, like a switch statement in C or PHP:
CASE something WHEN 1 THEN 'hello' ELSE 'goodbye' END
The other is a more general set of branching conditions, like an if-elseif-else sequence, or PHP's switch(true). The above can also be written like this:
CASE WHEN something = 1 THEN 'hello' ELSE 'goodbye' END
So to use any comparison other than =, you need the "if-like" version. In your example:
SELECT CASE WHEN 2+2 > 1 THEN 2 ELSE 3 END
select case when 2+2 > 1 then this else that end

SQL Sever: in...case...in WHERE clause

I need to code up a query for something like this:
Select [something]
Where
condition in
case
when (if another_condition = A and 3rd Condition = B) then (C,D)
when (if another_condition = N and 3rd Condition = E) then (F,G)
else (J,K)
end
essentially, what I want is if A and B are met, condition could be set to either C or D, if N or E are met, then condition could be set to F or G, else condition set to J or K.
However, when I run this, I kept getting
Incorrect syntax near the keyword 'Case'.
Please help! Thanks!
Maybe this:
Where (Another_Condition = 'A' And Third_Condition = 'B' And Condition in ('C','D'))
Or
(Another_Condition = 'N' and Third_Condition = 'E' And Condition in ('F','G'))
Or
Condition In ('J','K')
Be very careful about mixing and's and or's in a where clause. Parenthesis are important.
How about this - the UNION subquery will give you the full result set within the subquery. Then you can say 'WHERE condition IN ' (subquery). Like this:
SELECT [something]
WHERE
condition IN
(SELECT CASE WHEN (another_condition = A AND 3rd Condition = B) THEN C
WHEN (another_condition = N AND 3rd Condition = E) THEN F
ELSE J
END AS Value
UNION
SELECT CASE WHEN (another_condition = A AND 3rd Condition = B) THEN D
WHEN (another_condition = N AND 3rd Condition = E) THEN G
ELSE K
END AS Value
)
I'd probably go with G Mastro's approach of expanding the query as a Boolean expression. While the nested query approach will work, the intent of the code is less obvious IMO.
Having said that, if there are a lot of cases in your CASE statement, you may want to consider reshaping your data, because no matter how you write the query, it boils down to a big Boolean expression.