Insert data based on Where condition in Neo4j - node.js

I want to insert data based on other lebel/collection. I have 2 lebel/collection ( unit, user ) and they have 1 relation (Business) between them, And I want to insert data into unit based on their relationship. My cypher query is given below:
MATCH (u:Units)<-[:Business]-(s:Users)
WHERE s.id = 'some-user-id'
WITH count(u) as numOfUnit
// return number of units connected with user
// if numOfUnit is smaller then 2
// insert/merge new data into Units lebel/collection
// with relation between them
MERGE ( bu:Units {name:'some-name-01', info:'some-info-01' })
WHERE numOfUnit < 2
ON CREATE SET
bu.id = '${uuid()}',
bu.created = '${moment().toISOString()}'
ON MATCH SET
bu.updated = '${moment().toISOString()}'
WITH bu as bu
MATCH ( bs:Users {id: 'some-user-id' })
MERGE (bs)-[r:Business]-(bu)
RETURN properties(bu)
After running above query, It shown following error:
{ Neo4jError: Invalid input 'H': expected 'i/I' (line 10, column 18
(offset: 377))
" ON CREATE SET"
^
at Neo4jError.Error (native)
at new Neo4jError (../../../../node_modules/neo4j-driver/lib/v1/error.js:76:132)
at newError (../../../../node_modules/neo4j-driver/lib/v1/error.js:66:10)
at Connection._handleMessage (../../../../node_modules/neo4j-driver/lib/v1/internal/connector.js:355:56)
at Dechunker.Connection._dechunker.onmessage (../../../../node_modules/neo4j-driver/lib/v1/internal/connector.js:286:12)
at Dechunker._onHeader (../../../../node_modules/neo4j-driver/lib/v1/internal/chunking.js:246:14)
at Dechunker.AWAITING_CHUNK (../../../../node_modules/neo4j-driver/lib/v1/internal/chunking.js:199:21)
at Dechunker.write (../../../../node_modules/neo4j-driver/lib/v1/internal/chunking.js:257:28)
at NodeChannel.self._ch.onmessage (../../../../node_modules/neo4j-driver/lib/v1/internal/connector.js:259:27)
at TLSSocket.<anonymous> (../../../../node_modules/neo4j-driver/lib/v1/internal/ch-node.js:308:16)
code: 'Neo.ClientError.Statement.SyntaxError',
name: 'Neo4jError' }

The docs about WHERE clause says:
WHERE adds constraints to the patterns in a MATCH or OPTIONAL MATCH
clause or filters the results of a WITH clause.
That is: WHERE cannot be used in conjunction with MERGE.
As stated in comments, your query should work putting the WHERE condition after the WITH clause, because you can use WHERE to filter results of a WITH.
MATCH (u:Units)<-[:Business]-(s:Users)
WHERE s.id = 'some-user-id'
WITH count(u) as numOfUnit
WHERE numOfUnit < 2
MERGE ( bu:Units {name:'some-name-01', info:'some-info-01' })
ON CREATE SET
bu.id = '${uuid()}',
bu.created = '${moment().toISOString()}'
ON MATCH SET
bu.updated = '${moment().toISOString()}'
WITH bu as bu
MATCH ( bs:Users {id: 'some-user-id' })
MERGE (bs)-[r:Business]-(bu)
RETURN properties(bu)

Related

Problems with the BQL "IN<>" statement

The requirement I have is to get a list of all discount codes defined in an instance and which ones a particular customer is currently assigned to, in the case given CustomerID=28. I further have to include only discount codes that naturally will be applicable to customers. There are only 3 of these; "Customer", "Customer and Item", "Customer and Item price Class". These are ARDiscount.ApplicableTo containing "CU", "CP","CI"
Select a.CompanyID, a.DiscountID, a.DiscountSequenceID, b.ApplicableTo, c.CustomerID
From DiscountSequence a
Join ARDiscount b On a.CompanyID = b.CompanyID and a.DiscountID = b.DiscountID
Left Outer Join DiscountCustomer c On a.CompanyID = c.CompanyID
And a.DiscountID = c.DiscountID
And a.DiscountSequenceID = c.DiscountSequenceID
And (IsNull(c.CustomerID,0) = 0 OR c.CustomerID = 72)
Where a.CompanyID = 2
And b.ApplicableTo In ('CU','CP','CI')
Order By a.DiscountID, a.DiscountSequenceID
I created data view delegate to return the 4 columns I need to display and in the view I created
to read the data like the SQL query above I used the BQL "IN<>" statement like this. The method was taken directlty from a blog post found here :
https://asiablog.acumatica.com/2017/11/sql-in-operator-in-bql.html
Object[] applicableTovalues = new String[] { "CP","CI","CU" }; // Customer and Price Class // Customer and Item// Customer
var Results = PXSelectJoin<DiscountSequence
, InnerJoin<ARDiscount, On<DiscountSequence.discountID, Equal<ARDiscount.discountID>>
, LeftJoin<DiscountCustomer, On<DiscountSequence.discountID, Equal<DiscountCustomer.discountID>,
And<DiscountSequence.discountSequenceID, Equal<DiscountCustomer.discountSequenceID>,
And<Where<DiscountCustomer.customerID, Equal<Current<Customer.bAccountID>>,
Or<DiscountCustomer.customerID, IsNull>>>>>>>
, Where<DiscountSequence.discountID, IsNotNull
, And<ARDiscount.applicableTo, In<Required<ARDiscount.applicableTo>>>>
, OrderBy<Asc<DiscountSequence.discountID, Asc<DiscountSequence.discountSequenceID>>>
>.Select(Base, applicableTovalues);
The problem is that the resulting SQL server select statement caught with TRACE only includes the first of the three IN values (''CU'') leaving (CI and CU) out.
I was expecting all three values in the IN statement like this : AND [ARDiscount].[ApplicableTo] IN ( ''CP'', ''CI'', ''CU'')
exec sp_executesql N'SELECT [DiscountSequence].[DiscountID], [DiscountSequence].[DiscountSequenceID], [DiscountSequence].[LineCntr],
<snip>
[DiscountCustomer].[CreatedDateTime], [DiscountCustomer].[LastModifiedByID], [DiscountCustomer].[LastModifiedByScreenID], [DiscountCustomer].[LastModifiedDateTime]
FROM [DiscountSequence] [DiscountSequence] INNER JOIN [ARDiscount] [ARDiscount] ON ( [ARDiscount].[CompanyID] = 2) AND [DiscountSequence].[DiscountID] = [ARDiscount].[DiscountID]
LEFT JOIN [DiscountCustomer] [DiscountCustomer] ON ( [DiscountCustomer].[CompanyID] = 2) AND [DiscountSequence].[DiscountID] = [DiscountCustomer].[DiscountID]
AND [DiscountSequence].[DiscountSequenceID] = [DiscountCustomer].[DiscountSequenceID] AND ( [DiscountCustomer].[CustomerID] = #P0 OR [DiscountCustomer].[CustomerID] IS NULL )
WHERE ( [DiscountSequence].[CompanyID] = 2)
AND ( [DiscountSequence].[DiscountID] IS NOT NULL
AND [ARDiscount].[ApplicableTo] IN ( ''CU''))
ORDER BY [DiscountSequence].[DiscountID], [DiscountSequence].[DiscountSequenceID]
OPTION(OPTIMIZE FOR UNKNOWN) /* AR.30.30.00 */',N'#P0 int',#P0=39
The issue is passing the array into the 'params' parameter. It thinks that you are passing a list of parameters into the bql query instead of a single array as a parameter.
If you cast it as follows it should work:
.Select(Base, (object)applicableTovalues);

Differentiating missing documents in MongoDB find()

When I run the following query I am getting the document that matches as normal which is "LON" in this case.
But is there any way that I can make the response seperately return the values that didn't match or found, which is "BUJ" in this case. Instead of running a for loop for individual values.
ports = [
"LON",
"BUJ"
];
findDatas = async(coll, values, key) => {
let datas = await coll.find({[key] : values});
// let datas = await coll.find().where(key).in(values);
console.log(datas)
}
findDatas(airportsModel, ports, "iata_code")
In my DB I only have "LON" which mean "BUJ" is not found. So is there any way to make mongo to tell that the given values haven't been found? along with the found ones.
This code dynamically creates a $match stage to find the documents, the uses $facet to split into 2 pipelines, the first returns the documents, the second uses a $group stage created from the input array to count how many documents match each element. The result should be a document with 2 fields: documents and counts
matchdata={};
matchdata[key]={"$in":ports};
groupdata = {_id:null};
ports.forEach(function(p){
groupdata[p] = {"$sum":{"$cond":[{"$eq":["$" + key, p ]},1,0]}}
});
db.coll.aggregate([
{$match:matchdata},
{$facet:{
documents:[{$match:{}}],
counts:[{$group: groupdata},{$project:{_id:0}}]
}}
])

Getting pagination to work with one to many join

I'm currently working on a database with several one-to-many and many-to-many relationships and I am struggling getting ormlite to work nicely.
I have a one-to-many relationship like so:
var q2 = Db.From<GardnerRecord>()
.LeftJoin<GardnerRecord, GardnerEBookRecord>((x, y) => x.EanNumber == y.PhysicalEditionEan)
I need to return a collection of ProductDto that has a nested list of GardnerEBookRecord.
Using the SelectMult() technique it doesn't work because the pagination breaks as I am condensing the left joined results to a smaller collection so the page size and offsets are all wrong (This method: How to return nested objects of many-to-many relationship with autoquery)
To get the paging right I need to be able to do something like:
select r.*, count(e) as ebook_count, array_agg(e.*)
from gardner_record r
left join gardner_e_book_record e
on r.ean_number = e.physical_edition_ean
group by r.id
There are no examples of this in the docs and I have been struggling to figure it out. I can't see anything that would function like array_agg in the Sql object of OrmLite.
I have tried variations of:
var q2 = Db.From<GardnerRecord>()
.LeftJoin<GardnerRecord, GardnerEBookRecord>((x, y) => x.EanNumber == y.PhysicalEditionEan)
.GroupBy(x => x.Id).Limit(100)
.Select<GardnerRecord, GardnerEBookRecord>((x, y) => new { x, EbookCount = Sql.Count(y), y }) //how to aggregate y?
var res2 = Db.SelectMulti<GardnerRecord, GardnerEBookRecord>(q2);
and
var q2 = Db.From<GardnerRecord>()
.LeftJoin<GardnerRecord, GardnerEBookRecord>((x, y) => x.EanNumber == y.PhysicalEditionEan)
.GroupBy(x => x.Id).Limit(100)
.Select<GardnerRecord, List<GardnerEBookRecord>>((x, y) => new { x, y });
var res = Db.SqlList<object>(q2);
But I can't work out how to aggregate the GardnerEBookRecord to a list and keep the paging and offset correct.
Is this possible? Any workaround?
edit:
I made project you can run to see issue:
https://github.com/GuerrillaCoder/OneToManyIssue
Database added as a docker you can run docker-compose up. Hopefully this shows what I am trying to do
Npgsql doesn't support reading an unknown array or records column type, e.g array_agg(e.*) which fails with:
Unhandled Exception: System.NotSupportedException: The field 'ebooks' has a type currently unknown to Npgsql (OID 347129).
But it does support reading an array of integers with array_agg(e.id) which you can query instead:
var q = #"select b.*, array_agg(e.id) ids from book b
left join e_book e on e.physical_book_ean = b.ean_number
group by b.id";
var results = db.SqlList<Dictionary<string,object>>(q);
This will return a Dictionary Dynamic Result Set which you'll need to combine into a distinct id collection to query all ebooks referenced, e.g:
//Select All referenced EBooks in a single query
var allIds = new HashSet<int>();
results.Each(x => (x["ids"] as int[])?.Each(id => allIds.Add(id)));
var ebooks = db.SelectByIds<EBook>(allIds);
Then you can create a dictionary mapping of id => Ebook and use it to populate a collection of ebooks entities using the ids for each row:
var ebooksMap = ebooks.ToDictionary(x => x.Id);
results.Each(x => x[nameof(ProductDto.Ebooks)] = (x["ids"] as int[])?
.Where(id => id != 0).Map(id => ebooksMap[id]) );
You can then use ServiceStack AutoMapping Utils to convert each Object Dictionary into your Product DTO:
var dtos = results.Map(x => x.ConvertTo<ProductDto>());

How to return insert query result values using pg-promise helpers

I am using pgp.helpers.insert to save data into PostgreSQL which is working well. However, I need to return values to present in a response. I am using:
this.collection.one(this.collection.$config.pgp.helpers.insert(values, null, 'branch'))
which returns no data.
What I want to be able to do is return the branch id after a successful insert, such as:
INSERT into branch (columns) VALUES (values) RETURNING pk_branchID
Simply append the RETURNING... clause to the generated query:
var h = this.collection.$config.pgp.helpers;
var query = h.insert(values, null, 'branch') + 'RETURNING pk_branchID';
return this.collection.one(query);
You must have a large object there if you want to automatically generate the insert. Namespace helpers is mostly valued when generating multi-row inserts/updates, in which case a ColumnSet is used as a static variable:
var h = this.collection.$config.pgp.helpers;
var cs = new h.ColumnSet(['col_a', 'col_b'], {table: 'branch'});
var data = [{col_a: 1, col_b: 2}, ...];
var query = h.insert(data, cs) + 'RETURNING pk_branchID';
return this.collection.many(query);
Note that in this case we do .many, as 1 or more rows/results are expected back. This can even be transformed into just an array of id-s:
return this.collection.map(query, [], a => a.pk_branchID);
see: Database.map

Arangodb LET variable from AQL for use it in FILTER

I have collection 'Notifier'.
Given the following example document of this collection:
{
"timestamp": 1413543986,
"message": "message",
"readed": {
"user_8": 0,
"user_9": 0,
"user_22": 0
},
"type": "1014574149174"
}
I try find this document with the following AQL:
FOR n IN Notifier
LET usersFound = TO_BOOL(
FOR user IN n.readed
FILTER user == 'user_8'
LIMIT 1
RETURN user
)
FILTER usersFound==true
RETURN n
I get the following error:
[1501] syntax error, unexpected FOR declaration, expecting ) near 'OR
user IN n.readed
FILT...' at position 3:10
How can I write this AQL correctly using LET?
Your query is almost correct, there are 2 minor issues.
1) TO_BOOL() are chars to trigger the correspoding function, you now want to insert a subquery, that is triggered by wrapping an AQL statement in additional ().
So instead of TO_BOOL(AQL) you have to use: TO_BOOL((AQL));
2) n.readed is a JSON object, FOR x IN expectes a list. As you are iterating over the Attributes of n.readed you can use ATTRIBUTES(n.readed) here.
Here is the correct solution for your example:
FOR n IN Notifier
LET usersFound = TO_BOOL(
(FOR user IN ATTRIBUTES(n.readed)
FILTER user == 'user_8'
LIMIT 1
RETURN user)
)
FILTER usersFound==true
RETURN n
PS:
If you are only looking for the exisence of an attribute and do not want to do further filtering you can get that a bit easier using HAS:
FOR n IN Notifier
LET usersFound = HAS(n.readed, 'user_8')
FILTER usersFound==true
RETURN n
If you are just looking for a presence of some field which value can be converted to boolean value you don't need to bother with LET and subqueries. Following query uses HAS function to determine presence of specified field inside document and BOOL to convert it's value to boolean representation:
LET search = 'user_8'
FOR n IN Notifier
FILTER HAS(n.readed, search) == true && TO_BOOL(n.readed[search])
RETURN n

Resources