Neo4j - querying N items per group - graph

The following is my query:
MATCH (u:User{id:1})-[r:FOLLOWS]->(p:Publisher)<-[:PUBLISHED]-(i:Item)-[:TAGGED]->(t:Tag)<-[f:FOLLOWS]-u
RETURN a, count(t) ORDER BY count(k) DESC LIMIT 100
So User can follow Publisher and a Tag. The query find the items, that user may like by counting matching tags.
Suppose there two properties, MIN and MAX, on relationship u-r->p. These properties specify, how many items user wants to see from each publisher. How can I rewrite the query to allow this?

Here is one thought. Say for instance that the FOLLOWS relationship has a min value and a max value set. You could use the following query to limit the data that is returned by the query based on those values. I have not rewritten the entire query to include the tags and a limit there either.
// find the user and the publisher and the relationship
// which has the min/max parameters
match (u:User {id: 1})-[r:FOLLOWS]->(p:Publisher)
with u, p, r
// macth the items that the publisher published
match p-[:PUBLISHED]-(i:Item)
// order them just because we can
with u, p, r, i
order by i.name
// collect the ordered items as the total list of items
with u, p, r, collect(i.name) as items
// make sure the collection is >= the minimum size of the list
// if so then return the items in the collection up to the max length
// otherwise return and empty collection
// you might want to do something else
with u, p, r, case
when length(items) >= r.min then items[..r.max]
else []
end as items
return u.name, p.name, r.min, r.max, items
The unfortunate thing about this is that you have already performed the query to get the items and are just filtering them out for display purposes. It would be nice to know the person's preference before hand so you could apply the max limit in the query for the items using limit and a parameter. This would eliminate unnecessary database hits. Depending on the publisher there could be many, many items and limiting them up front might be advantageous.
Here are a couple of variations to experiment with too. You could also do something like this...
// slight variation where the minimum is enforced with where instead of case
match (u:User {id: 1})-[r:FOLLOWS]->(p:Publisher)
with u, p, r
match p-[:PUBLISHED]-(i:Item)
with u, p, r, i
order by i.name
with u, p, r, collect(i.name) as items
where length(items) >= r.min
return u.name, p.name, items[..r.max]
or even this...
// only results actually between the min and max are returned
match (u:User {id: 1})-[r:FOLLOWS]->(p:Publisher)
with u, p, r
match p-[:PUBLISHED]-(i:Item)
with u, p, r, i
order by i.name
with u, p, r, collect(i.name) as items
where length(items) >= r.min
and length(items) <= r.max
return u.name, p.name, items[..r.max]

Related

Applying conditional comparative (lt, eq, gt) expressions for items that are don't exist in the DynamoDB table

My objective is to insert an item in a DynamoDB table if it does not exist already and one of attributes has a minimum value.
The hash-key is productId and rage-key is locationId. The attribute that should have a minimum value if price. The minimum value is 0.0.
e.g.
if an item with primary key that does not exist in the table, has price as 10.0, it will be inserted. But in case that item has price as 0.0, it should be rejected.
if the item already existed and the price was 0.0, I want to update the price to 0.0.
The code that I am trying to use is:
PrimaryKey primaryKey = new PrimaryKey(
"productId", productId,
"locationId", locationId
);
Item newItem = new Item()
.withPrimaryKey(primaryKey)
.withDouble("price", 0.0d);
PutItemSpec putItemSpec = new PutItemSpec()
.withItem(newItem)
.withConditionExpression("attribute_not_exists(productId) AND attribute_not_exists(locationId) AND (price > :lowerBound)")
.withValueMap(new ValueMap().withNumber(":lowerBound", 0.0d));
PutItemOutcome putItemOutcome = table.putItem(putItemSpec);
When I run this I get ConditionalCheckFailedException. In order to isolate the issue I tried breaking the condition into
attribute_not_exists(productId) AND attribute_not_exists(locationId)
price > :lowerBound
I found that attribute_not_exists(productId) AND attribute_not_exists(locationId) by itself work fine.
price > :lowerBound fails when used by itself. I tried values like 0.0, 18000.0 and even -2.0. They all fail due to ConditionalCheckFailedException.
I wonder if my understanding about the condition-expressions in incorrect? Is it even possible to apply comparative expressions to items that do no exist yet?
Any suggestions are welcome.
Condition expressions in DynamoDB describe the expected item in DynamoDB before the write. There's no need to use a condition expression to describe the expectations of the value you're writing, because you can just validate those expectations yourself before doing the write.

Use linq in the where clause

I have 2 tables _customerRepository.GetAllQueryable() and _customerSettlementRepository.GetAllQueryable().
In table _customerSettlementRepository.GetAllQueryable(), I have column ApplyD (date), after joining these two together, I want to find out max ApplyD in the where clause. This is my code:
var settlements = from c in _customerRepository.GetAllQueryable()
join cs in _customerSettlementRepository.GetAllQueryable() on new {c.CustomerMainC, c.CustomerSubC}
equals new {cs.CustomerMainC, cs.CustomerSubC} into c1
where cs.ApplyD == (c1.Select(b=>b.ApplyD).Max())
select new CustomerSettlementViewModel()
{
TaxRate = cs.TaxRate
};
It's remarkable that quite often in these questions people come up with an SQL(-like) statement without specification of the goal they want to reach. Hence it is impossible to see whether the provided statement fulfills the requirements.
Anyway, it seems you have something like Customers (in CustomerRepository) and CustomerSettlements in CustomerSettlementRepository.
both Customers and CustomerSettlements have a CustomerMainC and a CustomerSubC. You want to join Customers and CustomerSettlements on these two properties.
A CustomerSettlement also has an ApplyD and a TaxRate.
You only want to keep the join results where ApplyD has the maximum value of ApplyD
Finally, from every remaining join result you want to create one CustomerSettlementViewModel object with the value of the TaxRate in the join result that was taken from the CustomerSettlement.
Now that I wrote this, it baffles me why you need to join in the first place, because you only use values from the CustomerSettlements, not from the Customer.
Besides, if two Customers are joined with the same CustomerSettlements. this will result in two equal CustomerSettlementViewModel objects.
But let's assume this is really what you want.
In baby steps:
IQueryable<Customer> customers = ...
IQueryable<CustomerSettlement> customerSettlements = ...
var joinResults = customers.Join(customerSettlements
customer => new {customer.CustomerMainC, customer.CustomerSubC},
settlement => new {settlement.CustomerMainC, settlement.CustomerSubC}
(customer, settlement) => new
{
settlement.ApplyD,
settlement.TaxRate,
// add other properties from customers and settlements you want in the end result
});
In words: take all Customers and all CustomerSettlements. From every Customer create an object having the values of the customer's CustomerMainC and CustomerSubC. Do the same from every CustomerSettlement. When these two objects are equal, create a new object, having the values of the CustomerSettlement's ApplyD and TaxRate (and other properties you need in the end result)
Note that this is still an IQueryable. No query is performed yet.
From this joinResult you only want to keep those objects that have the value of ApplyD that equals the maximum value of ApplyD.
This question on StackOverflow is about selecting the records with the max value. The idea is to group the records into groups with the same value for ApplyD. Then order the groups in descending Key order and take the first group.
var groupsWithSameApplyD = joinResults.GroupBy(
joinedItem => joinedItem.ApplyD,
joinedItem => new CustomerSettlementViewModel()
{
TaxRate = orderedItem.TaxRate,
// add other values from joinedItems as needed
});
Every group in groupsWithSameApplyD has a key equal to ApplyD. The group consists of CustomerSettlementViewModel objects created frome the joinedItems that all have the same ApplyD that is in the Key of the group.
Now order by descending:
var orderedGroups = groupsWithSameApplyD.OrderByDescending(group => group.Key);
The first group contains all elements that had the largest ApplyD. Your desired result is the sequence of elements in the group.
If there is no group at all, return an empty sequence. Note if a sequence is requested as result, it is always better to return an empty sequence instead of null, so callers can use the returned value in a foreach without having to check for null return
var result = orderedGroups.FirstOrDefault() ??
// if no groups at all, return empty sequence:
Enumerable.Empty<CustomerSettlementViewModel>();
Note: the FirstOrDefault is the first step where the query is actually performed. If desired you could put everything in one big query. Not sure if this would improve readability and maintainability.
This is my syntet error, I need to write this since the first
var settlements = from c in _customerRepository.GetAllQueryable()
join cs in _customerSettlementRepository.GetAllQueryable() on new {c.CustomerMainC, c.CustomerSubC}
equals new {cs.CustomerMainC, cs.CustomerSubC}
select new CustomerSettlementViewModel()
{
TaxRate = cs.TaxRate
};
settlements = settlements.Where(p => p.ApplyD == settlements.Max(b => b.ApplyD));

How can I calculate a field based on fields in other tables while inserting a record (in a procedure)

I'm sorry for the confusing title, but couldn't find another way to ask it.
Let's say I want to write a procedure add_salesline. I enter all the fields with parameters, except subtotal. Subtotal (just the price for the salesline) needs to be calculated based on fields in other tables such as productprice in the table products, pricereduction in the table promotion, etc. (based on the properties).
How can I do this? I've been trying to solve this problem for a good week now, and it's just not working...
Presumably one of the parameters passed to procedure add_salesline() is productid, or whatever. So you use that to SELECT products.productprice, promotion.pricereduction and whatever else you need to perform the calculation.
The purpose of writing a stored procedure is to associate several calls into a single program unit. So add_salesline() might look something like this (lots of caveats because your question is very light on details):
create or replace procedure add_salesline(
p_orderno in salesline.orderno%type
, p_salesqty in salesline.salesqty%type
, p_productid in products.productid%type
)
is
new_rec salesline%rowtype;
begin
new_rec.orderno := p_orderno;
new_rec.salesqty := p_salesqty;
new_rec.productid := p_productid;
select p_salesqty * (p.productprice * nvl(pp.pricereduction, 1))
into new_rec.subtotal
from products p
left outer join promotion pp
on pp.productid = p.productid
where p.productid = p_productid
;
insert into salesline
value new_rec;
end;
This code assumes pricereduction is a rate. If the value is an absolute discount the formula will be different (p.productprice - nvl(pp.pricereduction, 0)). Or if it's a replacement price: coalesce(pp.pricereduction, p.productprice).

Get relationship node from 2 edges in 1 cypher query

I need a cypher query that retrieves the weight of two edges at the same time. This is my attempt:
MATCH (n:User)-[r:VIEWED|UPDATED]->(f:File) WHERE f.id IN 'some_id','another_id'] RETURN n, r.weight, ORDER BY r.weight DESC
The result contains two lines for each user who updated and viewed the file. However, I want the result in one line. For example: user: x - updated: 12 - viewed:15
How can I do this?
UPDATED:
try:
MATCH (f:File)
OPTIONAL MATCH (n:User)-[r1:VIEWED]->(f:File)
OPTIONAL MATCH (n:User)-[r2:UPDATED]->(f:File)
where f.id IN ['some_id','another_id']
return n,
sum(r1.weight) as totalViewedWeight,
sum(r2.weight) as totalUpdatedWeight
MATCH (n:User)-[r:VIEWED|UPDATED]->(f:File)
WHERE f.id IN ['some_id','another_id']
RETURN n, collect(type(r)), collect(r.weight)

cypher: how to return distinct relationship types?

How to return the distinct relationship types from all paths in cypher?
Example query:
MATCH p=(a:Philosopher)-[*]->(b:SchoolType)
RETURN DISTINCT EXTRACT( r in RELATIONSHIPS(p)| type(r) ) as RelationshipTypes
This returns a collection for each path p.
I would like to return a single collection contain the distinct relationship types across all collections.
Here is a link to a graph gist to run the query-
http://gist.neo4j.org/?7851642
You might first collect all relationships on the matched path to a collection "allr", and then get the collection of distinct type(r) from the collection of all relationships,
MATCH p=(a:Philosopher)-[rel*]->(b:SchoolType)
WITH collect(rel) AS allr
RETURN Reduce(allDistR =[], rcol IN allr |
reduce(distR = allDistR, r IN rcol |
distR + CASE WHEN type(r) IN distR THEN [] ELSE type(r) END
)
)
Note, each element 'rcol' in the collection "allr" is in turn a collection of relationships on each matched path.

Resources