SQL LEFT OUTER JOIN subquery - sql

I have SQL data that looks like this:
events
id name capacity
1 Cooking 10
2 Swimming 20
3 Archery 15
registrants
id name
1 Jimmy
2 Billy
3 Sally
registrant_event
registrant_id event_id
1 3
2 3
3 2
I would like to select all of the fields in 'events' as well as an additional field that is the number of people who are currently registered for that event. In this case Archery would have 2 registrants, Swimming would have 1, and Cooking would have 0.
I imagine this could be accomplished in a single query but I'm not sure of the correct syntax. How would a query be written to get that data?

SELECT e.*, ISNULL(ec.TotalRegistrants, 0) FROM events e LEFT OUTER JOIN
(
SELECT event_id, Count(registrant_id) AS TotalRegistrants
FROM registrant_event
GROUP BY event_id
) ec ON e.id = ec.event_id

SELECT Events.ID, Events.Name, Events.Capacity,
ISNULL(COUNT(Registrant_Event.Registrant_ID), 0)
FROM Events
LEFT OUTER JOIN Registrant_Event ON Events.ID = Registrant_Event.Event_ID
GROUP BY Events.ID, Events.Name, Events.Capacity

select d.id1, d.name, d.cappacity, count(d.b_id) as number_of_people
from (select eve.id1,eve.name,eve.cappacity,re_eve.b_id
from eve left join re_eve on eve.id1 = re_eve.b_id) d
group by d.id1, d.name, d.cappacity

I tested this in Oracle 11g, and it seems to work
SELECT e.id, e.name, e.capacity, COUNT(re.event_id)
FROM events e
LEFT JOIN registrant_event re
ON e.id = re.event_id
GROUP BY e.id, e.name, e.capacity

select e.id, e.name, e.capacity, IsNull(re.eventCount,0) from events e
left join (
select count(event_id) as eventCount, event_id from registrant_event group by event_id
) re
on e.id = re.event_id

SELECT e.id, count(*) AS NumRegistrants
FROM events e
LEFT JOIN registrant_event re ON re.event_id=e.id
GROUP BY e.id
Note that this will return 1 instead of 0 for events with no registrants. To get it to show 0 instead, you have to get a little more complicated:
SELECT *,
(SELECT COUNT(*) FROM registrant_event WHERE event_id=id) AS NumRegistrants
FROM events

SELECT
events.*
, COUNT(registrant_event.registrant_id) AS registrantsCount
FROM events
LEFT JOIN registrant_event ON events.id = registrant_event.event_id
GROUP BY events.id

Related

SQL: Which person took which test or not

I want to people and exams they did or did not participate in
I can't figure it out for some reason
Tables
EXAM: ID, NAME, ...etc
PERSON: ID, NAME, ...etc
PERSON_EXAM: ID, PERSON_ID, EXAM_ID, ...etc
Desired result
PERSON_ID EXAM_ID PERSON_EXAM_ID
1 1 1
1 2 NULL
1 3 2
2 1 NULL
2 2 NULL
2 3 3
Currently I did this
select
p.ID as PERSON_ID,
e.ID as EXAM_ID,
(select pe.ID from PERSON_EXAM pe
where pe.PERSON_ID = p.ID and pe.EXAM_ID = e.ID) as PERSON_EXAM_ID
from PERSON p, EXAM e
But I fear it will be slow
I tried to join two tables with one but I can't do that for some reason as
select p.ID as PERSON_ID, e.ID as EXAM_ID, pe.ID as PERSON_EXAM_ID
from PERSON p, EXAM e
left join PERSON_EXAM pe on
p.ID = pe.PERSON_ID and // 'p' isn't recognized because the join is on 'e'
e.ID = pe.ITEM_ID
You have the right idea. Just use the correct syntax. Never use commas in the FROM clause. Always use proper, explicit JOIN syntax:
select p.ID as PERSON_ID, e.ID as EXAM_ID, pe.ID as PERSON_EXAM_ID
from PERSON p cross join
EXAM e left join
PERSON_EXAM pe
on p.ID = pe.PERSON_ID and
e.ID = pe.ITEM_ID;
The semantics of the archaic comma mean that the table alias is not recognized in subsequent on clauses.

SQL Finding Group where attendance less than average

How can i get the number of attendees below average? This is my Oracle query
SELECT e.event_id EventID,c.concert_name ConcertName, c.concert_date,
AVG(e.attendance) Attendance
FROM event e INNER JOIN concert c ON c.concert_id = e.concert_id
WHERE Attendance - AVG(e.Attendance)
GROUP BY c.concert_id ASC;
Using the AVG(..) OVER () analytic function (and without a correlated sub-query):
SELECT eventId,
ConcertName,
Concert_Date
FROM (
SELECT e.event_id EventID,
c.concert_name ConcertName,
c.concert_date,
e.attendance,
AVG(e.attendance) OVER () AS avg_Attendance
FROM event e
INNER JOIN concert c
ON c.concert_id = e.concert_id
)
WHERE attendance < avg_attendance;
You can try with this way:
SELECT e.event_id EventID,c.concert_name ConcertName, c.concert_date,
AVG(e.attendance) Attendance
FROM event e INNER JOIN concert c ON c.concert_id = e.concert_id
WHERE e.attendance < (select AVG(ev.attendance) from event ev)
GROUP BY c.concert_id ASC;

SQL Server Query using GROUP BY

I am having trouble writing a query that will select all Skills, joining the Employee and Competency records, but only return one skill per employee, their newest Skill. Using this sample dataset
Skills
======
id employee_id competency_id created
1 1 1 Jan 1
2 2 2 Jan 1
3 1 2 Jan 3
Employees
===========
id first_name last_name
1 Mike Jones
2 Steve Smith
Competencies
============
id title
1 Problem Solving
2 Compassion
I would like to retrieve the following data
Skill.id Skill.employee_id Skill.competency_id Skill.created Employee.id Employee.first_name Employee.last_name Competency.id Competency.title
2 2 2 Jan 1 2 Steve Smith 2 Compassion
3 1 2 Jan 3 1 Mike Jones 2 Compassion
I was able to select the employee_id and max created using
SELECT MAX(created) as created, employee_id FROM skills GROUP BY employee_id
But when I start to add more fields in the select statement or add in a join I get the 'Column 'xyz' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.' error.
Any help is appreciated and I don't have to use GROUP BY, it's just what I'm familiar with.
The error that you were getting is because SQL Server requires any item in the SELECT list to be included in the GROUP BY if there is an aggregate function being used.
The problem with that is you might have unique values in some columns which can throw off the result. So you will want to rewrite the query to use one of the following:
You can use a subquery to get this result. This gets the max(created) in a subquery and then you use that result to get the correct employee record:
select s.id SkillId,
s.employee_id,
s.competency_id,
s.created,
e.id employee,
e.first_name,
e.last_name,
c.id competency,
c.title
from Employees e
left join Skills s
on e.id = s.employee_id
inner join
(
SELECT MAX(created) as created, employee_id
FROM skills
GROUP BY employee_id
) s1
on s.employee_id = s1.employee_id
and s.created = s1.created
left join Competencies c
on s.competency_id = c.id
See SQL Fiddle with Demo
Or another way to do this is to use row_number():
select *
from
(
select s.id SkillId,
s.employee_id,
s.competency_id,
s.created,
e.id employee,
e.first_name,
e.last_name,
c.id competency,
c.title,
row_number() over(partition by s.employee_id
order by s.created desc) rn
from Employees e
left join Skills s
on e.id = s.employee_id
left join Competencies c
on s.competency_id = c.id
) src
where rn = 1
See SQL Fiddle with Demo
For every non-aggregated column you add to your SELECT statement you need to update your GROUP BY to include it.
This article may help you understand why.
;WITH
MAX_SKILL_created AS
(
SELECT
MAX(skills.created) as created,
skills.employee_id
FROM
skills
GROUP BY
skills.employee_id
),
MAX_SKILL_id AS
(
SELECT
MAX(skills.id) as id,
skills.employee_id
FROM
skills
INNER JOIN MAX_SKILL_created
ON MAX_SKILL_created.employee_id = skills.employee_id
AND MAX_SKILL_created.created = skills.created
GROUP BY
skills.employee_id
)
SELECT
* -- type all your columns here
FROM
employees
INNER JOIN MAX_SKILL_id
ON MAX_SKILL_id.employee_id = employees.employee_id
INNER JOIN skills
ON skills.id = MAX_SKILL_id.id
INNER JOIN competencies
ON competencies.id = skills.competency_id
If you are using SQL Server than you can use OUTER APPLY
SELECT *
FROM employees E
OUTER APPLY (
SELECT TOP 1 *
FROM skills
WHERE employee_id = E.id
ORDER BY created DESC
) S
INNER JOIN competencies C
ON C.id = S.competency_id

using count on when creating a view

im have a view that need to include the count of members volunteering for an event. I'm getting an error 'not a single-group group functions'. Any idea how to resolve this?
CREATE VIEW atbyrd.events__view AS
SELECT e.name, e."DATE", b."LIMIT",b.allocated_amount, COUNT(em.member_id), e.comments
FROM events e INNER JOIN budgets b ON b.event_id = e.id
INNER JOIN event_members em ON em.event_id = e.id;
SELECT e.name,
e."DATE",
b."LIMIT",
b.allocated_amount,
(select COUNT(member_id) from event_members) as mem_count,
e.comments
FROM events e
INNER JOIN budgets b ON b.event_id = e.id
INNER JOIN event_members em ON em.event_id = e.id;
You could use the analytic function count()
SELECT e.name
, e."DATE"
, b."LIMIT"
, b.allocated_amount
, COUNT(em.member_id) over ( partition by em.event_id )
, e.comments
FROM events e
INNER JOIN budgets b
ON b.event_id = e.id
INNER JOIN event_members em
ON em.event_id = e.id;
Simply put this counts the number of members per event_id but as it's not an aggregate function no GROUP BY is required. You receive the same value per event_id.

Why are my SQL statement count different fields from differrent tables in one SQL statement?

I have a SQL query:
SELECT
e.name as estate_name
, g.name as governing_body
, count(s.id) as total_stands
, count(sp.id) as service_providers
FROM estates e
LEFT JOIN governing_bodies
ON e.governing_body_id = g.id
LEFT JOIN stands s
ON s.estate_id = e.id
LEFT JOIN services sp
ON sp.estate_id = e.id
GROUP BY e.id
It seems like my counts multiply each other. If my first count is 3 and second count is 10 the results in service_providers field and total_stands field will be 30.
What am I doing wrong?
What about changing the COUNT(blah) constructs to COUNT(DISTINCT blah) ?
A count() displays the number of rows found for your group. Since you're grouping on estate, it will count the number of rows you join to estate. Joins will multiply the number of rows, so 3 x 10 = 30 sounds like the correct count. Run the query without GROUP BY to see what's happening.
One way to fix it would look like this:
SELECT
e.name as estate_name,
g.name as governing_body,
(select count(*) from stands s where s.estate_id = e.id) as stands,
(select count(*) from services sp where sp.estate_id = e.id) as services
FROM estates e
LEFT JOIN governing_bodies g on e.governing_body_id = g.id
Writing out Alex Martelli's informative answer:
SELECT
e.name as estate_name
, g.name as governing_body
, count(distinct s.id) as total_stands
, count(distinct sp.id) as service_providers
FROM estates e
LEFT JOIN governing_bodies
ON e.governing_body_id = g.id
LEFT JOIN stands s
ON s.estate_id = e.id
LEFT JOIN services sp
ON sp.estate_id = e.id
GROUP BY e.id, g.name
Or, as a more complex alternative with JOIN syntax:
SELECT
e.name as estate_name,
g.name as governing_body,
IsNull(stand_count.total,0) as stand_count,
IsNull(service_count.total,0) as service_count
FROM estates e
LEFT JOIN governing_bodies g on e.governing_body_id = g.id
LEFT JOIN (
select estate_id, total = count(*) from stands group by estate_id
) stand_count on stand_count.estate_id = e.id
LEFT JOIN (
select estate_id, total = count(*) from services group by estate_id
) service_count on service_count.estate_id = e.id
GROUP BY
e.name,
g.name,
IsNull(stand_count.total,0),
IsNull(service_count.total,0)