tags:

views:

183

answers:

5

Hello,

Tiredness prevents me from finding this one... Say you have the following tables:

Parent

  • PARENT_ID (LONG)

Child

  • CHILD_ID (LONG)
  • PARENT_ID (LONG, FK)
  • HAS_GRADUATED (BOOLEAN)

I want a query to return the following true (1, in the case of Oracle) if the parent has at least one child that has graduated, and false (0, in the case if Oracle) if the parent does not have a child that has graduated, or has no children at all:

PARENT_ID................HAS_CHILDREN_WHO_GRADUATED

5.................................1

3.................................1

6.................................0

2.................................0

In the above, parent with parent_id=5 may have >=1 children that have graduated. Same is parent with parent_id=3. Parent with parent_id=6 either has no children at all, or has children but none of them has graduated.

What would the query to this be like?

A: 

First of all I don't think you can use LONG columns for this since LONG values cannot be used in WHERE conditions. Note this is true as of 10g, since that's what I use.

Second I assume you mean that your child table should have a column called PARENT_ID otherwise there would be no way to link the two tables. Given that, this query ought to work:

SELECT PARENT_ID, COUNT(1) FROM Child WHERE HAS_GRADUATED = 1 GROUP BY PARENT_ID
Dan
This won't list the parents without children.
pascal
You're right, my mistake, will fix.
Dan
+2  A: 

Will this give you what you expect?

SELECT 
    P.Parent_Id,
    CASE WHEN (SUM (CASE WHEN Has_Graduated = 1 then 1 else 0 END)) = 0 THEN 0 ELSE 1  as HAS_CHILDREN_WHO_GRADUATED
FROM Parent P
    LEFT JOIN Child C
        ON P.Parent_Id = C.Parent_Id
GROUP BY P.Parent_Id
Raj More
Would this not actually count how many children are graduated for that parent? He specifically asked for 1 or 0.
Willfulwizard
To be more useful than just pointing out the error, could we just make this change: ( 1 <= Count(CASE WHEN Has_Graduated = 1 then 1 else 0))?
Willfulwizard
answer corrected.
Raj More
Thanks for the answer (+1)
Markos Fragkakis
+7  A: 

Use:

   SELECT DISTINCT
          p.parent_id,
          CASE WHEN c.parent_id IS NULL THEN 0 ELSE 1 END
     FROM PARENT p
LEFT JOIN CHILD c ON c.parent_id = p.parent_id
                 AND c.has_graduated = 1

You have to use an outer join in order to see the parent values that don't have supporting records in the child table.

OMG Ponies
DISTINCT should perform better than a group by.
Philip Kelley
OMG Ponies
Thanks for the answer (+1). Actually, I think that it does not work. If a parent has a child that has grauated and another that has not graduated, there will be two rows in the final resultset. One with the parent_id and 1, and another one with parent_id and 0.
Markos Fragkakis
It would work if I summed up the CASE in your query, and then put the SUM into an outer CASE when SUM >=1 THEN 1 else 0 END. Is this the best way, through?
Markos Fragkakis
@Markos Fragkakis: Because the LEFT JOIN includes the `has_graduated = 1`, only those records will be joined to the PARENT table - so there's no possibilty of other records turning up in the output and it satisfieds the "has at least one child who has graduated" requirement. The CASE expression leverages the LEFT JOIN to show zero or one appropriately, the DISTINCT just removes the visibilty of duplicates. You could always test it ;)
OMG Ponies
+1 Good answer. You could use NVL2(c.parent_id,1,0) rather than the case.
Leigh Riffel
@Leigh Riffel: True, but CASE is ANSI - NVL2 wouldn't port to other databases.
OMG Ponies
@OMG Ponies: That is good to know for those who might need to do so, but since the question is tagged for Oracle a database dependent solution is appropriate. It may not be applicable in this case, but in general I would prefer a shorter/faster database specific solution.
Leigh Riffel
@Leigh Riffel: Valid point; I'd like to know what's most efficient of the three options - NVL, NVL2, CASE expression.
OMG Ponies
In my testing NVL2 has a marginal edge over a similarly purposed case, but the difference isn't significant enough to be useful except in rare situations. In this case the shorter code would be the primary benefit.
Leigh Riffel
A: 

Here's the form of the query, though the syntax for Oracle may be off:

SELECT
   Parent.PARENT_ID
  ,case count(Child.PARENT_ID) when 0 then 0 else 1 end HAS_CHILDREN_WHO_GRADUATED
 from Parent
  left outer join Child
   on Child.PARENT_ID = Parent.PARENT_ID
 where Child.HAS_GRADUATED = 1
 group by Parent.PARENT_ID

This will list all Parent items once, with HAS_CHILDREN_WHO_GRADUATED set to 1 or 0 set as desired.

(Edited to add the where clause)

Philip Kelley
+2  A: 

It is likely that OMG Ponies solution will perform better (which is why he got my +1), but this yet another way of solving the problem.

Select Parent_Id
    , Case
        When Exists( Select 1
                    From Child
                    Where Child.Parent_Id = Parent.Parent_Id
                        And Child.Has_Graduated = 1 ) Then 1
        Else 0
        End
From Parent
Thomas
+1 - your variant works(@OMG Ponies variant returns wrong result). Performance are questionable, in some variants your variant works faster
ThinkJet
Oops ... loose "c.has_graduated = 1" at @OMG Ponies answer. Sorry. It works too.
ThinkJet