Basically i have two tables one being doctor the second being appointments, i want to count the appointments made for each doctor but also include any doctors in the list which didn't have any appointments so far i have come up with this.
SELECT DISTINCT doctor.doctor_id
, sum(case when appt_date > 0 then 1 else 0 end) AppointmentCount
FROM appointment,doctor JOIN doctor d
WHERE appointment.doctor_id = d.doctor_id
group by doctor_id;
this prints out each doctor id but makes it so the count for each doctor is exactly the same whereas i want them to have different values based on how many appointments have been made.
Any idea how to fix this?
Select d.doctor_id, count(a.app_date) as count
from doctor d left join appointment a on (d.doctor_id = a.doctor_id)
group by d.doctor_id;
You can check the demo here: SQLFiddle
Remove distinct keyword (note that you are using group by). Avoid using Join in where clause; Try using on.
Left Join shall give you the expected result.
Your query will be:
SELECT d.doctor_id,
sum(case when a.appt_date > 0 then 1 else 0 end) AppointmentCount
FROM doctor d
left join appointment a
on d.doctor_id = a.doctor_id
group by d.doctor_id;
You don't need to use distinct: GROUP BY is already deduplicating the entries (because it's grouping by doctor_id.
The easiest way to do this is to use left join:
select d.doctor_id
, count(a.appt_date) as appointmentCount
from doctor as d
left join appointment as a on d.doctor_id = a.doctor_id
group by d.doctor_id
select doctor.doctor_id, nvl(count(appointment),0)
from doctor, appointment
where doctor.doctor_id = appointment.doctor_id(+)
group by doctor.doctor_id
outer join on appointment with doctor_id, group by doctor_id. use NVL in case no appointments for doctor so you can display zero
Related
All i want to do is to join two tables, list ALL the rows from the first table, find the average from the second table from all the rows, then list only the ones that are greater than the average.
This is wahat i have done so far, and i am only getting one greater than the average but there are others.
SELECT winner_age, AVG(actor_age) FROM oscar_winners
INNER JOIN actors ON actors.id = oscar_winners.id
WHERE winner_age > (
SELECT AVG(actor_age)
)
You don't really need a join here:
SELECT o.WINNER_AGE
FROM OSCAR_WINNERS o
WHERE o.WINNER_AGE > (SELECT AVG(a.ACTOR_AGE)
FROM ACTORS a)
Something like this?
SELECT actors.*, (SELECT AVG(actor_age) from actors) as average
FROM oscar_winners
INNER JOIN actors ON actors.id = oscar_winners.id and actors.winner_age > (SELECT AVG(actor_age) from actors)
The problem with your query is because you are using a where clause, while you should probably be using having:
SELECT w.winner_age, AVG(a.actor_age)
FROM oscar_winners w
INNER JOIN actors a
ON actors.id = oscar_winners.id
group by w.winner_age
having w.winner_age > AVG(a.actor_age)
I have a question regarding an Sqlite3 query.
The task is only one sentence: Select all customers columns which bought more than 4 times (>=5 times)
between 2018-01-01 and 2018-12-31 and did not buy product number 4402
Explanation about all the database tables for this question:
1) Customer
columns: CustomerNum, CustomerName, DateOfBirth, Gender, CustomerAddress, CustomerPhoneNum.
2) Purchase
columns: PurchaseNum, PurchaseTime, CustomerNum, ShippingCompanyNum
3) PurchaseContains
columns: PurchaseNum, SupplierNum, ProductNum, Amount
I've bolded the columns I think are necessary to solve this question.
My query is:
SELECT Customer.* FROM Customer INNER JOIN Purchase
on Customer.CustomerNum = Purchase.CustomerNum
INNER JOIN PurchaseContains
on Purchase.PurchaseNum = PurchaseContains.PurchaseNum
WHERE Purchase.PurchaseTime BETWEEN '2018-01-01' AND '2018-12-31' AND PurchaseContains.ProductNum != 4402
GROUP BY Purchase.CustomerNum
HAVING COUNT(Purchase.PurchaseNum) > 4;
However because of the INNER JOIN, I think some of the rows are being duplicated!
For example this is without the inner join of 'PurchaseContains' :
SELECT Customer.* FROM Customer INNER JOIN Purchase
on Customer.CustomerNum = Purchase.CustomerNum
--COMMENT:
--INNER JOIN PurchaseContains
--on Purchase.PurchaseNum = PurchaseContains.PurchaseNum
WHERE Purchase.PurchaseTime BETWEEN '2018-01-01' AND '2018-12-31' -- COMMENT: AND PurchaseContains.ProductNum != 4402
GROUP BY Purchase.CustomerNum
HAVING COUNT(Purchase.PurchaseNum) > 4;
And this returns only 2 rows. But now it does not check if the person did not have a purchase that contains item number 4402 (It should output only 1 person).
The first SQL Query in this question returns 2078 rows!
I'm lost!
Thanks in advance!
You are correct in your diagnosis of the problem. You can use NOT EXISTS:
SELECT c.*
FROM Customer c INNER JOIN
Purchase p
ON c.CustomerNum = p.CustomerNum
WHERE p.PurchaseTime BETWEEN '2018-01-01' AND '2018-12-31' AND
NOT EXISTS (SELECT 1
FROM PurchaseContains pc
WHERE pc.PurchaseNum = p.PurchaseNum AND
pc.ProductNum = 4402
)
GROUP BY p.CustomerNum
HAVING COUNT(p.PurchaseNum) > 4;
I am using SQL Server to query these three tables that look like (there are some extra columns but not that relevant):
Customers -> Id, Name
Addresses -> Id, Street, StreetNo, CustomerId
Sales -> AddressId, Week, Total
And I would like to get the total sales per week and customer (showing at the same time the address details). I have come up with this query
SELECT a.Name, b.Street, b.StreetNo, c.Week, SUM (c.Total) as Total
FROM Customers a
INNER JOIN Addresses b ON a.Id = b.CustomerId
INNER JOIN Sales c ON b.Id = c.AddressId
GROUP BY a.Name, c.Week, b.Street, b.StreetNo
and even if my SQL skill are close to none it looks like it's doing its job. But now I would like to be able to show 0 whenever the one customer don't have sales for a particular week (weeks are just integers). And I wonder if somehow I should get distinct values of the weeks in the Sales table, and then loop through them (not sure how)
Any help?
Thanks
Use CROSS JOIN to generate the rows for all customers and weeks. Then use LEFT JOIN to bring in the data that is available:
SELECT c.Name, a.Street, a.StreetNo, w.Week,
COALESCE(SUM(s.Total), 0) as Total
FROM Customers c CROSS JOIN
(SELECT DISTINCT s.Week FROM sales s) w LEFT JOIN
Addresses a
ON c.CustomerId = a.CustomerId LEFT JOIN
Sales s
ON s.week = w.week AND s.AddressId = a.AddressId
GROUP BY c.Name, a.Street, a.StreetNo, w.Week;
Using table aliases is good, but the aliases should be abbreviations for the table names. So, a for Addresses not Customers.
You should generate a week numbers, rather than using DISTINCT. This is better in terms of performance and reliability. Then use a LEFT JOIN on the Sales table instead of an INNER JOIN:
SELECT a.Name
,b.Street
,b.StreetNo
,weeks.[Week]
,COALESCE(SUM(c.Total),0) as Total
FROM Customers a
INNER JOIN Addresses b ON a.Id = b.CustomerId
CROSS JOIN (
-- Generate a sequence of 52 integers (13 x 4)
SELECT ROW_NUMBER() OVER (ORDER BY a.x) AS [Week]
FROM (VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) a(x)
CROSS JOIN (SELECT x FROM (VALUES(1),(1),(1),(1)) b(x)) b
) weeks
LEFT JOIN Sales c ON b.Id = c.AddressId AND c.[Week] = weeek.[Week]
GROUP BY a.Name
,b.Street
,b.StreetNo
,weeks.[Week]
Please try the following...
SELECT Name,
Street,
StreetNo,
Week,
SUM( CASE
WHEN Total IS NULL THEN
0
ELSE
Total
END ) AS Total
FROM Customers a
JOIN Addresses b ON a.Id = b.CustomerId
RIGHT JOIN Sales c ON b.Id = c.AddressId
GROUP BY a.Name,
c.Week,
b.Street,
b.StreetNo;
I have modified your statement in three places. The first is I changed your join to Sales to a RIGHT JOIN. This will join as it would with an INNER JOIN, but it will also keep the records from the table on the right side of the JOIN that do not have a matching record or group of records on the left, placing NULL values in the resulting dataset's fields that would have come from the left of the JOIN. A LEFT JOIN works in the same way, but with any extra records in the table on the left being retained.
I have removed the word INNER from your surviving INNER JOIN. Where JOIN is not preceded by a join type, an INNER JOIN is performed. Both JOIN and INNER JOIN are considered correct, but the prevailing protocol seems to be to leave the INNER out, where the RDBMS allows it to be left out (which SQL-Server does). Which you go with is still entirely up to you - I have left it out here for illustrative purposes.
The third change is that I have added a CASE statement that tests to see if the Total field contains a NULL value, which it will if there were no sales for that Customer for that Week. If it does then SUM() would return a NULL, so the CASE statement returns a 0 instead. If Total does not contain a NULL value, then the SUM() of all values of Total for that grouping is performed.
Please note that I am assuming that Total will not have any NULL values other than from the RIGHT JOIN. Please advise me if this assumption is incorrect.
Please also note that I have assumed that either there will be no missing Weeks for a Customer in the Sales table or that you are not interested in listing them if there are. Again, please advise me if this assumption is incorrect.
If you have any questions or comments, then please feel free to post a Comment accordingly.
I am trying to create an SQL query in MS Access that will show how many appointments an employee will have in the current month, even if it is 0. It is very similar to this question, but i can't get it to work with a WHERE clause.
I have 3 tables:
tblEmployees
-employeeID PK
-FirstName ETC
tblEngineersAppts1
-ApptID PK
-EmployeeID*
tblEngineersAppts2
-DiaryID PK
-ApptDate
-ApptID*
I want to show all employees, a COUNT of all appointments (DiaryID) in tblEngineersAppts2 even if there are none where ApptDate is the current month.
This is my query, it only shows employees that have an appointment in the current month, it doesn't show those who have none.
SELECT tblEmployees.EmployeeID, Count(tblEngineersAppts2.DiaryID) AS CountOfDiaryID
FROM (tblEmployees
LEFT JOIN tblEngineersAppts1 ON tblEmployees.EmployeeID = tblEngineersAppts1.EmployeeID)
LEFT JOIN tblEngineersAppts2 ON tblEngineersAppts1.ApptID = tblEngineersAppts2.ApptID
WHERE (((Format$([ApptDate],'MM/YY'))='03/17'))
GROUP BY tblEmployees.EmployeeID;
Thanks
The problem is when you put the WHERE condition you make the LEFT JOIN an INNER JOIN
WHERE (((Format$([ApptDate],'MM/YY'))='03/17'))
So Include the ApptDate constraint in the ON condition.
SELECT tblEmployees.EmployeeID, Count(tblEngineersAppts2.DiaryID) AS CountOfDiaryID
FROM (tblEmployees
LEFT JOIN tblEngineersAppts1 ON tblEmployees.EmployeeID = tblEngineersAppts1.EmployeeID)
LEFT JOIN tblEngineersAppts2
ON ( tblEngineersAppts1.ApptID = tblEngineersAppts2.ApptID
AND Format$([ApptDate],'MM/YY')='03/17'
)
GROUP BY tblEmployees.EmployeeID;
I think you could do something like this:
SELECT
tblEmployees.EmployeeID,
(
SELECT Count(tblEngineersAppts2.DiaryID)
FROM tblEngineersAppts1
JOIN tblEngineersAppts2 ON tblEngineersAppts1.ApptID = tblEngineersAppts2.ApptID
WHERE tblEmployees.EmployeeID = tblEngineersAppts1.EmployeeID
AND (((Format$([ApptDate],'MM/YY'))='03/17'))
) AS CountOfDiaryID
FROM
tblEmployees
I've just got myself a little bit stuck with some SQL. I don't think I can phrase the question brilliantly - so let me show you.
I have two tables, one called person, one called appointment. I'm trying to return the number of appointments a person has (including if they have zero). Appointment contains the person_id and there is a person_id per appointment. So COUNT(person_id) is a sensible approach.
The query:
SELECT person_id, COUNT(person_id) AS "number_of_appointments"
FROM appointment
GROUP BY person_id;
Will return correctly, the number of appointments a person_id has. However, a person who has 0 appointments isn't returned (obviously as they are not in that table).
Tweaking the statement to take person_id from the person table gives me something like:
SELECT person.person_id, COUNT(appointment.person_id) AS "number_of_appointments"
FROM appointment
JOIN person ON person.person_id = appointment.person_id
GROUP BY person.person_id;
This however, will still only return a person_id who has an appointment and not what I want which is a return with persons who have 0 appointments!
Any suggestions please?
You want an outer join for this (and you need to use person as the "driving" table)
SELECT person.person_id, COUNT(appointment.person_id) AS "number_of_appointments"
FROM person
LEFT JOIN appointment ON person.person_id = appointment.person_id
GROUP BY person.person_id;
The reason why this is working, is that the outer (left) join will return NULL for those persons that do not have an appointment. The aggregate function count() will not count NULL values and thus you'll not get a zero.
If you want to learn more about outer joins, here is a nice tutorial: http://sqlzoo.net/wiki/Using_Null
You must use LEFT JOIN instead of INNER JOIN
SELECT person.person_id, COUNT(appointment.person_id) AS "number_of_appointments"
FROM person
LEFT JOIN appointment ON person.person_id = appointment.person_id
GROUP BY person.person_id;
if you do the outer join (with the count), and then use this result as a sub-table, you can get 0 as expected (thanks to the nvl function)
Ex:
select P.person_id, nvl(A.nb_apptmts, 0) from
(SELECT person.person_id
FROM person) P
LEFT JOIN
(select person_id, count(*) as nb_apptmts
from appointment
group by person_id) A
ON P.person_id = A.person_id
USE join to get 0 count in the result using GROUP BY.
simply 'join' does Inner join in MS SQL so , Go for left or right join.
If the table which contains the primary key is mentioned first in the QUERY then use LEFT join else RIGHT join.
EG:
select WARDNO,count(WARDCODE) from MAIPADH
right join MSWARDH on MSWARDH.WARDNO= MAIPADH.WARDCODE
group by WARDNO
.
select WARDNO,count(WARDCODE) from MSWARDH
left join MAIPADH on MSWARDH.WARDNO= MAIPADH.WARDCODE group by WARDNO
Take group by from the table which has Primary key and count from the another table which has actual entries/details.
To change even less on your original query, you can turn your join into a RIGHT join
SELECT person.person_id, COUNT(appointment.person_id) AS "number_of_appointments"
FROM appointment
RIGHT JOIN person ON person.person_id = appointment.person_id
GROUP BY person.person_id;
This just builds on the selected answer, but as the outer join is in the RIGHT direction, only one word needs to be added and less changes. - Just remember that it's there and can sometimes make queries more readable and require less rebuilding.
The problem with a LEFT JOIN is that if there are no appointments, it will still return one row with a null, which when aggregated by COUNT will become 1, and it will appear that the person has one appointment when actually they have none. I think this will give the correct results:
SELECT person.person_id,
(SELECT COUNT(*) FROM appointment WHERE person.person_id = appointment.person_id) AS 'Appointments'
FROM person;