I want to use a spatial index in Neo4j with the spring-data-neo4j framework. Additionally I want to query the index using cypher. The database is embedded.
I'm a bit clueless as to how to get it to all wire together.
With a domain object like this,
#NodeEntity
class Junction {
#GraphId Long id;
#Indexed(indexType = IndexType.POINT, indexName = "junctionLocations") Point wkt;
}
SDN ought to be maintaining the index for me. It appears so, as I can do spatial queries using the repository:
interface JunctionGraph extends GraphRepository<Junction>, SpatialRepository<Junction> {}
with
junctionGraph.findWithinBoundingBox("junctionLocations", new Box(lowerBound.point, upperBound.point))
However, I understand that to query this index using cypher (via a #Query in the repository) this spatial index configuration won't work. I think that this is because each node needs to be manually added to the spatial index (or at least a proxy for the node). That means that adding this to JunctionGraph:
#Query("START n=node:junctionLocations('withinDistance:[{0}, {1}, {2}]') MATCH n-[*]->(i:Item) return i")
Collection<Item> getItemsWithin(double lat, double lon, double radius)
doesn't work.
Does anyone have a working recipe? It appears to be a bit black magic to me, and I'm unsure what the best way to proceed within SDN is.
It works, you just have to create the whole query string outside and pass it as parameter, placeholders within string constants are not substituted.
#Query("START n=node:junctionLocations({0}) MATCH n-[*]->(i:Item) return i")
Collection<Item> getItemsWithin(String query)
you have to do the replacement yourself, .e.g. using String.format
String.format("withinDistance:[%f, %f, %f]",lat,lon,radius)
Related
I have a following SDN 4 node entity:
#NodeEntity
public class Product {
#Index(unique = false)
private String name;
...
}
Inside of this entity I have added name property and declared an index.
Right now I'm going to implement case insensitive search by product name.
I have created a SDN 4 repository method:
#Query("MATCH (p:Product) WHERE LOWER(d.name) = LOWER({name}) RETURN p")
Product findByName(#Param("name") String name);
In order to search a product I use following Cypher: LOWER(d.name) = LOWER({name})
I think the index doesn't effectively work in this case because I lowercase the strings.
What is a proper way in Neo4j/SDN 4 to make index working here ?
If you do not need to store the name in the original case, then convert the name to lowercase before storing it in the DB.
If you do need the name in the original case, then you could add an extra property (say, "lower_name") that stores the lowercased name. You can index that property and use it for indexed comparisons.
A third choice is to use legacy indexing, which is much more complex to use and no longer favored. However, it does support case-insensitive indexing (see the second example on this page).
This is my first attempt at Neo4j, please excuse me if I am missing something very trivial.
Here is my problem:
Consider the graph as created in the following Neo4j console example:
http://console.neo4j.org/?id=y13kbv
We have following nodes in this example:
(Person {memberId, memberName, membershipDate})
(Email {value, badFlag})
(AccountNumber {value, badFlag})
We could potentially have more nodes capturing features related to a Person like creditCard, billAddress, shipAddress, etc.
All of these nodes will be the same as Email and AccountNumber nodes:
(creditCard {value, badFlag}), (billAddress {value, badFlag}),etc.
With the graph populated as seen in the Neo4j console example, assume that we add one more Person to the graph as follows:
(p7:Person {memberId:'18' , memberName:'John', membershipDate:'12/2/2015'}),
(email6:Email {value: 'john#gmail.com', badFlag:'false'}),
(a2)-[b13:BELONGS_TO]->(p7),
(email6)-[b14:BELONGS_TO]->(p7)
When we add this new person to the system, the use case is that we have to check if there exists a path from features of the new Person ("email6" and "a2" nodes) to any other node in the system where the "badFlag=true", in this case node (a1 {value:1234, badFlag:true}).
Here, the resultant path would be (email6)-[BELONGS_TO]->(p7)<-[BELONGS_TO]-(a2)-[BELONGS_TO]->(p6)<-[BELONGS_TO]-(email5)-[BELONGS_TO]->(p5)<-[BELONGS_TO]-(a1:{badFlag:true})
I tried something like this:
MATCH (newEmail:Email{value:'john#gmail.com'})-[:BELONGS_TO]->(p7)-[*]-(badPerson)<-[:BELONGS_TO]-(badFeature{badFlag:'true'}) RETURN badPerson, badFeature;
which seems to work when there is only one level of chaining, but it doesn't work when the path could be longer like in the case of Neo4j console example.
I need help with the Cypher query that will help me solve this problem.
I will eventually be doing this operation using Neo4j's Java API using my application. What could be the right way to go about doing this using Java API?
You had a typo in you query. PART_OF should be BELONGS_TO. This should work for you:
MATCH (newEmail:Email {value:'john#gmail.com'})-[:BELONGS_TO]->(p7)-[*]-(badPerson)<-[:BELONGS_TO]-(badFeature {badFlag:'true'})
RETURN badPerson, badFeature;
Aside: You seem to use string values for all properties. I'd replace the string values 'true' and 'false' with the boolean values true and false. Likewise, values that are always numeric should just use integer or float values.
In our project , PEPOLEs are connected by KNOWS relationships. We need to query one’s friends in depth n which n is a parameter inputed by user.We use Spring Data Neo4j to implement it.
public interface PeopleRepository extends GraphRepository<People>
{
#Query("MATCH (startnode{name:{name}})-[:KNOWS*1..{depth}]-(remote_friend) RETURN remote_friend.name");
List<People> getFriendsInDepth(#Param("name") String name, #Param("depth") Integer depth);
}
The above codes won’t work. But if I replace {depth} parameter with a fixed Integer value as follows:
#Query("MATCH (startnode{name:{name}})-[:KNOWS*1..2]-(remote_friend) RETURN remote_friend.name");
List<People> getFriendsInDepth(#Param("name") String name, #Param("depth") Integer depth);
it works. I know the problem is caused by the depth parameter. But I have tried a lot of methods to replace {depth}, for example: toInt({depth}), it still won’t work. Is there anyone know how to use the parameter in the Cypher statement for variable length pattern matching?
Cypher does not allow you to parameterize the depth of a relationship, hence #Query won't support it either.
If you use Spring Data Neo4j 4, then perhaps you can translate your #Query to a set of org.neo4j.ogm.cypher.Filter.
Then you can use the Session.loadAll methods which accept Filters as well as a depth.
MusicIntegrationTest contains a couple of Filter examples.
I am implementing search on node attributes. Suppose I have a millions node of User with attributes name, location, income.
n:user (name:"abc",location:"xyz", income:"123")
n:user (name:"def",location:"ghj", income:"1877")
I want to search User either with name or location or income or the combination of attributes like name and location etc. I can perform this search with simple cypher query
#Query("MATCH(n:user{location:{xyz},}) RETURN n") or
#Query("MATCH(n:userr{location:{xyz},name:{abc}}) RETURN n")
But i don't want to do like that, i just want to pass the search parameter and create search query at runtime with the requested parameter. In neo4template class, got this method query(String statement, Map<String,Object> params) Now i am clueless, any detailed answer would be appreciated, as i am new to neo4j.
Unfortunately, parameter maps cannot be used in MATCH patterns.
So I believe you cannot use universal query like:
#Query("MATCH(n:user{0}) RETURN n")
public List<User> complexFind(Map<String, Object> props);
I'd suggest building query string dynamically, using string concatenation, and executing it later on with smth like that:
neo4template.query(YourQueryStringBuilder.build(search_params), other_params);
I have imported freebase dump to neo4j. But currently i am facing issue with get queries because of size of db. While import i just created node index and indexed URI property to index for each node. For each node i am adding multiple properties like label_en, type_content_type_en.
props.put(URI_PROPERTY, subject.stringValue());
Long subjectNode = db.createNode(props);
tmpIndex.put(subject.stringValue(), subjectNode);
nodeIndex.add(subjectNode, props);
Now my cypher queries are like this. Which are timing out. I am unable to add index on label_en property. Can anybody help?
match (n)-[r*0..1]->(a) where n.label_en=~'Hibernate.*' return n, a
Update
BatchInserter db = BatchInserters.inserter("ttl.db", config);
BatchInserterIndexProvider indexProvider = new LuceneBatchInserterIndexProvider(db);
BatchInserterIndex index = indexProvider.nodeIndex("ttlIndex", MapUtil.stringMap("type", "exact"));
Question: When i have added node in nodeindex i have added with property URI
props.put(URI_PROPERTY, subject.stringValue());
Long subjectNode = db.createNode(props);
nodeIndex.add(subjectNode, props);
Later in code i have added another property to node(Named as label_en). But I have not added or updated nodeindex. So as per my understanding lucene does not have label_en property indexed. My graph is already built so i am trying to add index on label_en property of my node because my query is on label_en.
Your code sample is missing how you created your index. But I'm pretty sure what you're doing is using a legacy index, which is based on Apache Lucene.
Your Cypher query is using the regex operator =~. That's not how you use a legacy index; this seems to be forcing cypher to ignore the legacy index, and have the java layer run that regex on every possible value of the label_en property.
Instead, with Cypher you should use a START clause and use the legacy indexing query language.
For you, that would look something like this:
START n=node:my_index_name("label_en:Hibernate.*")
MATCH (n)-[r*0..1]->(a)
RETURN n, a;
Notice the string label_en:Hibernate.* - that's a Lucene query string that says to check that property name for that particular string. Cypher/neo4j is not interpreting that; it's passing it through to Lucene.
Your code didn't provide the name of your index. You'll have to change my_index_name above to whatever you named it when you created the legacy index.