Count relationships by types in Neo4j - neo4j

I have many relationship types in the database. How do I count relationships by each type without using apoc?

Solution
MATCH ()-[relationship]->()
RETURN TYPE(relationship) AS type, COUNT(relationship) AS amount
ORDER BY amount DESC;
The first line specifies the pattern to define the relationship variable, which is used to determine type and amount in line two.
Example result
╒══════════════╤════════╕
│"type" │"amount"│
╞══════════════╪════════╡
│"BELONGS_TO" │1234567 │
├──────────────┼────────┤
│"CONTAINS" │432552 │
├──────────────┼────────┤
│"IS_PART_OF" │947227 │
├──────────────┼────────┤
│"HOLDS" │4 │
└──────────────┴────────┘

There's also a built in procedure in 3.5.x that you can use to retrieve counts, but it does take a bit of filtering to get down to those you are interested in:
CALL db.stats.retrieve('GRAPH COUNTS') YIELD data
UNWIND [data IN data.relationships WHERE NOT exists(data.startLabel) AND NOT exists(data.endLabel)] as relCount
RETURN coalesce(relCount.relationshipType, 'all') as relationshipType, relCount.count as count

Related

Neo4J Subquery over same property to calculate ratio

I'm working with a tweets graph. I'm trying to get the ratio between tweets in spanish and tweets in english.
When checking the number of tweets by language:
MATCH (twtEs:Tweet)<-[:HAS_WRITEN]-()-[:HAS_AS_PROFILE_LANGUAGE]->(l:Language)
RETURN DISTINCT l.languageCode, count(*)
\\ Result:
╒════════════════╤══════════╕
│"l.languageCode"│"count(*)"│
╞════════════════╪══════════╡
│"en" │165392 │
├────────────────┼──────────┤
│"es" │73693 │
└────────────────┴──────────┘
We can see the counts for each language.
But when trying to calculate the ratio directly:
MATCH (twtEs:Tweet)<-[:HAS_WRITEN]-()-[:HAS_AS_PROFILE_LANGUAGE]->(:Language{languageCode:'es'})
WITH count(twtEs) AS tweetsEs
MATCH (twtEn:Tweet)<-[:HAS_WRITEN]-()-[:HAS_AS_PROFILE_LANGUAGE]->(:Language{languageCode:'en'})
WITH count(twtEn) as tweetsEn, tweetsEs
RETURN tweetsEs/tweetsEn as RatioTweetsEsvsEn;
\\ Result:
╒═══════════════════╕
│"RatioTweetsEsvsEn"│
╞═══════════════════╡
│0 │
└───────────────────┘
Thats what I obtain, when it should be 0,44557.
I've been checking the documentation and other answers in Stackoverflow but haven't found something similar to use as example. Probably the second query is incorrect but I'm strugling to resolve it.
Thanks in advance.
I'm running:
Neo4j Browser version: 3.2.20
Neo4j Server version: 3.5.8 (community)
It's because count values are Integers.
For example :
WITH 165392 AS v1, 73693 AS v2
RETURN v1/v2
╒═══════╕
│"v1/v2"│
╞═══════╡
│2 │
└───────┘
You can transform them to floats instead :
WITH 165392 AS v1, 73693 AS v2
RETURN v1*1.0f/v2*1.0f
╒══════════════════╕
│"v1*1.0f/v2*1.0f" │
╞══════════════════╡
│2.2443379968246644│
└──────────────────┘
Which would give for you :
MATCH (twtEs:Tweet)<-[:HAS_WRITEN]-()-[:HAS_AS_PROFILE_LANGUAGE]->(:Language{languageCode:'es'})
WITH count(twtEs) AS tweetsEs
MATCH (twtEn:Tweet)<-[:HAS_WRITEN]-()-[:HAS_AS_PROFILE_LANGUAGE]->(:Language{languageCode:'en'})
WITH count(twtEn) as tweetsEn, tweetsEs
RETURN tweetsEs*1.0f/tweetsEn*1.0f as RatioTweetsEsvsEn;

Find a number of photos inside a photo album

I have photo albums and their photos stored in Neo4j. I would like to be able to find one album and get a certain amount of photos. The goal is to lazily load photos as required (pagination).
Now I can do the following to achieve what I want:
match(p:Photo)-[bt:BELONGS_TO]->(a:Album) where a.name = "Summer 2019" return a, collect(p)[..4] as photos
However I would like to be able to sort the list of photos by different criteria such as their upload date or creation date. I'm not exactly sure whether this is the best approach to do this.
match(p:Photo)-[bt:BELONGS_TO]->(a:Album) where a.name = "Summer 2019" return a, collect(p)[4..] as photos order by p.file_name
Fails and tells me the following:
In a WITH/RETURN with DISTINCT or an aggregation, it is not possible to access variables declared before the WITH/RETURN: p
I would like to keep the exact same format of the result (one album, one page of photos) if possible so that I don't have to do complicated mapping inside my application code:
╒══════════════════════╤══════════════════════════════════════════════════════════════════════╕
│"a" │"photos" │
╞══════════════════════╪══════════════════════════════════════════════════════════════════════╡
│{"name":"Summer 2019"}│[{"file_name":"cat.jpeg"},{"file_name":"dog.jpeg"},{"file_name":"birdi│
│ │e.jpeg"},{"file_name":"bird.jpeg"}] │
└──────────────────────┴──────────────────────────────────────────────────────────────────────┘
Is there a clean way to get this format while being able to sort the photos?
You need to ORDER BY before collecting your p nodes
MATCH (p:Photo)-[bt:BELONGS_TO]->(a:Album)
WHERE a.name = "Summer 2019"
WITH
a,
p
ORDER BY p.file_name
RETURN a, collect(p)[4..] as photos

Convert string into multidimensional array in Neo4J

I loaded data from csv into Neo4J. One column in the file is an array of arrays that Neo4J now considers one large string. How can I convert it back into an array?
My file looks like this:
Id, name, reviews
1, item1, "[[date1, User1, Rating1],
[date2, User2, Rating2],
[date3, User3, Rating3]] "
Into Neo4J:
USING PERIODIC COMMIT
LOAD CSV WITH HEADERS FROM "file:///data/file.csv" AS line
CREATE(:Product{
Id:toInteger(line.Id),
name:toString(line.name),
reviews:line.reviews})
RETURN line
Now the review column is loaded, but considered one large string.
"[date1, User1, Rating1], [date2, User2, Rating2], [date3, User3, Rating3]"
Is there any way to split it into two arrays like this:
First Array:
[date1, User1, Rating1], //0
[date2, User2, Rating2], //1
[date3, User3, Rating3] //2
Second Array example:
// 0 , 1 , 2
[date1, User1, Rating1]
I'd like to be able to acces my data like this:
MATCH (n) RETURN n.reviews[2] (output: date3, User3, Rating3)
MATCH (n) RETURN n.reviews[2][0] (output: date3)
MATCH (n) RETURN n.reviews[1][1] (output: User2)
Is there any way to do this?
Using APOC Procedures, you can use the apoc.convert.fromJsonList() function to convert the list, though you'll need to make sure each of the subitems in the arrays is quoted so they'll be interpreted as strings.
WITH "[['date1', 'User1', 'Rating1'], ['date2', 'User2', 'Rating2'], ['date3', 'User3', 'Rating3']]" as inputString
WITH apoc.convert.fromJsonList(input) as input
RETURN input[2][0] //returns 'date3'
Just a note, the conversion functions are currently missing in the APOC docs, but you can reference them and their signature by entering this in the Neo4j browser:
CALL apoc.help('fromjson')
And now for the bad news.
Although you can do this with literal input and parameters and convert from properties which are JSON strings, you cannot use a nested list as a property of a node or relationship, that's just a current limitation of the properties implementation.
That said, this feels like your modeling may need some improvement. We'd recommend using separate nodes for your reviews rather than this nested structure, so something like:
(:Product)-[:HAS_REVIEW]->(:Review)
Where the :Review node has the date and rating, and either has the user ID, or has a relationship to the user node who rated the product.
Usage would look something like:
MATCH (p:Product {id:12345})-[:HAS_REVIEW]->(r)
WITH p, collect(r) as reviews
...
At that point you have an (unordered) array of the review nodes, and can do index access to get a review at a particular index, then use dot notation to access the property or properties you want. If you want ordering, you'll need to do an explicit ORDER BY before the collect(), and you'll need something to order it by, probably the date of the review.

How to match all possible paths through multiple relationship types with filtering (Neo4j, Cypher)

I am new to Neo4j and I have a relatively complex (but small) database which I have simplified to the following:
The first door has no key, all other doors have keys, the window doesn't require a key. The idea is that if a person has key:'A', I want to see all possible paths they could take.
Here is the code to generate the db
CREATE (r1:room {name:'room1'})-[:DOOR]->(r2:room {name:'room2'})-[:DOOR {key:'A'}]->(r3:room {name:'room3'})
CREATE (r2)-[:DOOR {key:'B'}]->(r4:room {name:'room4'})-[:DOOR {key:'A'}]->(r5:room {name:'room5'})
CREATE (r4)-[:DOOR {key:'C'}]->(r6:room {name:'room6'})
CREATE (r2)-[:WINDOW]->(r4)
Here is the query I have tried, expecting it to return everything except for room6, instead I have an error which means I really don't know how to construct the query.
with {key:'A'} as params
match (n:room {name:'room1'})-[r:DOOR*:WINDOW*]->(m)
where r.key=params.key or not exists(r.key)
return n,m
To be clear, I don't need my query debugged so much as help understanding how to write it correctly.
Thanks!
This should work for you:
WITH {key:'A'} AS params
MATCH p=(n:room {name:'room1'})-[:DOOR|WINDOW*]->(m)
WHERE ALL(r IN RELATIONSHIPS(p) WHERE NOT EXISTS(r.key) OR r.key=params.key)
RETURN n, m
With your sample data, the result is:
╒════════════════╤════════════════╕
│"n" │"m" │
╞════════════════╪════════════════╡
│{"name":"room1"}│{"name":"room2"}│
├────────────────┼────────────────┤
│{"name":"room1"}│{"name":"room3"}│
├────────────────┼────────────────┤
│{"name":"room1"}│{"name":"room4"}│
├────────────────┼────────────────┤
│{"name":"room1"}│{"name":"room5"}│
└────────────────┴────────────────┘

Accessing map values from neo4, apoc, Cypher

I am still rather new to Neo4j, Cypher and programming in general.
Is there a way to access the posted output below, i.e. access the "count" values for every "item“ (which has to be the pair), and also access the "item" values? I need the amount of how often a pair, i.e. specific neighboring nodes occur not only as information, but as values with which I can further work with in order to adjust my graph.
My last lines of code (in the preceding lines I just ordered the nodes sequentially):
...
WITH apoc.coll.pairs(a) as pairsOfa
WITH apoc.coll.frequencies(pairsOfa) AS giveBackFrequencyOfPairsOfa
UNWIND giveBackFrequencyOfPairsOfa AS x
WITH DISTINCT x
RETURN x
Output from the Neo4j Browser that I need to work with:
"x"
│{"count":1,"item":[{"aName“:"Rob","time":1},{"aName":"Edwin“,"time“:2}]},{„count“:4,“item":[{"aName":"Edwin","time":2},{"aName“:"Celesta","time":3}]}
...
Based on your code, your result should contain multiple x records (not a single record, as implied by the "output" provided in your question). Here is an example of what I would expect:
╒══════════════════════════════════════════════════════════════════════╕
│"x" │
╞══════════════════════════════════════════════════════════════════════╡
│{"count":1,"item":[{"aName":"Rob","time":1},{"aName":"Edwin","time":2}│
│]} │
├──────────────────────────────────────────────────────────────────────┤
│{"count":1,"item":[{"aName":"Edwin","time":2},{"aName":"Celesta","time│
│":3}]} │
└──────────────────────────────────────────────────────────────────────┘
If that is true, then you can just access the count and item properties of each x directly via x.count and x.item. To get each value within an item, you could use x.item[0] and x.item[1].
Asides: you probably want to use apoc.coll.pairsMin instead of apoc.coll.pairs, to avoid the generation of a spurious "pair" (whose second element is null) when the number of values to be paired is odd. Also, you probably do not need the DISTINCT step.

Resources