Finding all edges joining nodes within a set of nodes in postgres - postgresql

I have data stored in two tables called objects and object_relationships.
It's a simple self referential many to many.
Objects table
id
description
type
1
Subject: an email about birds
email
2
Subject: birds
email
3
john
person
4
mark
person
5
lex
person
6
Subject: ants
email
words between tables to fix SE formatting
Object_relationships table
object_id
child_id
type
1
3
to
3
1
from
6
4
to
5
4
family
2
5
from
5
3
friends
Using an initial query like
select * from objects where description like '%birds%' or description like '%lex%' or description like '%john%'
Returns id's [1, 2, 3, 5]
I then want every edge between these "nodes"
specifically:
1 - to - 3
3 - from - 1
2 - from - 5
5 - friends - 3
The code I have for the getting the edges using joins but it's wrong because it pulls in new nodes and I can't figure out how to exclude nodes outside the initial query.
I think my approach is wrong because the query does not even consider the parents of the objects. The json build object is to quickly plot the output in any cytoscape compatible viewer
json_build_object(
'source', base.object_id,
'target', base.child_id,
'type', base.child_type
) as edge1,
json_build_object(
'source', base.child_id,
'target', base.child2_id,
'type', base.child2_type
) as edge2 from
(
with parent as (
select
distinct unnest(array[base.object_id, base.child_id, base.child2_id]) as id
from
(
select
o.id as object_id,
o.type,
or1.child_object_id as child_id,
or1."type" as child_type
or2.child_object_id as child2_id,
or2."type" as child2_type
from
objects o
join object_relationships or1 on
or1.object_id = o.id
join objects o1 on
o1.id = or1.child_object_id
join object_relationships or2 on or2.object_id = o1.id
join objects o2 on o2.id = or2.child_object_id
where
o.description like '%birds%' or o.description like '%lex%' or o.description like '%john%'
limit 1) base
limit 100)
select
o.id as object_id,
or1.child_object_id as child_id,
or1."type" as child_type,
or2.child_object_id as child2_id,
or2."type" as child2_type
from
parent p
join objects o on
o.id = p.id
join object_relationships or1 on
or1.object_id = o.id
join objects o1 on
o1.id = or1.child_object_id
join object_relationships or2 on
or2.object_id = o1.id
join objects o2 on
o2.id = or2.child_object_id
limit 100) base;

Get an array of ids from object table and search rows in object_relationships by elements of the array.
select object_id, type, child_id
from (
select array_agg(id) as ids
from objects
where description like any('{%birds%, %lex%, %john%}')
) s
join object_relationships
on object_id = any(ids) and child_id = any(ids)
order by object_id, child_id;
Test it in db<>fiddle.

Related

Postgresql recursive query

I have table with self-related foreign keys and can not get how I can receive firs child or descendant which meet condition. My_table structure is:
id
parent_id
type
1
null
union
2
1
group
3
2
group
4
3
depart
5
1
depart
6
5
unit
7
1
unit
I should for id 1 (union) receive all direct child or first descendant, excluding all groups between first descendant and union. So in this example as result I should receive:
id
type
4
depart
5
depart
7
unit
id 4 because it's connected to union through group with id 3 and group with id 2 and id 5 because it's connected directly to union.
I've tried to write recursive query with condition for recursive part: when parent_id = 1 or parent_type = 'depart' but it doesn't lead to expected result
with recursive cte AS (
select b.id, p.type_id
from my_table b
join my_table p on p.id = b.parent_id
where b.id = 1
union
select c.id, cte.type_id
from my_table c
join cte on cte.id = c.parent_id
where c.parent_id = 1 or cte.type_id = 'group'
)
Here's my interpretation:
if type='group', then id and parent_id are considered in the same group
id#1 and id#2 are in the same group, they're equals
id#2 and id#3 are in the same group, they're equals
id#1, id#2 and id#3 are in the same group
If the above is correct, you want to get all the first descendent of id#1's group. The way to do that:
Get all the ids in the same group with id#1
Get all the first descendants of the above group (type not in ('union', 'group'))
with recursive cte_group as (
select 1 as id
union all
select m.id
from my_table m
join cte_group g
on m.parent_id = g.id
and m.type = 'group')
select mt.id,
mt.type
from my_table mt
join cte_group cg
on mt.parent_id = cg.id
and mt.type not in ('union','group');
Result:
id|type |
--+------+
4|depart|
5|depart|
7|unit |
Sounds like you want to start with the row of id 1, then get its children, and continue recursively on rows of type group. To do that, use
WITH RECURSIVE tree AS (
SELECT b.id, b.type, TRUE AS skip
FROM my_table b
WHERE id = 1
UNION ALL
SELECT c.id, c.type, (c.type = 'group') AS skip
FROM my_table c
JOIN tree p ON c.parent_id = p.id AND p.skip
)
SELECT id, type
FROM tree
WHERE NOT skip

how to skip the rows of a specific id in postgresql?

what am trying to do is displaying only the fk_students who got marks in biology and chemistry and computer like 5 3 3 respectively but not 2 which considered as a failure here the result am getting also the students who got 2 marks in the subjects.
*
select school_name,fk_students, count(marks),sum(marks) as total from students inner join remarks on fk_students = student_id
inner join subjects on subject_id = fk_subjects
inner join school on school_id = fk_school_idd
where marks = 3 or marks = 4 or marks = 5
group by fk_students,school_name
except
select school_name,fk_students, count(marks),sum(marks) as total from students inner join remarks on fk_students = student_id
inner join subjects on subject_id = fk_subjects
inner join school on school_id = fk_school_idd
where marks =2
group by fk_students,school_name
order by fk_students
the column fk_students where it contains 9 and 10 and 11 shouldn't be shown because they've got 2 mark in of their subjects which is considered failure
result
I solved this problem by using "where in"
full solution is shown below
select school_name,st_name,sum(marks) as total from students inner
join remarks on fk_students = student_id
inner join subjects on subject_id = fk_subjects
inner join school on school_id = fk_school_idd
group by fk_students,school_name,st_name
having sum(marks)=15
except
select school_name,st_name,sum(marks) as total from students inner
join remarks on fk_students = student_id
inner join subjects on subject_id = fk_subjects
inner join school on school_id = fk_school_idd
where fk_students = any(select fk_students from remarks
where marks in (2))
group by fk_students,school_name,st_name
having sum(marks)=15
order by school_name

Postgresql group by relation

I want to group the records by relation.
products table:
id
price
1
100
2
200
3
300
4
400
product_properties table:
id
productId
propertyId
1
1
2
2
1
3
3
2
2
4
2
3
5
3
4
6
4
4
The query should select lowest price group by product_properties. I mean, If products have same properties in product_properties, query should return product that has lowest price.
So, For these tables query should return products that have ids 1,3.
I use TypeORM, I tried join the relation and distinct on relation alias name but its not worked.
How can I achieve this?
I wrote two variants query for you:
-- variant 1
select distinct t1.product_id from (
select
pr.price, pp.product_id, pp.property_id, min(pr.price) OVER(PARTITION BY pp.property_id) as min_price
from
test.product_properties pp
inner join
test.products pr on pp.product_id = pr.id
) t1
where
t1.price = t1.min_price;
-- variant 2
select distinct t1.product_id from test.product_properties t1
inner join test.products t2 on t1.product_id = t2.id
inner join (
select
pp.property_id, min(pr.price) as min_price
from
test.product_properties pp
inner join
test.products pr on pp.product_id = pr.id
group by pp.property_id
) t3 on t3.property_id = t1.property_id and t3.min_price = t2.price;

Limit for inner Join Table

I have a scenario where I am joining three tables and getting the results.
My problem is i have apply limit for joined table.
Take below example, i have three tables 1) books and 2) Customer 3)author. I need to find list of books sold today with author and customer name however i just need last nth customers not all by passing books Id
Books Customer Authors
--------------- ---------------------- -------------
Id Name AID Id BID Name Date AID Name
1 1 1 ABC 1 A1
2 2 1 CED 2 A2
3 3 2 DFG
How we can achieve this?
You are looking for LATERAL.
Sample:
SELECT B.Id, C.Name
FROM Books B,
LATERAL (SELECT * FROM Customer WHERE B.ID=C.BID ORDER BY ID DESC LIMIT N) C
WHERE B.ID = ANY(ids)
AND Date=Current_date

How to count detail rows on nested categories?

Let us consider that we have Categories (with PK as CategoryId) and Products (with PK as ProductId). Also, assume that every Category can relate to its parent category (using ParentCategoryId column in Categories).
How can I get Category wise product count? The parent category should include the count of all products of all of its sub-categories as well.
Any easier way to do?
sounds like what you are asking for would be a good use for with rollup
select cola, colb, SUM(colc) AS sumc
from table
group by cola, colb
with rollup
This would give a sum for colb and a rollup sum for cola. Example result below. Hope the formatting works. The null values are the rollup sums for the group.
cola colb sumc
1 a 1
1 b 4
1 NULL 5
2 c 2
2 d 3
2 NULL 5
NULL NULL 10
Give it a go and let me know if that has worked.
--EDIT
OK i think ive got this as it is working on a small test set i am using. Ive started to see a place where i need this myself so thanks for asking the question. I will admit this is a bit messy but should work for any number of levels and will only return the sum at the highest level.
I made an assumption that there is a number field in products.
with x
as (
select c.CategoryID, c.parentid, p.number, cast(c.CategoryID as varchar(8000)) as grp, c.CategoryID as thisid
from Categories as c
join Products as p on p.Categoryid = c.CategoryID
union all
select c.CategoryID, c.parentid, p.number, cast(c.CategoryID as varchar(8000))+'.'+x.grp , x.thisid
from Categories as c
join Products as p on p.Categoryid = c.CategoryID
join x on x.parentid = c.CategoryID
)
select x.CategoryID, SUM(x.number) as Amount
from x
left join Categories a on (a.CategoryID = LEFT(x.grp, case when charindex('.',x.grp)-1 > 0 then charindex('.',x.grp)-1 else 0 end))
or (a.CategoryID = x.thisid)
where a.parentid = 0
group by x.CategoryID
Assuming that Products can only point to a subcategory, here's a probable solution to the problem:
SELECT
cp.CategoryId,
ProductCount = COUNT(*)
FROM Products p
INNER JOIN Categories cc ON p.CategoryId = cc.CategoryId
INNER JOIN Categories cp ON cc.ParentCategoryId = cp.CategoryId
GROUP BY cp.CategoryId
But if the above assumption is wrong and a product can reference a parent category directly as well as a subcategory, then here's how you could count the products in this case:
SELECT
CategoryId = ISNULL(c2.CategoryId, c1.CategoryId),
ProductCount = COUNT(*)
FROM Products p
INNER JOIN Categories c1 ON p.CategoryId = c1.CategoryId
LEFT JOIN Categories c2 ON c1.ParentCategoryId = c2.CategoryId
GROUP BY ISNULL(c2.CategoryId, c1.CategoryId)
EDIT
This should work for 3 levels of hierarchy of categories (category, sub-category, sub-sub-category).
SELECT
CategoryId = COALESCE(c3.CategoryId, c2.CategoryId, c1.CategoryId),
ProductCount = COUNT(*)
FROM Products p
INNER JOIN Categories c1 ON p.CategoryId = c1.CategoryId
LEFT JOIN Categories c2 ON c1.ParentCategoryId = c2.CategoryId
LEFT JOIN Categories c3 ON c2.ParentCategoryId = c3.CategoryId
GROUP BY ISNULL(c3.CategoryId, c2.CategoryId, c1.CategoryId)
COALESCE picks the first non-NULL component. If the category is a child, it picks c3.Category, which is its grand-parent, if a parent, then its parent c2.Category is chosen, otherwise it's a grand-parent (c1.CategoryId).
In the end, it selects only grand-parent categories, and shows product count for them that includes all the subcategories of all levels.