tags:

views:

1048

answers:

7

I have a table which has several one to many relationships with other tables. Let's say the main table is a person, and the other tables represent pets, cars and children. I would like a query that returns details of the person,the number of pets, cars and children they have e.g.

Person.Name   Count(cars) Count(children) Count(pets)

John Smith    3           2               4
Bob Brown     1           3               0

What is the best way to do this?

A: 

Note, that it depends on your flavour of RDBMS, whether it supports nested selects like the following:

SELECT p.name AS name
   , (SELECT COUNT(*) FROM pets e WHERE e.owner_id = p.id) AS pet_count
   , (SELECT COUNT(*) FROM cars c WHERE c.owner_id = p.id) AS world_pollution_increment_device_count
   , (SELECT COUNT(*) FROM child h WHERE h.parent_id = p.id) AS world_population_increment
FROM person p
ORDER BY p.name

IIRC, this works at least with PostgreSQL and MSSQL. Not tested, so your mileage may vary.

Dirk
This *should* work in Oracle - I haven't tested it
Chris Gill
This is the simplest solution, but would likely result in poor performance due to the correlated subqueries. If your database is small though, it may not matter.
Eric Petroelje
interesting to see that you give your technical answer your personal flavour ;)
lewap
@Eric: you're right, however, the db will probably rewrite this statement. So finally it might become what you proposed.
lewap
Sleep deprivation can cause loss of self-restraint :-)
Dirk
A: 

Using subselects not very good practice, but may be here it will be good

select p.name, (select count(0) from cars c where c.idperson = p.idperson), 
               (select count(0) from children ch where ch.idperson = p.idperson),
               (select count(0) from pets pt where pt.idperson = p.idperson)
  from person p
Alexey Sviridov
A: 

You could do this with three outer joins:

SELECT
    Person.Name,
    sum(case when cars.id is not null then 1 else 0 end) car_count,
    sum(case when children.id is not null then 1 else 0 end) child_count,
    sum(case when pets.id is not null then 1 else 0 end) pet_count
FROM
    Person
LEFT OUTER JOIN
    cars on
    Person.id = cars.person_id
LEFT OUTER JOIN
    children on
    Person.id = children.person_id
LEFT OUTER JOIN
    pets on
    Person.id = pets.person_id
GROUP BY
    Person.Name

I belive that Oracle now supports the case when syntax, but if not you could use a decode.

Doug Hays
Oracle has supported CASE statements since 9i.
OMG Ponies
Rexem, Thanks for the clarification! I've written some nasty decode statements and would have loved to have been able to use the case statement!Doug.
Doug Hays
You can also just use count(cars.id), etc. It won't count null values.
Daniel Emge
+1  A: 

I would probably do it like this:

SELECT Name, PersonCars.num, PersonChildren.num, PersonPets.num
FROM Person p
LEFT JOIN (
   SELECT PersonID, COUNT(*) as num
   FROM Person INNER JOIN Cars ON Cars.PersonID = Person.PersonID
   GROUP BY Person.PersonID
) PersonCars ON PersonCars.PersonID = p.PersonID
LEFT JOIN (
   SELECT PersonID, COUNT(*) as num
   FROM Person INNER JOIN Children ON Children.PersonID = Person.PersonID
   GROUP BY Person.PersonID
) PersonChildren ON PersonChildren.PersonID = p.PersonID
LEFT JOIN (
   SELECT PersonID, COUNT(*) as num
   FROM Person INNER JOIN Pets ON Pets.PersonID = Person.PersonID
   GROUP BY Person.PersonID
) PersonPets ON PersonPets.PersonID = p.PersonID
Eric Petroelje
A: 

You'd need to include multiple count statements in the query. Off the top of my head,

SELECT  p.Name, COUNT(DISTINCT t.Cars), COUNT(DISTINCT o.Children), Count(DISTINCT p.Pets)
FROM Person p
INNER JOIN Transport t ON p.ID = t.PersonID
LEFT JOIN Offspring o ON p.ID = o.PersonID
LEFT JOIN Pets p ON p.ID = o.OwnerID
GROUP BY p.Name
ORDER BY p.Name
Pete OHanlon
+4  A: 

Subquery Factoring (9i+):

WITH count_cars AS (
    SELECT t.person_id
           COUNT(*) num_cars
      FROM CARS c
  GROUP BY t.person_id),
     count_children AS (
    SELECT t.person_id
           COUNT(*) num_children
      FROM CHILDREN c
  GROUP BY t.person_id),
     count_pets AS (
    SELECT p.person_id
           COUNT(*) num_pets
      FROM PETS p
  GROUP BY p.person_id)
   SELECT t.name,
          NVL(cars.num_cars, 0) 'Count(cars)',
          NVL(children.num_children, 0) 'Count(children)',
          NVL(pets.num_pets, 0) 'Count(pets)'
     FROM PERSONS t
LEFT JOIN count_cars cars ON cars.person_id = t.person_id
LEFT JOIN count_children children ON children.person_id = t.person_id
LEFT JOIN count_pets pets ON pets.person_id = t.person_id

Using inline views:

   SELECT t.name,
          NVL(cars.num_cars, 0) 'Count(cars)',
          NVL(children.num_children, 0) 'Count(children)',
          NVL(pets.num_pets, 0) 'Count(pets)'
     FROM PERSONS t
LEFT JOIN (SELECT t.person_id
                  COUNT(*) num_cars
             FROM CARS c
         GROUP BY t.person_id) cars ON cars.person_id = t.person_id
LEFT JOIN (SELECT t.person_id
                  COUNT(*) num_children
             FROM CHILDREN c
         GROUP BY t.person_id) children ON children.person_id = t.person_id
LEFT JOIN (SELECT p.person_id
                  COUNT(*) num_pets
             FROM PETS p
         GROUP BY p.person_id) pets ON pets.person_id = t.person_id
OMG Ponies
+2  A: 

Hi Macleojw,

you could use the COUNT(distinct x.id) synthax:

SELECT person.name, 
       COUNT(DISTINCT car.id) cars, 
       COUNT(DISTINCT child.id) children, 
       COUNT(DISTINCT pet.id) pets
  FROM person
  LEFT JOIN car ON (person.id = car.person_id)
  LEFT JOIN child ON (person.id = child.person_id)
  LEFT JOIN pet ON (person.id = pet.person_id)
 GROUP BY person.name
Vincent Malgrat