Home > Back-end >  SQL query where has children with conditions
SQL query where has children with conditions

Time:06-02

Let's say I have:

  • a parents table with the following columns: (id, name)
  • a children attributes table with the following columns: (id, child_id, parent_id, attribute, attribute_value)

Now I want to filter any parent id's that has at least a child with both:

  • attribute => intelligence of 5
  • attribute => health of 4

Either one child with intelligence of 5 and health of 4, or one child has intelligence of 5 and another child has health of 4.

How would you query that, in PostgreSQL? Thank you

CodePudding user response:

You can just do the intersection of

  • parents that have children with intelligence 5
  • parents that have children with health 4
(SELECT parent_id 
 FROM tab
 WHERE attribute = 'intelligence'
   AND attribute_value = 5       )
INTERSECT
(SELECT parent_id 
 FROM tab
 WHERE attribute = 'health'
   AND attribute_value = 4       )

CodePudding user response:

If you only wants parents info:

SELECT 
  DISTINCT parents.id, parents.name 
FROM 
  parents 
  LEFT JOIN attributes ON parents.id = attributes.parent_id 
WHERE 
  (attribute = 'intelligence' AND attribute_value = 5)
   OR (attribute = 'health' AND attribute_value = 4)

CodePudding user response:

First we need to join the tables -- like this

select p.id as p_id, p.name as parent_name,
       k.* -- we won't need this in later versions
from parent p
join kidatt k on p.id = k.parent_id

now we have two attributes we care about -- let make a query that shows those

select p.id as p_id, p.name as parent_name,
       case when k.attribute = 'intelligence' and k.attribute_value = 5 then 1 else 0 end as has_a1,
       case when k.attribute = 'health' and k.attribute_value = 4 then 1 else 0 end as has_a2
from parent p
join kidatt k on p.id = k.parent_id

we now have a query with a 1 in a row for those that have each of these

now we group by the parent.

select p.id as p_id, p.name as parent_name,
       SUM(case when k.attribute = 'intelligence' and k.attribute_value = 5 then 1 else 0 end) as has_a1,
       SUM(case when k.attribute = 'health' and k.attribute_value = 4 then 1 else 0 end) as has_a2
from parent p
join kidatt k on p.id = k.parent_id
group by p.id, p.name

now we have a query where a1 and a2 are greater than 0 if one or more child has it.

Now just select the results

select * 
from (
  select p.id as p_id, p.name as parent_name,
       SUM(case when k.attribute = 'intelligence' and k.attribute_value = 5 then 1 else 0 end) as has_a1,
       SUM(case when k.attribute = 'health' and k.attribute_value = 4 then 1 else 0 end) as has_a2
  from parent p
  join kidatt k on p.id = k.parent_id
  group by p.id, p.name
)
where has_a1 > 0 and has_a2 > 0

note -- I did not write this query to be the best way to solve this problem -- instead I wrote it in a way to show you how to "think" in SQL and solve the problem with a series of steps.

I'd have to test to be sure, but I expect this would be the fastest way to do this query (depends on data and indexes etc.)

select distinct p.id as p_id, p.name as parent_name,          
from parent p
join kidatt k on p.id = k.parent_id
where k.attribute = 'intelligence' and k.attribute_value = 5

intersect

select distinct p.id as p_id, p.name as parent_name,          
from parent p
join kidatt k on p.id = k.parent_id
where k.attribute = 'health' and k.attribute_value = 4 
  • Related