I am trying to create a list of data from a data range in Google Sheets. I thought of using the query function, but with that, you can't seem to use regular logical statements.
I know that this snippet of code is wrong and doesn't work, but hopefully it makes it clear what I'm trying to do.
=Query(E2:E103, OR(AND(D2:D103=A$2,G2:G103=A$5),AND(D2:D103=A$3,G2:G103=A$5),D2:D103=A$1))
In this code, A$2, A$5, A$3, and A$1 are all just string variables in the corresponding cells that tell the logic statement what to compare.
If there is another way to write this or a different function that accomplishes what I would like to do, it would be greatly appreciated if you could share it with me.
Basic case
The basic syntax for building a query logic:
"where F = '" & A1 & "'"
assumes A1 is a string, values in column F are strings
adds single quotes, makes the resulting query string: where F = 'sample text'
Logic
Your case looks like this:
where (A) or (B) or C =>
where (A1 and A2) or (B1 and B2) or C =>
where (D = 'text1' and G = 'text2') or (D = 'text3' and G = 'text4') or D = 'text5'
I am trying to create an array of values that will be assigned based on the outcome of a case test. This test will be inside a query that I already know works with a preset value in the query.
The query I am trying to embed in the case test is something like this:
WITH SPLIT (('07/28/2015'), '/' AS cd
MATCH (nodeA: NodeTypeA)-(r:ARelation)->(nodeB: NodeTypeB)
WITH cd, SPLIT (nodeA.ADate, '/') AS dd, nodeA, nodeB, r
WHERE
(TOINT(cd[2])> TOINT(dd[2])) OR (TOINT(cd[2]= TOINT(dd[2]) AND ((TOINT(cd[0])> TOINT(dd[0])) OR (TOINT(cd[0])= TOINT(dd[0]) AND (TOINT(cd[1])>= TOINT(dd[1])))))
RETURN nodeA, nodeB, r
I want to replace the current date with whatever date will be 6 months from the current date, and I came up with something like this, though I am not sure where I would put it in my query or if it would even work (do I initialize the new variable for instance somehow?):
WHEN ((TOINT(cd[0])> 6))
THEN
TOINT(fd[2])=TOINT(cd[2])+1, TOINT(fd[0])=TOINT(cd[0])-6, TOINT(fd[1])=TOINT(cd[1])
ELSE
TOINT(fd[2])=TOINT(cd[2]), TOINT(fd[0])=TOINT(cd[0])+6, TOINT(fd[1])=TOINT(cd[1])
fd would then replace the cd in the original query's WHERE segment. Where would my case test go, is it correctly written (and if not, what is wrong), and would I need something else added to make it all work?
Just use a WITH block to do a computation and bind it to a new variable, like this:
WITH 2 + 2 as y RETURN y;
That basically assigns the value 4 to y.
In your query, you already have a big WITH block. Just put your computations in those, bound to new variables, and you can then refer to those variables in subsequent expressions.
Don't try to modify these variables, just create new ones (with new WITH blocks) as needed. If you need variables that can actually change, then...well hey you're working with a database, the ultimate way to store and update information. Create a new node, and then update it as you see fit. :)
This is my proposed solution
Explanation: I have declared four variables in my query i.e. name1, name2, ken and lana and I am using these variables for creating MATCH pattern (in the MATCH clause) and filtering those in the Where clause.
WITH "Lau" AS name1,
"L" AS name2,
"Keanu Reeves" AS ken,
"Lana Wachowski" AS lana
MATCH(x:Person{ name: ken})-[:ACTED_IN]->(m:Movie)<-[:ACTED_IN]-(y:Person),
(x1:Person{name: lana})-[:DIRECTED]->(m)<-[:DIRECTED]-(y1:Person)
WHERE y.name CONTAINS name1 OR
y.name CONTAINS name2 OR
(y.name CONTAINS name1 AND y.name CONTAINS name2)
RETURN x, m, y, x1;
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.
I want to use the FOREACH like:
a:{a_attr:chararray}
b:{b_attr:int}
FOREACH a {
res = CROSS a, b;
-- some processing
GENERATE res;
}
By this I mean to make for each element of a a cross-product with all the elements of b, then perform some custom filtering and return tuples.
==EDIT==
Custom filetering = res_filtered = FILTER res BY ...;
GENERATE res_filtered.
==EDIT-2==
How to do it with a nested CROSS no more no less inside a FOR loop without prior GROUP or COGROUP?
Depending on the specifics of your filtering, you may be able to design a limited set of disjoint classes of elements in a and b, and then JOIN on those. For example:
If your filtering rules are
if a_attr starts with "Foo" and b is 4, accept
if a_attr starts with "Bar" and b is greater than 17, accept
if a_attr begins with a letter in [m-z] and b is less than 0, accept
otherwise, reject
Then you can write a UDF that will return 1 for items satisfying the first rule, 2 for the second, 3 for the third, and NULL otherwise. Your CROSS/FILTER then becomes
res = JOIN a BY myUDF(a), b BY myUDF(b);
Pig drops null values in JOINs, so only pairs satisfying your filtering criteria will be passed.
CROSS generates a cross-product of all the tuples in each relation. So there is no need to have a nested FOREACH. Just do the CROSS and then FILTER:
a: {a_attr: chararray}
b: {b_attr: int}
crossed = CROSS a, b;
crossed: {a::a_attr: chararray,b::b_attr: int}
res = FILTER crossed BY ... -- your custom filtering
If you have the FILTER immediately after the CROSS, you should not have (unnecessary) excessive IO trouble from the CROSS writing the entire cross-product to disk before filtering. Records that get filtered will never be written at all.