SQL query where has children with conditions - sql

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

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 )

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)

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

How to retrieve all grand parents at once

I have two simple tables in SQLITE3 (family bonds):
"persons" (id, fname, lname)
"relationships" (parent/child)
I would like to get each grand children along with all their grand parents (from 1 to 4 of them depending on the grand child) so that 1 row result =
Distinct Grand child, Grand parent 1, Grand parent 2, Grand parent 3, Grand parent 4
Thanks to Caius Jard, I've been able to get each child and its grand parents in another star overflow question. However, so far I have:
1 line = Grand child, 1 grand parent (so it needs up to 4 lines to get all grand parents of a child).
SELECT c.fname, c.lname, gp.fname, gp.lname
FROM relations p_c
INNER JOIN relationships gp_p ON gp_p.child = p_c.parent
INNER JOIN persons gp ON gp.id = gp_p.parent
INNER JOIN persons c ON c.id = p_c.child
ORDER BY c.id;
How could I edit this so that I get each grand child along with all the grand parents in one single row result?
If possible only using SELECT (+ count/distinct), WHERE (+in/exists), INNER/LEFT JOIN, GROUP BY (+having), ORDER BY.
We can turn rows into columns by a technique known as conditional aggregation:
Suppose you have a result set:
x, y
1, hello
2, goodbye
If you wrote this:
SELECT x,
CASE WHEN x = 1 THEN y END as x1,
CASE WHEN x = 2 THEN y END as x2
FROM ...
You would get this:
x, x1, x2
1, hello, NULL
2, NULL, goodbye
There is only one value in a column, the rest are null. If you then use e.g. MAX, which discards nulls:
SELECT
MAX(CASE WHEN x = 1 THEN y END) as x1,
MAX(CASE WHEN x = 2 THEN y END) as x2
FROM ...
You would get
x1, x2
hello, goodbye
We've had to drop the x, because we can't put it in there otherwise we'd have to group it, which would prevent rows collapsing down
Now to your query, we probably have to add other info in because we need something to CASE WHEN on. You don't indicate that we can arbitrarily throw a row number in there. Hopefully persons contains a gender indicator because 4 grandparents are the result of two males and two females arising from one male and one female (so we have MM - father's father, MF - father's mother, FM and FF etc)
SELECT ...
FROM relations p_c
INNER JOIN relationships gp_p ON gp_p.child = p_c.parent
INNER JOIN persons gp ON gp.id = gp_p.parent
INNER JOIN persons p ON p.id = p_c.parent
INNER JOIN persons c ON c.id = p_c.child
We can now query the gender of each level of family tree:
SELECT c.name as child,
CASE WHEN gp.gender = 'm' AND p.gender = 'm' THEN gp.name END AS fathersfather,
CASE WHEN gp.gender = 'm' AND p.gender = 'f' THEN gp.name END AS fathersmother,
CASE WHEN gp.gender = 'f' AND p.gender = 'm' THEN gp.name END AS mothersfather,
CASE WHEN gp.gender = 'f' AND p.gender = 'f' THEN gp.name END AS mothersmother
FROM relations p_c
INNER JOIN relationships gp_p ON gp_p.child = p_c.parent
INNER JOIN persons gp ON gp.id = gp_p.parent
INNER JOIN persons p ON p.id = p_c.parent
INNER JOIN persons c ON c.id = p_c.child
This will give the child and then a diagonal array of the grandparent names. You can squish it to a row per child with:
SELECT c.name as child,
MAX(CASE WHEN gp.gender = 'm' AND p.gender = 'm' THEN gp.name END) AS fathersfather,
MAX(CASE WHEN gp.gender = 'm' AND p.gender = 'f' THEN gp.name END) AS fathersmother,
MAX(CASE WHEN gp.gender = 'f' AND p.gender = 'm' THEN gp.name END) AS mothersfather,
MAX(CASE WHEN gp.gender = 'f' AND p.gender = 'f' THEN gp.name END) AS mothersmother
FROM relations p_c
INNER JOIN relationships gp_p ON gp_p.child = p_c.parent
INNER JOIN persons gp ON gp.id = gp_p.parent
INNER JOIN persons p ON p.id = p_c.parent
INNER JOIN persons c ON c.id = p_c.child
GROUP BY c.name
Instead of 4 columns for the grand parents, I would suggest to use GROUP_CONCAT() and get 1 column with all the grandparents:
SELECT p.fname, p.lname,
GROUP_CONCAT(gp.fname || ' ' || gp.lname, '|')
FROM persons p
INNER JOIN relationships r1 ON r1.child = p.id
INNER JOIN relationships r2 ON r2.child = r1.parent
INNER JOIN persons gp ON r2.parent = gp.id
GROUP BY p.id
The code is by far simpler.
Use LEFT joins instead of INNER joins if you want all the persons returned, even the ones without grandparents.
Assuming there are four grandparents, you can enumerate them and aggregate:
SELECT fname, lname,
MAX(CASE WHEN seqnum = 1 THEN grandparent_name END) as grandparent_1,
MAX(CASE WHEN seqnum = 2 THEN grandparent_name END) as grandparent_2,
MAX(CASE WHEN seqnum = 3 THEN grandparent_name END) as grandparent_3,
MAX(CASE WHEN seqnum = 4 THEN grandparent_name END) as grandparent_4
FROM (SELECT c.id, c.fname, c.lname,
gp.fname || ' ' || gp.lname as grandparent_name,
ROW_NUMBER() OVER (PARTITION BY c.id ORDER BY gp.fname, gp.lanme) as seqnum
FROM relations p_c JOIN
relationships gp_p
ON gp_p.child = p_c.parent JOIN
persons gp
ON gp.id = gp_p.parent
persons c
ON c.id = p_c.child
) cgp
GROUP BY fname, lname, id;

WebSQL selecting child + parent

I'm trying to export a list of categories with it's childs.
the designer of the DB made it so that a parent category can have the same ID of a child
so the query should return a list of:
Parent items
Child items with the parent name
I'm trying to do this in 1 Query, So I did the following:
SELECT DISTINCT c.*, pc.name as parentName
FROM Category as c
INNER JOIN Category as pc on c.parent_id = pc.id
WHERE c.building_id = 1
AND (
c.parent_id = -1
OR (
c.usingtemplate = 'true'
AND
pc.parent_id = -1
)
);
but the parents aren't in the list
I have a sql fiddle here: http://sqlfiddle.com/#!7/75dd9/83
Doing a LEFT JOIN did the trick
SELECT DISTINCT c.*, pc.name as parentName
FROM Category as c
LEFT JOIN Category as pc on c.parent_id = pc.id
WHERE c.building_id = 1
AND (
c.parent_id = -1
OR (
c.usingtemplate = 'true'
AND
pc.parent_id = -1
)
);

Join table on conditions, count on conditions

SELECT *, null AS score,
'0' AS SortOrder
FROM products
WHERE datelive = -1
AND hidden = 0
UNION
SELECT e.*, (SUM(r.a)/(COUNT(*)*1.0)+
SUM(r.b)/(COUNT(*)*1.0)+
SUM(r.c)/(COUNT(*)*1.0)+
SUM(r.d)/(COUNT(*)*1.0))/4 AS score,
'1' AS SortOrder
FROM products e
LEFT JOIN reviews r
ON r.productID = e.productID
WHERE e.hidden = 0
AND e.datelive != -1
GROUP BY e.productID
HAVING COUNT(*) >= 5
UNION
SELECT e.*, (SUM(r.a)/(COUNT(*)*1.0)+
SUM(r.b)/(COUNT(*)*1.0)+
SUM(r.c)/(COUNT(*)*1.0)+
SUM(r.d)/(COUNT(*)*1.0))/4 AS score,
'2' AS SortOrder
FROM products e
LEFT JOIN reviews r
ON r.productID = e.productID
WHERE e.hidden = 0
AND e.datelive != -1
GROUP BY e.productID
HAVING COUNT(*) < 5
ORDER BY SortOrder ASC, score DESC
This creates an SQL object for displaying products on a page. The first request grabs items of type datelive = -1, the second of type datelive != -1 but r.count(*) >= 5, and the third of type datelive != -1 and r.count(*) < 5. The reviews table is structured similar to the below:
reviewID | productID | a | b | c | d | approved
-------------------------------------------------
1 1 5 4 5 5 1
2 5 3 2 5 5 0
3 2 5 5 4 3 1
... ... ... ... ... ... ...
I'm trying to work it such that r.count(*) only cares for rows of type approved = 1, since tallying data based on unapproved reviews isn't ideal. How can I join these tables such that the summations of scores and the number of rows is dependent only on approved = 1?
I've tried adding in AND r.approved = 1 in the WHERE conditional for the joins and it doesn't do what I'd like. It does sort it properly, but then it no longer includes items with zero reviews.
You seem to be nearly there.
In your question you talked about adding the AND r.approved = 1 to the join criteria but by the sounds of it you are actually adding it to the WHERE clause.
If you instead properly add it to the join criteria like below then it should work fine:
SELECT *, null AS score,
'0' AS SortOrder
FROM products
WHERE datelive = -1
AND hidden = 0
UNION
SELECT e.*, (SUM(r.a)/(COUNT(*)*1.0)+
SUM(r.b)/(COUNT(*)*1.0)+
SUM(r.c)/(COUNT(*)*1.0)+
SUM(r.d)/(COUNT(*)*1.0))/4 AS score,
'1' AS SortOrder
FROM products e
LEFT JOIN reviews r ON r.productID = e.productID
WHERE e.hidden = 0
AND e.datelive != -1
GROUP BY e.productID
HAVING COUNT(*) >= 5
UNION
SELECT e.*, (SUM(r.a)/(COUNT(*)*1.0)+
SUM(r.b)/(COUNT(*)*1.0)+
SUM(r.c)/(COUNT(*)*1.0)+
SUM(r.d)/(COUNT(*)*1.0))/4 AS score,
'2' AS SortOrder
FROM products e
LEFT JOIN reviews r ON r.productID = e.productID AND r.approved = 1
WHERE e.hidden = 0
AND e.datelive != -1
GROUP BY e.productID
HAVING COUNT(*) < 5
ORDER BY SortOrder ASC, score DESC
SQL Fiddle here.
Notice again how I have simply put the AND r.approved = 1 directly after LEFT JOIN reviews r ON r.productID = e.productID which adds an extra criteria to the join.
As I mentioned in my comment, the WHERE clause will filter rows out of the combined record set after the join has been made. In some cases the RDBMS may optimise it out and put it into the join criteria but only where that would make no difference to the result set.
Calculating the non-zero sums and joining it to your result may solve it;
fiddle
SELECT a.productID,
NULL AS score,
'0' AS SortOrder
FROM products a
WHERE datelive = -1
AND hidden = 0
UNION
SELECT e.productID,
(min(x.a)/(min(x.cnt)*1.0)+ min(x.b)/(min(x.cnt)*1.0)+ min(x.c)/(min(x.cnt)*1.0)+ min(x.d)/(min(x.cnt)*1.0))/4 AS score,
'1' AS SortOrder
FROM products e
JOIN reviews r ON r.productID = e.productID
LEFT JOIN
(SELECT ee.productID,
sum(rr.a) AS a,
sum(rr.b) AS b,
sum(rr.c) AS c,
sum(rr.d) AS d,
count(*) AS cnt
FROM products ee
LEFT JOIN reviews rr ON ee.productID = rr.productID
GROUP BY ee.productID) x ON e.productID = x.productID
WHERE e.hidden = 0
AND e.datelive != -1
GROUP BY e.productID HAVING COUNT(*) >= 5
UNION
SELECT e.productID,
(min(x.a)/(min(x.cnt)*1.0)+ min(x.b)/(min(x.cnt)*1.0)+ min(x.c)/(min(x.cnt)*1.0)+ min(x.d)/(min(x.cnt)*1.0))/4 AS score,
'2' AS SortOrder
FROM products e
LEFT JOIN reviews r ON r.productID = e.productID
LEFT JOIN
(SELECT ee.productID,
sum(rr.a) AS a,
sum(rr.b) AS b,
sum(rr.c) AS c,
sum(rr.d) AS d,
count(*) AS cnt
FROM products ee
LEFT JOIN reviews rr ON ee.productID = rr.productID
GROUP BY ee.productID) x ON e.productID = x.productID
WHERE e.hidden = 0
AND e.datelive != -1
GROUP BY e.productID HAVING COUNT(*) < 5
ORDER BY SortOrder ASC,
score DESC
You could create a temp table that only contains rows where approved = 1, and then join on the temp table instead of reviews.
create table tt_reviews like reviews;
insert into tt_reviews
select * from reviews
where approved = 1;
alter table tt_reviews add index(productID);
Then replace reviews with tt_reviews in your above query.

Cannot perform an aggregate function on an expression containing an aggregate or a subquery sql server 2012

I am performing a query and its showing an error Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
the query is
SELECT
tbl_Product.ID,
tbl_Product.ArticleID,
tbl_Product.Title,
tbl_Product.Description,
tbl_Product.Price,
tbl_ProductType.Name,
tbl_Status.StatusName,
tbl_VisibilityStatus.VisibilityStatus,
MAX(
CASE
WHEN tbl_RelatedProduct.TypeOfRelation = 1 THEN 'Bundle'
WHEN tbl_Product.ID IN
(
select tbl_RelatedProduct.Product2ID
from tbl_RelatedProduct
where tbl_RelatedProduct.Product1ID=9 and tbl_RelatedProduct.TypeOfRelation=1) THEN 'Bundle'
END
) 'Bundle',
MAX(
CASE
WHEN tbl_RelatedProduct.TypeOfRelation = 2 THEN 'Follower' END) 'Follower',
MAX(
CASE
WHEN tbl_RelatedProduct.TypeOfRelation = 3 THEN 'Related' END) 'Related'
FROM
tbl_Product inner JOIN
tbl_ProductType ON tbl_Product.ProductTypeId = tbl_ProductType.ID Inner JOIN
tbl_Status ON tbl_Product.StatusID = tbl_Status.ID Inner JOIN
tbl_VisibilityStatus ON tbl_Product.VisibilityID = tbl_VisibilityStatus.ID
left JOIN tbl_RelatedProduct ON tbl_Product.ID = tbl_RelatedProduct.Product1ID
group by
tbl_Product.ID,
tbl_Product.ArticleID,
tbl_Product.Title,
tbl_Product.Description,
tbl_Product.Price,
tbl_ProductType.Name,
tbl_Status.StatusName,
tbl_VisibilityStatus.VisibilityStatus
order by tbl_Product.Title
ANyone know how to help on this...plsss
The below line is causing an issue:
WHEN tbl_Product.ID IN
(
select tbl_RelatedProduct.Product2ID
from tbl_RelatedProduct
where tbl_RelatedProduct.Product1ID=9 and tbl_RelatedProduct.TypeOfRelation=1) THEN 'Bundle'
END
) 'Bundle',
You are using MAX with CASE and one of the statements within CASE uses a subquery, which is causing the error. You might want to consider using joins instead of subqueries to implement this.
SELECT tbl_Product.ID,tbl_Product.ArticleID,tbl_Product.Title,tbl_Product.Description,tbl_Product.Price,tbl_ProductType.Name,tbl_Status.StatusName, tbl_VisibilityStatus.VisibilityStatus,
Case when ((select count(1) from tbl_RelatedProduct where tbl_RelatedProduct.TypeOfRelation = 1 and tbl_RelatedProduct.Product1ID = tbl_Product.Id) > 0) then 1
when ((select count(1) from tbl_RelatedProduct where tbl_RelatedProduct.TypeOfRelation = 1 and tbl_RelatedProduct.Product2ID = tbl_Product.Id) > 0) then 1
else 0 end as Bundle,
(select count(1) from tbl_RelatedProduct where tbl_RelatedProduct.TypeOfRelation = 2 and tbl_RelatedProduct.Product1ID = tbl_Product.Id) as Follower,
(select count(1) from tbl_RelatedProduct where tbl_RelatedProduct.TypeOfRelation = 3 and tbl_RelatedProduct.Product1ID = tbl_Product.Id) as Related
FROM tbl_Product
inner JOIN
tbl_ProductType ON tbl_Product.ProductTypeId = tbl_ProductType.ID Inner JOIN
tbl_Status ON tbl_Product.StatusID = tbl_Status.ID
Inner JOIN
tbl_VisibilityStatus ON tbl_Product.VisibilityID = tbl_VisibilityStatus.ID
Its resolved with this query :)
I think I see what you are trying to do, and it is a bit strange. I would output this all as a single column, and lose the MAX aggregate as follows:
SELECT
p.ID,
p.ArticleID,
p.Title,
p.Description,
p.Price,
pt.Name,
s.StatusName,
v.VisibilityStatus,
CASE
WHEN rp.TypeOfRelation = 1 OR (rp2.Product1ID = 9 AND rp2.TypeOfRelation = 1)
THEN 'Bundle'
WHEN rp.TypeOfRelation = 2
THEN 'Follower'
WHEN rp.TypeOfRelation = 3
THEN 'Related'
ELSE Null
END AS Relation
FROM
tbl_Product p
INNER JOIN tbl_ProductType pt
ON p.ProductTypeId = pt.ID
INNER JOIN tbl_Status s
ON p.StatusID = s.ID
INNER JOIN tbl_VisibilityStatus v
ON p.VisibilityID = v.ID
LEFT JOIN tbl_RelatedProduct rp
ON p.ID = rp.Product1ID
LEFT JOIN tbl_RelatedProduct rp2
ON p.ID = rp2.Product2ID
ORDER BY p.Title
If you would still like to have them in separate columns, just break up the CASE statement as follows:
SELECT
p.ID,
p.ArticleID,
p.Title,
p.Description,
p.Price,
pt.Name,
s.StatusName,
v.VisibilityStatus,
CASE
WHEN rp.TypeOfRelation = 1 OR (rp2.Product1ID = 9 AND rp2.TypeOfRelation = 1)
THEN 'Bundle'
ELSE Null
END AS Bundle,
CASE
WHEN rp.TypeOfRelation = 2
THEN 'Follower'
ELSE Null
END AS Follower,
CASE
WHEN rp.TypeOfRelation = 3
THEN 'Related'
ELSE Null
END AS Related
FROM
tbl_Product p
INNER JOIN tbl_ProductType pt
ON p.ProductTypeId = pt.ID
INNER JOIN tbl_Status s
ON p.StatusID = s.ID
INNER JOIN tbl_VisibilityStatus v
ON p.VisibilityID = v.ID
LEFT JOIN tbl_RelatedProduct rp
ON p.ID = rp.Product1ID
LEFT JOIN tbl_RelatedProduct rp2
ON p.ID = rp2.Product2ID
ORDER BY p.Title

Selecting Parent Rows With Multiple Conditions

I had most of this query down until a new condition arose and it has confounded me. Given the following simplified table schema:
Parent Table:
ID
FName
LName
Child Table:
[Index]
ParentID
Active_Flag
ExpirationDate
What I want to do is get Parent rows for which:
There are no children.
There are children whose Active_Flag is 1 but whose expiration dates are blank or NULL.
There are indeed children but none have the Active_Flag set to 1.
The following query came up with my first two criteria:
SELECT p.ID, p.LNAME, p.FNAME,
CASE
WHEN COUNT(ct.indx) = 0 THEN 'None'
WHEN ct.ExpirationDate is NULL or ct.ExpirationDate = '' THEN 'No expiration date'
END AS Issue
FROM ParentTable AS p
LEFT JOIN ChildTable ct
ON p.ID = ct.ParentID
GROUP BY p.ID, p.LNAME, p.FNAME, ct.[INDEX], ct.ExpirationDate
HAVING (COUNT(ct.[INDEX]) = 0) OR (ct.ExpirationDate IS NULL OR ct.ExpirationDate = '')
ORDER BY p.LNAME
I don't know how to account for #3. Any help is appreciated. Thanks in advance.
You can also do this in the HAVING clause:
SELECT p.ID, p.LNAME, p.FNAME,
(CASE WHEN COUNT(ct.indx) = 0 THEN 'None'
WHEN ct.ExpirationDate is NULL or ct.ExpirationDate = '' THEN 'No expiration date'
WHEN sum(case when ActiveFlag = 1 then 1 else 0 end) = 0 then 'No active children'
END) AS Issue
FROM ParentTable p LEFT JOIN
ChildTable c
ON p.ID = ct.ParentID
GROUP BY p.ID, p.LNAME, p.FNAME
HAVING (COUNT(ct.[INDEX]) = 0) OR
(ct.ExpirationDate IS NULL OR ct.ExpirationDate = '') or
sum(case when ActiveFlag = 1 then 1 else 0 end) = 0
ORDER BY p.LNAME
The DISTINCT in the SELECT is redundant. You do not need it with an aggregation.
You can simplify the having to "sum(ActiveFlag)" if the activeFlag is indeed an integer. If not, then it should be "= '1'" rather than "= 1'.
you can use unions for this query.....
I am not sure about this query ....but it will help you in solving your prolem
select p.id,p.lname,p.fname from parents where (p.id not in (select pid from children))
union
select p.id,p.lname,p.fname from parents p,children c inner join on c.pid=p.id where c.active_flag=1 and c.expiration_date is null or c.expiration_date=''
union
select p.id,p.lname,p.fname from parents p inner join on c.pid=p.id where c.active_flag<>1;
SELECT *
FROM parenttable pt
-- condition 1: There should be no parents without children at all
WHERE NOT EXISTS (
SELECT * FROM childtable c1
WHERE c1.parent_id = pt.id
)
-- condition 2: There should be no children with a flag but without a date
OR EXISTS (
SELECT * FROM childtable c2
WHERE c2.parent_id = pt.id
AND c2.active_flag = 1 AND c2.expire_date IS NULL
)
-- condition 3: There should at least be a child with the active_flag
OR NOT EXISTS (
SELECT * FROM childtable c3
WHERE c3.parent_id = pt.id
AND c2.active_flag = 1
)
;
-- Active flags for children c1 and c2
-- c1 c2 (X= child doesn't exists)
-----+-----+---+
-- X X rule1+rule3 # no children at all
-- 0 X rule3 # only one child
-- 1 X # idem
-- 0 0 rule 3 # two children
-- 0 1
-- 1 0
-- 1 1
--
-- , which means that rule3 implies rule1, and rule1 is redundant
-------------------------
SELECT *
FROM parenttable pt
-- condition 1+3: There should at least be a child with the active_flag
WHERE NOT EXISTS (
SELECT * FROM childtable c1
WHERE c1.parent_id = pt.id
AND c1.active_flag = 1
)
-- condition 2: There should be no children with a flag but without a date
OR EXISTS (
SELECT * FROM childtable c2
WHERE c2.parent_id = pt.id
AND c2.active_flag = 1 AND c2.expire_date IS NULL
)
;