11

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?

OMG Ponies
  • 325,700
  • 82
  • 523
  • 502
macleojw
  • 4,113
  • 10
  • 43
  • 63

7 Answers7

6

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
  • 325,700
  • 82
  • 523
  • 502
5

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
  • 66,725
  • 9
  • 119
  • 171
1

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
  • 59,820
  • 9
  • 127
  • 177
0

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
  • 30,623
  • 8
  • 82
  • 102
  • 2
    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 Sep 04 '09 at 14:59
  • interesting to see that you give your technical answer your personal flavour ;) – paweloque Sep 04 '09 at 15:01
  • @Eric: you're right, however, the db will probably rewrite this statement. So finally it might become what you proposed. – paweloque Sep 04 '09 at 15:02
  • Sleep deprivation can cause loss of self-restraint :-) – Dirk Sep 04 '09 at 15:19
0

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
  • 3,360
  • 28
  • 33
0

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
  • 1,507
  • 11
  • 13
-1

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
  • 9,086
  • 2
  • 29
  • 28