How to write this query to display a COUNT with other fields - sql

I have following two tables:
Person {PersonId, FirstName, LastName,Age .... }
Photo {PhotoId,PersonId, Size, Path}
Obviously, PersonId in the Photo table is an FK referencing the Person table.
I want to write a query to display all the fields of a Person , along with the number of photos he/she has in the Photo table.
A row of the result will looks like
24|Ryan|Smith|28|6
How to write such query in tsql?
Thanks,

You need a subquery in order to avoid having to repeat all the columns from Person in your group by clause.
SELECT
p.PersonId,
p.FirstName,
p.LastName,
p.Age,
coalesce(ph.PhotoCount, 0) as Photocount
FROM
Person p
LEFT OUTER JOIN
(SELECT PersonId,
COUNT(PhotoId) as PhotoCount
FROM Photo
GROUP BY PersonId) ph
ON p.PersonId = ph.PersonId

SELECT
p.PersonId,
p.FirstName,
p.LastName,
p.Age,
CASE WHEN
t.ThePhotoCount IS NULL THEN 0 ELSE t.ThePhotoCount END AS TheCount
--the above line could also use COALESCE
FROM
Person p
LEFT JOIN
(SELECT
PersonId,
COUNT(*) As ThePhotoCount
FROM
Photo
GROUP BY PersonId) t
ON t.PersonId = p.PersonID

SELECT P.PersonId, FirstName, LastName,Age, COUNT(PhotoId) AS Num
FROM Person P
LEFT OUTER JOIN PHOTO PH ON P.PersonId = PH.PersonId
GROUP BY P.PersonId, FirstName, LastName,Age

select Person.*, count(PhotoId) from Person left join Photo on Person.PersonId = Photo.PersonId

IMO GROUP BY should be the solution, something like this works for me even with other table joins:
SELECT meetings.id, meetings.location, meetings.date, COUNT( users.id ) AS attendees
FROM `meetings`
LEFT JOIN users ON meetings.id = users.meeting_id
WHERE meetings.moderator_id = 'XXX'
GROUP BY meetings.id

Related

need help writing subquery

I'm using a health database and trying to display patients who have visited the health facility more than two times. The basic query I have so far is
SELECT FirstName, LastName
FROM PATIENT
I know I have to use a subquery in there somehow, but I don't know if I need to use Count or any other operators to find patients visiting more than two times.
you could use a join with visit and count for distinct(VisitDate) filtering using having for the count > 1
SELECT FirstName, LastName , count(distinct VisitDate)
FROM PATIENT
inner join VISIT on VISIT.patientID = PATIENT.PatientID
group by FirstName, LastName
having count(distinct VisitDate) > 1
use aggregate function count and having clause for comparison
SELECT P.FirstName, P.LastName,COUNT(V.VisitID) as numberOfVisit
FROM VISIT V
JOIN PATIENT P ON P.PatientID = V.PatientID
GROUP BY V.PatientID, P.FirstName, P.LastName
HAVING COUNT(V.VisitID) > 2
By using sub query you can also get same result but no need that 1st query is more appropriate
select * from (
SELECT P.FirstName, P.LastName,COUNT(V.VisitID) as numberOfVisit
FROM VISIT V
JOIN PATIENT P ON P.PatientID = V.PatientID
GROUP BY V.PatientID, P.FirstName, P.LastName
) as T where T.numberOfVisit>2
select x.FirstName,x.LastName from (
SELECT a.FirstName, a.LastName,count(*) n
FROM PATIENTa,visit b
where a.patientid = b.patientid
group by a.FirstName, a.LastName having count(*) > 2
) x

SQL query find records that not persists in other tables

I need to print all persons which are not students and not teachers. I have three tables. Oracle database. Code so far:
SELECT PersonID, FirstName, LastName, Gender, DateOfBirth
FROM PERSON
INNER JOIN STUDENT S ON PERSON.PersonID = S.StudentID
INNER JOIN TEACHER T ON PERSON.PersonID = T.TeacherID
WHERE PERSON.PersonID != S.StudentID
AND PERSON.PersonID != T.TeacherID;
I guess my query is wrong because it returns 0 results. Do you have any idea what must I change?
" my query is wrong because it returns 0 results"
Inner joins return records when there is a match. You're trying to find PERSON records which join to neither STUDENT nor TEACHER. So, change your query to use outer joins:
SELECT PersonID, FirstName, LastName, Gender, DateOfBirth
FROM PERSON
LEFT OUTER JOIN STUDENT S ON PERSON.PersonID = S.StudentID
LEFT OUTER JOIN TEACHER T ON PERSON.PersonID = T.TeacherID
WHERE S.StudentID is null
AND T.TeacherID is null;
This is an anti-join: it returns records from PERSON which don't match records in STUDENT and TEACHER.
Use not exists:
select p.*
from person p
where not exists (select 1 from teacher t where t.teacherid = p.personid) and
not exists (select 1 from students s where s.studentid = p.personid);
Although you can write this query with left join, I think the version using not exists is almost a direct translation of the question, making it easier to understand.
In Oracle, you can also write this using minus -- if you want only the id:
select personid
from person
minus
select teacherid
from teacher
minus
select studentid
from student;
--Try this with Left Outer Join...
SELECT
P.PersonID,
P.FirstName,
P.LastName,
P.Gender,
P.DateOfBirth
FROM PERSON P
LEFT OUTER JOIN STUDENT S ON P.PersonID = S.StudentID
LEFT OUTER JOIN TEACHER T ON P.PersonID = T.TeacherID
WHERE S.PersonID is null and T.PersonID is null

How to select SQL results based on multiple tables

I need to select results from one table based on certain matching values in a couple of other tables. I have the following tables:
person: id, firstname, lastname
team: id, teamname
player: id, person_id(FK), team_id(FK)
coach: id, person_id(FK), team_id(FK)
I need to return all the coaches and players names for each team. I've only ever used inner joins, and it doesn't seem like I can use those here, so any idea how to do this?
This will give you the coach:
SELECT team.Teamname, person.Firstname, person.Lastname
FROM person
JOIN coach ON person.id = coach.person_id
JOIN team ON coach.team_id = team.id
And this will give you the players:
SELECT team.Teamname, person.Firstname, person.Lastname
FROM person
JOIN player ON person.id = player.person_id
JOIN team ON player.team_id = team.id
So, the non-elegant, simple answer is to just toss it all together with UNION.
Just use an OR in the join to Team
SELECT
P.firstname,
P.lastname,
T.teamname
FROM
person p id
LEFT JOIN player pl
ON p.id = pl.person_id
LEFT JOIN coach c
ON p.id = c.person_id
LEFT JOIN team t
ON pl.team_id = t.id
or.c.team_id = t.id
Or if you perfer if and your database has COALESCE
LEFT JOIN team t
ON COALESCE(pl.team_id,c.team_id) = t.id

SQL Query to Count() multiple tables

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?
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
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
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
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.
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
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.
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

Compare subselect value with value in master select

In MS Access, I have a query where I want to use a column in the outer query as a condition in the inner query:
SELECT P.FirstName, P.LastName, Count(A.attendance_date) AS CountOfattendance_date,
First(A.attendance_date) AS FirstOfattendance_date,
(SELECT COUNT (*)
FROM(SELECT DISTINCT attendance_date
FROM tblEventAttendance AS B
WHERE B.event_id=8
AND B.attendance_date >= FirstOfattendance_date)
) AS total
FROM tblPeople AS P INNER JOIN tblEventAttendance AS A ON P.ID = A.people_id
WHERE A.event_id=8
GROUP BY P.FirstName, P.LastName
;
The key point is FirstOfattendance_date - I want the comparison deep in the subselect to use the value in each iteration of the master select. Obviously this doesn't work, it asks me for the value of FirstOfattendance_date when I try to run it.
I'd like to do this without resorting to VB code... any ideas?
How about:
SELECT
p.FirstName,
p.LastName,
Count(a.attendance_date) AS CountOfattendance_date,
First(a.attendance_date) AS FirstOfattendance_date,
c.total
FROM (
tblPeople AS p
INNER JOIN tblEventAttendance AS a ON
a.people_id = p.ID)
INNER JOIN (SELECT people_id, Count (attendance_date) As total
FROM (
SELECT DISTINCT people_id,attendance_date
FROM tblEventAttendance)
Group By people_id) AS c ON
p.ID = c.people_id
GROUP BY
p.ID, c.total;
Can you change
B.attendance_date >= FirstOfattendance_date
to
B.attendance_date >= First(A.attendance_date)