Simple question about Cypher "RETURN"

Hi,

I'm a beginner in Neo4j and just learning with a video that talk about Spring Data Neo4j.
I have a question that is the video creator used query just like:

MATCH (a :Author)-[r]->(b: Book) RETURN (b) --> (a)

to get all the nodes and relationships with the ReactiveCrudRepository and @query annotation.

I tried to use the similar query but just throws the error that is "Neo.ClientError.Statement.SyntaxError".

I'm using the Neo4j Community 5.9.0, but I can't confirm what the version of Neo4j Community in that vedio and I couldn't get contact with the video creator so just wish anyone can help me here.

Thanks,
Saki

Hi @croxxxholic

This is a syntax error because you are matching

MATCH (a:Author)-[r]->(b:Book)

But you want to return the reverse relationship, which is not possible. Maybe in the video, the author, did the folllowing:

MATCH (a:Author)-->(b:Book)
RETURN a,b

which basically returns all the nodes Author and Bookthat are connected with any type of relationship between each other, by -> direction. I hope it helps.

Thank you for your reply.
Actually in the video he created an Entity just like:

@Node
public class Book
{
	@Id
	@GeneratedValue
	private Long id;

	@Relationship(direction = Relationship.Direction.OUTGOING, type = "WRITTEN_BY")
	private Author author;

	@Relationship(direction = Relationship.Direction.INCOMING, type = "WROTE")
	private Author author;'
}

and a Dao just like:

public interface BookDao extends ReactiveCrudRepository<Book, Long>
	
{
	@Query("MATCH  (a :Author) -[r]-> (b :Book) WHERE b.name = $0 RETURN (b) --> (a)")
	Flux<Book> findByName(String name);
}

then the testDao like:

	@ParameterizedTest
	@ValueSource(strings = {"bookName"})
	public void testFindByName(String name)
	{
		bookDao.findByName(name).toIterable()
				.forEach(book -> System.out.println(book + "->" + book.getAuthor()));

	}

The "book.getAuthor()" was outputed successful in the video, but [null] in my coding.

I tried to change the @Query in to:

@Query("MATCH p = (a :Author) -[r]-> (b :Book) WHERE b.name = $0 RETURN p")

or

@Query("MATCH  (a :Author) -[r]-> (b :Book) WHERE b.name = $0 RETURN a,b")

but dosen't work.

Well, your book object has two attributes with the same name. I assume that is an error. Try the following, after fixing the duplicate attribute. I picked the WROTE relationship because you specified an incoming relationship in your pattern. The relationship type is not required.

	@Query("MATCH  (b:Book{name:$name}) <-[:WROTE]- (a :Author)  RETURN a, b")
	Flux<Book> findByName(String name);

Note: you can't return a pattern like you did in your original post, i.e. (b) --> (a)

Thank you for your reply and I'm sorry that I made the mistake about the duplicate attribute.

So if my Book object is like:

@Node
public class Book
{
	@Id
	@GeneratedValue
	private Long id;

	@Relationship(direction = Relationship.Direction.OUTGOING, type = "WRITTEN_BY")
	private Author authorWrittenBy;

	@Relationship(direction = Relationship.Direction.INCOMING, type = "WROTE")
	private Author authorWrote;
}

and I want to get the "authorWrittenBy" and "authorWrote" in the same time.

capture

I just tried and the results would be:

①If I use the reactive crud method without customize query for example:

Flux<Book> findByName(String name);
	@ParameterizedTest
	@ValueSource(strings = {"Book Title"})
	public void testFindByName(String name)
	{
		bookDao.findByName(name).toIterable()
				.forEach(book -> {
					System.out.println("Book: "+ book);
					System.out.println("authorWrittenBy: "+ book.getAuthorWrittenBy());
					System.out.println("authorWrote: "+ book.getAuthorWrote());
				});

	}

	}

the result:

Book: Book [id=15, name=Book Title, price=100]
authorWrittenBy: Author{id=16, name='James', addr='Tokyo', age=30}
authorWrote: Author{id=16, name='James', addr='Tokyo', age=30}

②If I use the reactive crud method with customize query :

	@Query("MATCH p = (b:Book{name:$name}) <-[:WROTE]- (a :Author) RETURN p")
	Flux<Book> customFindByName1(String name);

the result:

Book: Book [id=15, name=Book Title, price=100]
authorWrittenBy: null
authorWrote: Author{id=16, name='James', addr='Tokyo', age=30}

③If I use the reactive crud method with customize query like you replied:

	@Query("MATCH (b:Book{name:$name}) <-[:WROTE]- (a :Author) RETURN a, b")
	Flux<Book> customFindByName2(String name);

the result:

Book: Book [id=15, name=Book Title, price=100]
authorWrittenBy: null
authorWrote: null

So I'm curious how to write the query could I get the result like ①.

I'm very sorry for the long question taking up your time, and I'd be very thankful if anyone could help me.

First, it seems the authors are the same in your data model. You just have different relationship types depending on the start node, bu each points to the same author. This is not necessary, because the direction doesn’t matter. You can traverse relationships in either direction. As such, eliminate one of the author attributes. It you kepted the written by relationship, you are still able to get all the books written by an author traversing the written by relationship backwards.

You could try the following to fix my suggestion. It still only gets author that wrote the book.

@Query("MATCH (b:Book{name:$name}) <-[r:WROTE]- (a :Author) RETURN a, r, b")
	Flux<Book> customFindByName2(String name);

If that works, try the following to get both:

@Query("MATCH (b:Book{name:$name}) -[r:WROTE|WRITTEN_BY]- (a :Author) RETURN a, r, b")
	Flux<Book> customFindByName2(String name);

Considering returning the path approach worked, try the following variation:

	@Query("MATCH p = (b:Book{name:$name}) -[:WROTE|WRITTEN_BY]- (a :Author) RETURN p")
	Flux<Book> customFindByName1(String name);

Thank you for your reply.
I now understanded that I don't need different directions on the same node.

But also something interesting about the query.

First, if I use the "RETURN a, r, b", both "authorWrittenBy" and "authorWrote" returns null.

And I tried the following:

	@Query("MATCH p = (b:Book{price:$price}) -[:WROTE|WRITTEN_BY]- (a :Author) RETURN p")
	Flux<Book> customFindByPrice(int price);

If I have the same price for lot of nodes, the results will become:

Book: Book [id=0, name=Book Title2, price=100]
authorWrittenBy: null
authorWrote: Author{id=1, name='Tom', addr='Tokyo', age=30}
---------------
Book: Book [id=6, name=Book Title3, price=100]
authorWrittenBy: Author{id=7, name='Jerry', addr='Tokyo', age=30}
authorWrote: null
---------------
Book: Book [id=15, name=Book Title, price=100]
authorWrittenBy: null
authorWrote: Author{id=16, name='James', addr='Tokyo', age=30}
---------------

Some of "authorWrittenBy" returns null and the others, "authorWrote" returns null.

The way I understand it, SDN takes the return values and tries to 'hydrate' the domain entity. Each row of the result set is supposed to represent one entity. In your case, the Author entity is indistinguishable in regards to the WROTE and WRITTEN_BY relationships since the Author node is the same for both. As such, it must be using the one Author node in the result set to set only one of the Book's authorWrittenBy and authorWrote attributes.

Oh, maybe it is because of the following. The query will result in two rows, one for each relationship type. Maybe the results for the second row is what you see in your results, while the first row got replaced by the creation of the second row. Just a hypothesis.

At this point, since you now know that the bidirectional relationship is not necessary, I would remove the 'authorWrote' attributes. You code works for one of these attributes.