OrientDB - match query for recommender - orientdb

I am trying to solve a simple MATCH query using OrientDB.
I would recommend for user A that bougth product P in common to user B.
When I try this query i Receive an error that states "an error to line 1 column 33"
SELECT d1.UserId, d2.UserId FROM (
MATCH
{class:User, as: U1} -buy-> {class:Product, as:P},
{class:User, as: U2} -buy-> {as:P},
{as:U2} -buy-> {class:Product, as P2}
RETURN U1 as d1, U2 as d2, O as o
)
Do you have any suggestion in order solve my issue?
Thanks
Rob

You miss a : at the end of last pattern, just try the following
SELECT d1.UserId, d2.UserId FROM (
MATCH
{class:User, as: U1} -buy-> {class:Product, as:P},
{class:User, as: U2} -buy-> {as:P},
{as:U2} -buy-> {class:Product, as:P2}
RETURN U1 as d1, U2 as d2, O as o
)

Finally I think the best solution is
SELECT d1.UserId, d2.UserId FROM (
MATCH
{class:User, as: U1, where: (UserId=12345) } -buy-> {class:Product, as:P},
{class:User, as: U2, where: (UserId<>12345)} -buy-> {as:OP},
{as:U2} -buy-> {class:Product, as:P2}
RETURN U2 as d2, P, OP
)
Thanks for all the help.
R.

Related

Converting relational tree to nested json document in postgres

In PostgreSQL have a relational data model that represents the hierarchy within an organization.
create table employee (
id integer primary key,
name varchar(40) not null,
supervisor_id integer references employee
);
Only the CEO has supervisor_id=NULL. Every other employ has a supervisor with some supervisor_id.
I would like export the data as a single nested json document
{
"id": 1,
"name": "Name of company's CEO",
"supervises": [
{
"id": 2,
"name": "Name of 1st EC member",
"supervises": [ ... nested employees ... ]
},
{
"id": 3,
"name": "Name of 2nd EC member",
"supervises": [ ... nested employees ... ]
}
...
]
}
I followed the example from https://www.postgresqltutorial.com/postgresql-recursive-query/ but it only helps me to identify all the employees top down along the reporting line using the WITH RECURSIVE clause.
I know that I need to start the aggregation with the employees that have the highest depth in the tree (not just leaf nodes) and aggregate them bottom up but I did not manage to write a query that does the job.
Thanks for your help!
At first glance your question sounds like quite simple using a recursive query, but it is not because of the list of employees who may have the same supervisor, which requires an aggregate function in order to build the corresponding json array, but the aggregate functions are not allowed in a recursive query ...
First step - We build the json objects for every employee and the corresponding array of supervised employees when he/she is a supervisor :
SELECT s.id AS father
, CASE WHEN array_agg(e.id) = array[NULL :: integer] THEN NULL ELSE array_agg(e.id) END AS children_list
, jsonb_build_object
( 'id', s.id
, 'name', s.name
, 'supervises', array_agg(e.id)
) AS json_tree
FROM employee AS s
LEFT JOIN employee AS e
ON s.id = e.supervisor_id
GROUP BY s.id, s.name
Second step - Using a recursive query, we go through the tree of employees in a top-down approach, we assign a rank to every employee and build the corresponding jsonpath that will be used in the next step :
WITH RECURSIVE elt AS
(
SELECT s.id AS father
, CASE WHEN array_agg(e.id) = array[NULL :: integer] THEN NULL ELSE array_agg(e.id) END AS children_list
, jsonb_build_object
( 'id', s.id
, 'name', s.name
, 'supervises', array_agg(e.id)
) AS json_tree
FROM employee AS s
LEFT JOIN employee AS e
ON s.id = e.supervisor_id
GROUP BY s.id, s.name
), list (father, json_tree, children_list, rank, path) AS
(
SELECT c.father, c.json_tree, c.children_list, 1, '{}' :: text[]
FROM elt AS c
LEFT JOIN elt AS f
ON array[c.father] <# f.children_list
WHERE f.father IS NULL
UNION ALL
SELECT c.father
, c.json_tree
, c.children_list
, f.rank + 1
, f.path || array['supervises',(array_position(f.children_list, c.father)-1) :: text]
FROM list AS f
INNER JOIN elt AS c
ON array[c.father] <# f.children_list
) --, ordered_list AS (
SELECT *
FROM list
ORDER BY rank DESC
Third step - We create the aggregate version of the jsonb_set function so that to build the final jsonb data while iterating on the previous resulting list
CREATE OR REPLACE FUNCTION jsonb_set(x jsonb, y jsonb, p text[], e jsonb, b boolean)
RETURNS jsonb LANGUAGE sql AS $$
SELECT CASE WHEN x IS NULL THEN e ELSE jsonb_set(x, p, e, b) END ; $$ ;
CREATE OR REPLACE AGGREGATE jsonb_set_agg(x jsonb, p text[], e jsonb, b boolean)
( STYPE = jsonb, SFUNC = jsonb_set) ;
Final query
WITH RECURSIVE elt AS
(
SELECT s.id AS father
, CASE WHEN array_agg(e.id) = array[NULL :: integer] THEN NULL ELSE array_agg(e.id) END AS children_list
, jsonb_build_object
( 'id', s.id
, 'name', s.name
, 'supervises', array_agg(e.id)
) AS json_tree
FROM employee AS s
LEFT JOIN employee AS e
ON s.id = e.supervisor_id
GROUP BY s.id, s.name
), list (father, json_tree, children_list, rank, path) AS
(
SELECT c.father, c.json_tree, c.children_list, 1, '{}' :: text[]
FROM elt AS c
LEFT JOIN elt AS f
ON array[c.father] <# f.children_list
WHERE f.father IS NULL
UNION ALL
SELECT c.father
, c.json_tree
, c.children_list
, f.rank + 1
, f.path || array['supervises',(array_position(f.children_list, c.father)-1) :: text]
FROM list AS f
INNER JOIN elt AS c
ON array[c.father] <# f.children_list
)
SELECT jsonb_set_agg(NULL :: jsonb, path, json_tree, true ORDER BY rank ASC)
FROM list
test result in dbfiddle

Can I mix LINQ and Fluent Syntax in EF Core?

I have the following query which works just fine (inasmuch as it generates the proper SQL command):
var sites = from sm in this.context.SiteMemberships
join s in this.context.Sites on sm.SiteUid equals s.SiteUid
join sd in this.context.SiteData on s.SiteUid equals sd.SiteUid
join p in this.context.Providers on s.ProviderUid equals p.ProviderUid
join r in this.context.Regions on p.RegionUid equals r.RegionUid
join o in this.context.Offices on s.OfficeUid equals o.OfficeUid
join u in this.context.Users on sm.UserUid equals u.UserUid
where
u.Email == userEmail ||
EF.Functions.Like(s.Classification, "117400-74%")
select new {
s.Field1,
sd.Field2,
p.Field3
};
As you can see, it's a complex query and trying to use the Fluent API is very cumbersome. However, I want the 'Where' clause to be programmable. Based on what features the current user has, they should be able to search on different criteria. A Level 1 support person can search only on an exact match of the Classification field, but a Level 2 support person can search on wildcards.
Is there any way to mix the syntax. What I want is something like this:
var (sites = from sm in this.context.SiteMemberships
join s in this.context.Sites on sm.SiteUid equals s.SiteUid
join sd in this.context.SiteData on s.SiteUid equals sd.SiteUid
join p in this.context.Providers on s.ProviderUid equals p.ProviderUid
join r in this.context.Regions on p.RegionUid equals r.RegionUid
join o in this.context.Offices on s.OfficeUid equals o.OfficeUid
join u in this.context.Users on sm.UserUid equals u.UserUid)
.Where(g => this.GetSearchCriteria(g))
.Select(g => g);
You need to add select new { s, sd, p, r, o, u } after your last join
var sites = (from sm in this.context.SiteMemberships
join s in this.context.Sites on sm.SiteUid equals s.SiteUid
join sd in this.context.SiteData on s.SiteUid equals sd.SiteUid
join p in this.context.Providers on s.ProviderUid equals p.ProviderUid
join r in this.context.Regions on p.RegionUid equals r.RegionUid
join o in this.context.Offices on s.OfficeUid equals o.OfficeUid
join u in this.context.Users on sm.UserUid equals u.UserUid
select new { s, sd, p, r, o, u })
.Where(g => this.GetSearchCriteria(g))
.Select(g => g);

Use PostgreSQL array_to_string in Phoenix

I have 2 n-n relationships between posts and tags tables. This is my query in Postgres:
SELECT t0.*, array_to_string(array_agg(t2.tag), ', ')
FROM "posts" AS t0
INNER JOIN "posts_tags" AS t1 ON (t0.id = t1.post_id)
INNER JOIN "tags" AS t2 ON (t1.tag_id = t2.id)
GROUP BY t0.id
I tried to use something similar in Ecto:
Repo.all(
from p in Post,
join: a in Post_Tag, on: p.id == a.post_id,
join: t in Tag, on: a.tag_id == t.id,
select: {p, array_to_string(array_agg(t.tag),', ')},
limit: ^limit,
offset: ^offset,
group_by: p.id
)
But I get this error:
(Ecto.Query.CompileError) `array_to_string(array_agg(t.tag()), ', ')` is not a valid query expression.
You will need to use a fragment/1 for the array_to_string(...) portion of your query. I have not tested it, but it should look something like:
fragment("array_to_string(array_agg(?), ', ')", t.tag)

Providing subquery in query parameters

I need to implement the following query with Ecto, however it doesn't compile saying the subquery statement is not a valid expression.
query = from p in Passphrase,
left_join: pi in PassphraseInvalidation, on: p.id == pi.target_passphrase_id,
join: u in User, on: p.user_id == u.id,
where: p.passkey == ^passkey and
is_nil(pi.inserted_at) and
p.inserted_at > ago(5, "month") and
p.inserted_at > subquery(from pr in PasswordReset,
where: pr.user_id == u.id,
select: max(pr.inserted_at)),
select: {u, p}
user = Repo.one!(query)
(Ecto.Query.CompileError) subquery(from(pr in PasswordReset, where: pr.user_id() == ^u.id(), select: max(pr.inserted_at()))) is not a valid query expression.
The equivalent PgSQL query would be something like:
SELECT u.*, p.*
FROM passphrases p
LEFT OUTER JOIN passphrase_invalidations pi ON p.id = pi.target_passphrase_id
INNER JOIN users u ON p.user_id = u.id
WHERE p.passkey = '/* some passkey */' AND
pi.inserted_at IS NULL AND
u.id = 2 AND
p.inserted_at > (SELECT max(pr.inserted_at)
FROM password_resets pr
WHERE pr.user_id = u.id)
Is there a way to implement that, or am I missing something?
As of Ecto 2.1.0, according to https://hexdocs.pm/ecto/Ecto.Query.html#subquery/1, subqueries cannot be use in where:
Subqueries are currently only supported in the from and join fields.
You can use fragment and put the entire subquery in it for now:
p.inserted_at > fragment("(SELECT max(pr.inserted_at) FROM password_resets pr WHERE pr.user_id = u.id)")
where just became available (Ecto 3.4.3)
https://hexdocs.pm/ecto/Ecto.Query.html#subquery/2

Postgresql like into if clause

I got this PostgreSQL query:
SELECT Sum(CASE
WHEN p LIKE '%add%'
AND p NOT LIKE '%add post%' THEN counter
ELSE 0
end) AS "AGREGAR",
Sum(IF p LIKE '%add%' AND p NOT LIKE '%add post%' THEN 1 ELSE 0 END IF ) AS P
FROM (SELECT l.action AS p,
Count(l.userid) AS counter
--r.name
FROM mdl_log AS l
JOIN mdl_role_assignments AS ra
ON l.userid = ra.userid
JOIN mdl_role AS r
ON ra.roleid = r.id
JOIN mdl_course AS c
ON c.id = l.course
AND course = 12033
AND roleid = 3
GROUP BY roleid,
l.action,
r.name
ORDER BY l.action) AS "P"
But when i execute it, i get this error:
ERROR: error de sintaxis en o cerca de «p»
LINE 6: Sum(IF p LIKE '%add%' AND p NOT LIKE '%add pos...
^
********** Error **********
ERROR: error de sintaxis en o cerca de «p»
SQL state: 42601
Character: 216
It's like if the engine don't recognise the "p" alias in the sub-query, but when I put "p" without anymore in the select, it works fine. What am I doing wrong?
Change the line with the if conditional to a normal case expression:
Sum(CASE WHEN p LIKE '%add%' AND p NOT LIKE '%add post%' THEN 1 ELSE 0 END) AS P
and you should be fine. The if statement is used in the PL/pgSQL language used in functions and triggers etc.