problem with Neo4j query, calling foreach for the previous match results - scala

I have this class (node):
case class User(name: String, lastName: String, age: Int, city: String)
I'm trying to add relationships for an existing node (name=Alice) with this query:
MATCH (user:User {name: $name}),(friend:User) WHERE friend.name IN [$fNames] WITH user,friend CREATE (user)-[f:FRIEND]->(friend) RETURN f
I'm passing in fNames = List(Bob, David), and all nodes exist in db (for Alice, Bob and David).
This query returns no results, and that one neither:
MATCH (user:User {name: $name})-[f:FRIEND]->(friend:User) RETURN friend
I tried using FOREACH in the first query but was getting errors (something about Nodes vs Paths).
My goal is to add FRIEND relationships (edges) between Alice and all User nodes (vertices) with "name" field in fNames list.
How can I do that using a single cipher query?
Thanks,
Joe

Since $fNames is already a list, [$fNames] would enclose that list within another list -- which is not what you intended. You should just use $fNames as-is:
MATCH (user:User {name: $name}), (friend:User)
WHERE friend.name IN $fNames
CREATE (user)-[f:FRIEND]->(friend)
RETURN f
Also, to speed up this query, you should consider creating an index on :User(name).

Related

Kmongo: how to add unique field

I have a simple user data class that looks like:
#Serializable
data class User(
#SerialName("_id")
val _id: Id<User> = newId(),
val email: String,
var password: String,
var tokens: Array<String> = arrayOf()
)
And I'd like the email value to be unique, i've tried a unique annotation which seemed most appropiate, but with no success.
I've also tried google and the KMongo website but I could not find an answer.
You need to create an index on the field (or combination of fields) that you want to ensure uniqueness on.
db.getCollection<User>().createIndex(User::email,
indexOptions = IndexOptions().unique(true))

How to pass variable between two queries in MongoDB?

I want to put the query result from one collection in a variable and use it as input for query in another collection. The queries look like this as follows:
Query 1:
var ID=db.User.findOne({Name:"Ivan"}, {ID: 1});
db.Artists.find({"Listeners.ID":ID});
Query 2:
var Friends=db.Users.find({Friends:x});
//Users.Friends is an array of interger identifier for User
db. Artists.find({"Listeners.ID":{$in:Friends}});
But they all don't work. How to write the right one?
The query db.User.findOne({Name:"Ivan"}, {ID: 1}); does not return a single value, it returns the document, reduced to the field you requested. What you get is an object, with two fields: _id (because you didn't explicitly exclude it) and ID (when it exists in the document). Your var ID looks like this:
{
_id:ObjectId(<long hex string>),
ID:<value>
}
So when you want to query by the ID value, you need to specify it:
db.Artists.find({"Listeners.ID":ID.ID});
Regarding your second query: when you use find instead of findOne you get a cursor object which can then be used to retrieve the individual documents using cursor.next() or cursor.toArray().

grails-mongodb: findAllBy*InList not returning results in order

I'm using grails2.4.4 with mongodb plugin version 3.0.3. I'm facing issue while getting results of my domain object. I'm using below code:
My domain:
Employee{
ObjectId id
String name
}
I have list of ids , using below code to fetch employees: (Please note that below data is just for representing my problem. In realtime, my ids are random and so i can't use sorting, but i just want the result in the order of input.)
def idsList=[new ObjectId("2001"), new ObjectId("2002"), new ObjectId("2003")]
def results=Employee.findAllByIdInList(idsList)
Expected result:
[Employee#2001,Employee#2002,Employee#2003]
Actual result (not in order):
[Employee#2002, Employee#2003 , Employee#2001] or sometimes
[Employee#2003, Employee#2001 , Employee#2002]
For now i'm doing like this to get the output in desired order:
def results=[]
for(id in idsList){
def emp=Employee.findById(id)
results<<emp
}
But i want to do this with single call(findAllBy*InList) without iterating over objects. Can anyone advise how can i get the results in the order of input ids?
Have you tried
Employee.findAllByIdInList(idsList, [sort: 'id', order:'asc'])
? It should work as expected
If it doesn't, instead of for loop you can use
def result = idList.collect { id -> Employee.findById(id) }

Lazyloading collection in play-salat

Is it possible to load a collection lazy with Sala?
e.g. I have an object like
Example 1 (in this case, the whole user list is loaded when retrieving the object)
case class Test(
#Key("_id") _id: ObjectId = new ObjectId,
name: String,
users: List[User]) {
}
or Example 2 (the object is loaded without the list, but no idea how to get the users list)
case class Test(
#Key("_id") _id: ObjectId = new ObjectId,
name: String) {
#Persist val users: List[User] = List()
}
How can I load the object in the first example without the users list?
or: How can I load the users list in the second example?
Thanks in advance!
Salat author here.
Salat doesn't have anything like ORM lazy loading. The #Persist annotation is meant to persist fields outside of the constructor, but suppresses deserialization because only fields in the constructor will be deserialized.
But you can easily decide when making the query whether you want the list of users or not.
case class Test(#Key("_id") id = new ObjectId, name: String, users: List[User] = Nil)
You can persist the users as embedded documents inside the test document, and then use the second argument of the query, the ref, to exclude (0) or include (1) fields in the object.
TestDAO.find(/* query */, MongoDBObject("users" -> 0))
The other strategy is to break out user documents into a child collection - see https://github.com/novus/salat/wiki/ChildCollection for more information. In this example, Test is the "parent" and User is the "child".
The strategy there is that in the parent DAO, when saving, you override the save methods to save users using the child DAO, and then save the parent object with users set to Nil.
Then, by default, a Test instance is retrieved with users set to Nil.
If you want to retrieve Test with users, you will need to add a find method to your DAO that manually:
find the test document
use the _id field of the test document to query for user documents by parent id - this will yield List[User]
deserialize the test document to an instance of Test using grater[Test] and copy it with the list of users

Using MapReduce/Aggregation to create search facets

I have the documents of the following prototype:
{
title: "HD8200 DLP Projector",
normal_price: 4999.99,
specifications: [
{
ov: "HD (1920 x 1080)",
fn: "Resolution (Native / Max)",
o: 7,
f: 211
},
{
ov: "20000",
fn: "Contrast Ratio",
o: 15,
f: 225
}
]
}
I'm looking to create a list of filters for this product database, based on the specifications.
How can I get a list of option IDs (o) mapped to their product counter, for each field (f)?
Let's assume I need to achieve this for a specific list of field IDs (say, 211 and 225).
Write a mapper to take one document per map() call and write out a record for each option. The record key would be the field ID and the value would be a concatenation of a flag "1", the product title and option ID.
Write another mapper to read the list of id's and to write out a record with the same format: the key is the field ID and the value is a concatenation of a flag "0" and two empty strings.
Write a Reducer to read in the records written by the two mappers. Each call to reducer() will pass in all records written for a given field. If one of those records has a "0" flag then that field was one of the fields you're interested in. Only then would you write out a record for each record with a "1" flag. The key is the product title and the value is the option id.
Define your first job driver class to use the two mappers and the one reducer. This step will come up with pairs of product-options for the field id's you specify. You'll need a second job to gather the options by product, or vide versa.