Why does my Cypher query not work? - node.js

I am making a Node.js server which manipulates a Neo4j database. Now I have a system of admins and posts, admins add posts. This is currently what my graph looks like:
Now I want the network to be interconnected so I want to add a relationship between the Admin nodes "COLLEAGUE_OF". I wrote a Cypher query but I am getting a Cypher syntax error.
This is the syntax:
MERGE (c:Admin)
MERGE (n:BlogPost {mongoId:{idNeo}})
MERGE (b:Admin {mongoAdminId:{idNeoAd}})
MERGE(n)-[:ADDED_BY]->(b), (b)-[:COLLUEGE_OF]->(c)
This is the entire route which executes the syntax:
routes.post('/blogPosts/frontpage/:id/:aid', function (req, res) {
const id = req.param('id');
const aid = req.param('aid');
session
.run("MERGE (c:Admin) MERGE (n:BlogPost {mongoId:{idNeo}}) MERGE (b:Admin {mongoAdminId:{idNeoAd}}) MERGE(n)-[:ADDED_BY]->(b), (b)-[:COLLUEGE_OF]->(c)", {idNeo: id, idNeoAd: aid})
.then(function(result) {
res.status(200).json({"response": "BlogPost added to front page."});
session.close();
})
.catch((error) => {
res.status(400).json(error);
});
});

Assuming that :COLLUEGE_OF is the spelling you want (if this is in English, you may want :COLLEAGUE_OF instead), this query should take an existing graph of :Admin nodes and connect them to each other:
MATCH (c:Admin)
WITH collect(c) as admins
UNWIND admins as admin1
UNWIND admins as admin2
WITH admin1, admin2
WHERE id(admin1) < id(admin2) // prevents matching to the same pair with swapped order, and matching a node to itself
MERGE (admin1)-[:COLLUEGE_OF]-(admin2)
And this query would be used when adding a new blogpost and admin to the graph (it will not perform the merge if it has previously been processed):
MERGE (n:BlogPost {mongoId:{idNeo}})
MERGE (b:Admin {mongoAdminId:{idNeoAd}})
MERGE (n)-[:ADDED_BY]->(b)
WITH b
WHERE NOT (b)-[:COLLUEGE_OF]-()
MATCH (c:Admin)
WHERE c <> b
MERGE (b)-[:COLLUEGE_OF]-(c)
That said, if you really need :Admins to be connected to each other, it would be far easier to model this differently, with some kind of :ColleagueGroup node that all :Admins connect to, like this:
// create the single :ColleagueGroup node for all admins:
MERGE (group:ColleagueGroup)
WITH group
MATCH (c:Admin)
MERGE (c)-[:COLLUEGE_OF]->(group)
And then for adding a new admin
// add a new :Admin to the group
MERGE (n:BlogPost {mongoId:{idNeo}})
MERGE (b:Admin {mongoAdminId:{idNeoAd}})
WITH n, b
MATCH (group:ColleagueGroup)
MERGE (n)-[:ADDED_BY]->(b)
MERGE (b)-[:COLLUEGE_OF]->(group)
And for finding colleagues of a single :Admin node (which will be all :Admins):
MATCH (b:Admin {mongoAdminId:{idNeoAd}})-[:COLLUEGE_OF*2]-(colleague:Admin)
RETURN colleague

The problem is in the last line of your query, specifically, the , (b)-[:COLLUEGE_OF]->(c) statement, which creates a syntax error. Just remove the comma and add an additional merge statement.
MERGE (c:Admin)
MERGE (n:BlogPost {mongoId:{idNeo}})
MERGE (b:Admin {mongoAdminId:{idNeoAd}})
MERGE(n)-[:ADDED_BY]->(b)
MERGE(b)-[:COLLUEGE_OF]->(c)

Related

ArangoDB populate relation as field over graph query

I recently started using Arango since I want to make use of the advantages of graph databases. However, I'm not yet sure what's the most elegant and efficient approach to query an item from a document collection and applying fields to it that are part of a relation.
I'm used to make use of population or joins in SQL and NoSQL databases, but I'm not sure how it works here.
I created a document collection called posts. For example, this is a post:
{
"title": "Foo",
"content": "Bar"
}
And I also have a document collection called tags. A post can have any amount of tags, and my goal is to fetch either all or specific posts, but with their tags included, so for example this as my returning query result:
{
"title": "Foo",
"content": "Bar",
"tags": ["tag1", "tag2"]
}
I tried creating those two document collections and an edge collection post-tags-relation where I added an item for each tag from the post to the tag. I also created a graph, although I'm not yet sure what the vertex field is used for.
My query looked like this
FOR v, e, p IN 1..2 OUTBOUND 'posts/testPost' GRAPH post-tags-relation RETURN v
And it did give me the tag, but my goal is to fetch a post and include the tags in the same document...The path vertices do contain all tags and the post, but in separate arrays, which is not nice and easy to use (and probably not the right way). I'm probably missing something important here. Hopefully someone can help.
You're really close - it looks like your query to get the tags is correct. Now, just add a bit to return the source document:
FOR post IN posts
FILTER post._key == 'testPost'
LET tags = (
FOR v IN 1..2 OUTBOUND post
GRAPH post-tags-relation
RETURN v.value
)
RETURN MERGE(
post,
{ tags }
)
Or, if you want to skip the FOR/FILTER process:
LET post = DOCUMENT('posts/testPost')
LET tags = (
FOR v IN 1..2 OUTBOUND post
GRAPH post-tags-relation
RETURN v.value
)
RETURN MERGE(
post,
{ tags }
)
As for graph definition, there are three required fields:
edge definitions (an edge collection)
from collections (where your edges come from)
to collections (where your edges point to)
The non-obvious vertex collections field is there to allow you to include a set of vertex-only documents in your graph. When these documents are searched and how they're filtered remains a mystery to me. Personally, I've never used this feature (my data has always been connected) so I can't say when it would be valuable, but someone thought it was important to include.

Get multiple documents from collection using nodejs and mongodb

Hi I have two mongodb collections. The first one returns json data (array) and with the output of this, I want to return documents that match.
When I run Console.log (req.bidder.myBids) I get the following output:
[{"productId":"3798b537-9c7b-4395-9e41-fd0ba39aa984","price":3010},{"productId":"3798b537-9c7b-4395-9e41-fd0ba39aa984","price":3020},{"productId":"4c4bd71c-6664-4d56-b5d3-6428fe1bed19","price":1040},{"productId":"4c4bd71c-6664-4d56-b5d3-6428fe1bed19","price":1050},{"productId":"4c4bd71c-6664-4d56-b5d3-6428fe1bed19","price":1060},{"productId":"4c4bd71c-6664-4d56-b5d3-6428fe1bed19","price":1070},{"productId":"4c4bd71c-6664-4d56-b5d3-6428fe1bed19","price":1090},{"productId":"4c4bd71c-6664-4d56-b5d3-6428fe1bed19","price":1100}]
The productId has duplicates, I want to remove duplicates and then call a routine that finds all the products that match and output as json.
So far I have this code that only outputs one document, but cant figure out how to add the array of productId's and then fetch all corresponding products.
var agencyId = req.body.agencyId;
var productId = req.body.productId;
if (!validate.STRING(agencyId)) {
res.apiError(messages.server.invalid_request);
} else {
dbProduct.find({productId:{$in:['3798b537-9c7b-4395-9e41-fd0ba39aa984','4c4bd71c-6664-4d56-b5d3-6428fe1bed19']}
}).then(dbRes => {
console.log(dbRes);
Updated code and works with hard-wired productId and updated above code. Looking at how to get the array data and transpose replacing the hard-wired productId's
The $in operator is what you want. See the docs here: https://docs.mongodb.com/manual/reference/operator/query/in/

How do I do a joined lookup with search.lookupFields()?

I'm trying to get some information about an item, including the item's subsidiary's logo, which naturally requires joining the item to the subsidiary.
The documentation for search.lookupFields says:
You can use joined-field lookups with this method, with the following syntax:
join_id.field_name
So, I duly request the fields I want, including a join on subsidiary:
require(['N/search'], function(search) {
var item = search.lookupFields({
type: search.Type.ITEM,
id: 2086,
columns: ['itemid', 'displayname', 'subsidiary.logo'],
});
log.debug(item);
});
itemid and displayname are fine, but when I try to join another record I get this error:
{
"type":"error.SuiteScriptError",
"name":"SSS_INVALID_SRCH_COLUMN_JOIN",
"message":"An nlobjSearchColumn contains an invalid column join ID, or is not in proper syntax: logo.",
"stack":["doLookupFields(N/search/searchUtil.js)","<anonymous>(adhoc$-1$debugger.user:2)","<anonymous>(adhoc$-1$debugger.user:1)"],
"cause":{
"type":"internal error",
"code":"SSS_INVALID_SRCH_COLUMN_JOIN",
"details":"An nlobjSearchColumn contains an invalid column join ID, or is not in proper syntax: logo.",
"userEvent":null,
"stackTrace":["doLookupFields(N/search/searchUtil.js)","<anonymous>(adhoc$-1$debugger.user:2)","<anonymous>(adhoc$-1$debugger.user:1)"],
"notifyOff":false
},
"id":"",
"notifyOff":false,
"userFacing":false
}
This seems to happen no matter which record and field I try to join. What am I missing?
Although you can return results from multi-select fields, you cannot join to fields on records referenced by multi-select fields (which the subsidiary field on the item record is). Also, you cannot search the logo field on the subsidiary record (not listed in Search Columns under Subsidiary in the NetSuite Records Browser).
This means you have to load the Subsidiary record to get the logo field. In other words:
require(['N/record', 'N/search'], function(record, search) {
var item = search.lookupFields({
type: search.Type.ITEM,
id: 2086,
columns: ['itemid', 'displayname', 'subsidiary'],
});
var subID = item.subsidiary[0].value; //internal id of *first* subsidiary
var subRec = record.load({
type: record.Type.SUBSIDIARY,
id: subID
});
var logo = subRec.getText('logo'); //gets the file name - use getValue to get its ID instead
});
Note that if multiple subsidiaries are set on the item, this only gets the values for the first one. You could iterate through the item.subsidiary result to handle values for multiple subsidiaries if required.
I believe you can't access to the subsidiary record from a lookupfield, you should do a proper search.
https://system.netsuite.com/help/helpcenter/en_US/srbrowser/Browser2018_2/script/record/item.html
You can only join to tables allowed in the Item search object. Try looking for "Subsidiary..." in the Search Results tab within the UI. It's not there. Use the Schema Browser to determine what fields and joins are available.
You cannot think of a NetSuite search as you would any regular SQL search. You have to be cognizant of which fields and which joins can be utilized via the search object.
As people have mentioned, the subsidiary is not a join field available from the item record, one way to achieve what you are trying to do is:
Make a lookup to get the internal id of the subsidiary belonging to the desired item.
Then make a lookup to get the internal id of the logo image (file cabinet image) belonging to the previous subsidiary.
Make another lookup/load the image file to get the URL of the image/logo
You can try to combine the above steps in a single saved search but I think you might need to load the image file to get the URL.
This won't answer your question, but this may help out in the future. The records browser shows everything that you can search and join on, columns and filters, and field IDs. Very useful when building out searches.
NetSuite Records Browser - 2018.2

ArangoDb AQL Graph queries traversal example

I am having some trouble wrapping my head around how to traverse a certain graph to extract some data.
Given a collection of "users" and a collection of "places".
And a "likes" edge collection to denote that a user likes a certain place. The "likes" edge collection also has a "review" property to store a user's review about the place.
And a "follows" edge collection to denote that a user follows another user.
How can I traverse the graph to fetch all the places that I like with my review of the place and the reviews of the users I follow that also like the same place.
for example, in the above graph. I am user 6327 and I reviewed both places(7968 and 16213)
I also follow user 6344 which also happens to have reviewed the place 7968.
How can I get all the places that I like and the reviews of the people that I follow who also reviewed the same place that I like.
an expected output would be something like the following:
[
{
name:"my name",
place: "place 1",
id: 1
review,"my review about place 1"
},
{
name:"my name",
place: "place 2",
id: 2
review,"my review about place 2"
},
{
name:"name of the user I follow",
place: "place 2",
id: 2
review,"review about place 2 from the user I follow"
}
]
There are a number of ways to do this query, and it also depends on where you want to add parameters, but for the sake of simplicity I've built this quite verbose query below to help you understand one way of approaching the problem.
One way is to determine the _id of your user record, then find all the _id's of the friends you follow, and then to work out all related reviews in one query.
I take a different approach below, and that is to:
Determine the reviews you have written
Determine who you follow
Determine the reviews the people you follow have written
Merge together your reviews with those of the people you follow
It is possible to merge these queries together more optimally, but I thought it worth breaking them out like this (and showing the output of each stage as well as the final answer) to help you see what data is available.
A key thing to understand about AQL graph queries is how you have access to vertices, edges, and paths when you perform a query.
A path is an object in it's own right and it's worth investigating the contents of that object to better understand how to exploit it for path information.
This query assumes:
users document collection contains users
places document collection contains places
follows edge collection tracks users following other users
reviews edge collection tracks reviews people wrote
Note: When providing an id on each record I used the id of the review, because if you know that id you can fetch the edge document and get the id of both the user and the place as well as read all the data about the review.
LET my_reviews = (
FOR vertices, edges, paths IN 1..1 OUTBOUND "users/6327" reviews
RETURN {
name: FIRST(paths.vertices).name,
review_id: FIRST(paths.edges)._id,
review: FIRST(paths.edges).review,
place: LAST(paths.vertices).place
}
)
LET who_i_follow = (
FOR v IN 1..1 OUTBOUND "users/6327" follows
RETURN v
)
LET reviews_of_who_i_follow = (
FOR users IN who_i_follow
FOR vertices, edges, paths in 1..1 OUTBOUND users._id reviews
RETURN {
name: FIRST(paths.vertices).name,
review_id: FIRST(paths.edges)._id,
review: FIRST(paths.edges).review,
place: LAST(paths.vertices).place
}
)
RETURN {
my_reviews: my_reviews,
who_i_follow: who_i_follow,
reviews_of_who_i_follow: reviews_of_who_i_follow,
merged_reviews: UNION(my_reviews, reviews_of_who_i_follow)
}
The first vertex in paths.vertices is the starting vertex (users/6327)
The last vertex in paths.vertices is the end of the path, e.g. who you follow
The first edge in paths.edges is the review that the user made of the place
Here is another more compact version of the query that takes a param, the _id of the user that is 'you'.
LET target_users = APPEND(TO_ARRAY(#user), (
FOR v IN 1..1 OUTBOUND #user follows RETURN v._id
))
LET selected_reviews = (
FOR u IN target_users
FOR vertices, edges, paths in 1..1 OUTBOUND u reviews
LET user = FIRST(paths.vertices)
LET place = LAST(paths.vertices)
LET review = FIRST(paths.edges)
RETURN {
name: user.name,
review_id: review._id,
review: review.review,
place: place.place
}
)
RETURN selected_reviews

loopback relational database hasManyThrough pivot table

I seem to be stuck on a classic ORM issue and don't know really how to handle it, so at this point any help is welcome.
Is there a way to get the pivot table on a hasManyThrough query? Better yet, apply some filter or sort to it. A typical example
Table products
id,title
Table categories
id,title
table products_categories
productsId, categoriesId, orderBy, main
So, in the above scenario, say you want to get all categories of product X that are (main = true) or you want to sort the the product categories by orderBy.
What happens now is a first SELECT on products to get the product data, a second SELECT on products_categories to get the categoriesId and a final SELECT on categories to get the actual categories. Ideally, filters and sort should be applied to the 2nd SELECT like
SELECT `id`,`productsId`,`categoriesId`,`orderBy`,`main` FROM `products_categories` WHERE `productsId` IN (180) WHERE main = 1 ORDER BY `orderBy` DESC
Another typical example would be wanting to order the product images based on the order the user wants them to
so you would have a products_images table
id,image,productsID,orderBy
and you would want to
SELECT from products_images WHERE productsId In (180) ORDER BY orderBy ASC
Is that even possible?
EDIT : Here is the relationship needed for an intermediate table to get what I need based on my schema.
Products.hasMany(Images,
{
as: "Images",
"foreignKey": "productsId",
"through": ProductsImagesItems,
scope: function (inst, filter) {
return {active: 1};
}
});
Thing is the scope function is giving me access to the final result and not to the intermediate table.
I am not sure to fully understand your problem(s), but for sure you need to move away from the table concept and express your problem in terms of Models and Relations.
The way I see it, you have two models Product(properties: title) and Category (properties: main).
Then, you can have relations between the two, potentially
Product belongsTo Category
Category hasMany Product
This means a product will belong to a single category, while a category may contain many products. There are other relations available
Then, using the generated REST API, you can filter GET requests to get items in function of their properties (like main in your case), or use custom GET requests (automatically generated when you add relations) to get for instance all products belonging to a specific category.
Does this helps ?
Based on what you have here I'd probably recommend using the scope option when defining the relationship. The LoopBack docs show a very similar example of the "product - category" scenario:
Product.hasMany(Category, {
as: 'categories',
scope: function(instance, filter) {
return { type: instance.type };
}
});
In the example above, instance is a category that is being matched, and each product would have a new categories property that would contain the matching Category entities for that Product. Note that this does not follow your exact data scheme, so you may need to play around with it. Also, I think your API query would have to specify that you want the categories related data loaded (those are not included by default):
/api/Products/13?filter{"include":["categories"]}
I suggest you define a custom / remote method in Product.js that does the work for you.
Product.getCategories(_productId){
// if you are taking product title as param instead of _productId,
// you will first need to find product ID
// then execute a find query on products_categories with
// 1. where filter to get only main categoris and productId = _productId
// 2. include filter to include product and category objects
// 3. orderBy filter to sort items based on orderBy column
// now you will get an array of products_categories.
// Each item / object in the array will have nested objects of Product and Category.
}

Resources