SQL: Which person took which test or not - sql

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.

Related

Count occurrences in many to many

In my database i have following tables:
Person (
id,
name,
agentId
)
Agent (
id,
title
)
Agency (
id,
name
)
AgentAgency (
id,
agentId,
agencyId
)
I need query that will get all info about Person -> Agents with extra attribute numberOfAgencies that will show number of agencies of each agent, AND i need to show one more attribute agencyName that will show me name of first or only agency that user have (i need it in case agent have only 1 agency).
I tried something like this but without any success.
SELECT *, COUNT (aa.agentId) as numberOfAgencies
FROM agentAgencies as aa
LEFT JOIN agent as a ON a.id = aa.agentId
LEFT JOIN agency as ag ON aa.agencyId= ag.id
LEFT JOIN person as p ON p.id = ag.personId
GROUP BY ag.id, aa.id, p.id, a.id
For example i expect response like this:
PersonName John, AgencyName Cool Agency, numberOfAgencies 4
SELECT
MAX(p.Name) PersonName,
count(a.id) NoOfAgencies,
MAX(a.name) AgencyName
FROM persons p
LEFT OUTER JOIN agent g ON g.Id=p.agentId
LEFT OUTER JOIN AgentAgency aa ON aa.agentId = g.Id
LEFT OUTER JOIN Agency a on a.id = aa.agencyId
GROUP BY a.Id

SQL - using ANSI standard

My below query using two select statements works:
create or replace view q2 as
select count(p.id) nstudents,
(select count(p.id) nstaff
from people p, staff s where p.id = s.id)
from people p, students s where p.id = s.id;
but when i include a third sub query:
create or replace view q2 as
select count(p.id) nstudents,
(select count(p.id) nstaff,
(select count(p.id) nboth
from people p, students s, staff t where p. id = s.id and p.id = t.id)
from people p, staff t where p.id = t.id)
from people p, students s where p.id = s.id;
it gives me the following error:
ERROR: subquery must return only one column
LINE 3: (select count(p.id) nstaff,
Am i making some mistake while including a third query or is there a limit of just 2 nested select statements?
The version with the additional sub-query fails because you insert the second sub-query into the first, when it should come after it like this:
select count(p.id) nstudents,
(select count(p.id) from people p, staff t where p.id = t.id) nstaff ,
(select count(p.id) from people p, students s, staff t where p. id = s.id and p.id = t.id) nboth
from people p, students s where p.id = s.id;
However, the query could just as well be written using conditional aggregation (and to use explicit ANSI standard joins) like this:
select
sum(case when s.id is not null then 1 end) nstudents,
sum(case when t.id is not null then 1 end) nstaff,
sum(case when s.id is not null and t.id is not null then 1 end) nboth
from people p
left join students s on p. id = s.id
left join staff t on p.id = t.id

SQL Server: select query from multiple tables

I got 3 tables: projects, employee and project_employee
employee
ID (int, PK)
Name
projects
project_id (int, PK)
project_name
project_employee
project_id (int, PK)
employee_id (int, PK)
What I trying to do is write a query that get ID and Name of all employees that are not in a project, for example project number 9.
So I tried:
SELECT ID, Name
FROM [employee], [project_employee]
WHERE [employee].ID != [project_employee].emp_id AND [project_employee].project_id = 9;
but I always get empty result, something must be wrong with my logic?
You can do it using NOT EXISTS :
SELECT u.ID, u.Name
FROM [User] u
WHERE NOT EXISTS ( SELECT *
FROM [project_employee] pe
WHERE pe.project_id = 9
AND pe.employee_id = u.ID);
Instead try something like this, it should get you all users that are not part of project_id = 9
SELECT u.ID, u.Name
FROM [User] u
WHERE u.ID NOT IN (SELECT pe.employee_id
FROM [project_employee] pe
WHERE pe.project_id = 9);
SELECT ID, NAME
FROM employee e
LEFT JOIN project_employee pe
ON pe.employee_id = e.id
AND pe.project_id = 9
WHERE pe.employee_id IS NULL
You don't even need projects table since you are not looking for employees on a project.
You can use a LEFT JOIN to get the required data
select e.id,e.name
from employee e
left join project_employee pe
on e.id = pe.employee_id
left join projects p
on pe.project_id = p.project_id
where p.project_id is null
(OR) Just left join with project_employee table. No condition has been checked assuming that all employees with no project at hand is the desired output.
select e.id,e.name
from employee e
left join project_employee pe
on e.id = pe.employee_id
where pe.project_id is null
He asked for the ability to check that the employees are not in a specific project. The not in solution is fine, but I generally prefer using left joins.
SELECT ID, NAME
FROM employee e
LEFT JOIN project_employee pe
ON pe.emp_id = e.id
and pe.project_id = 9
WHERE pe.employee_id IS NULL

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

Sql query to get total number of students in a class

I have this SQL query:
SELECT c.id as ID, c.class_name as CLASS, COUNT(e.student_id) AS STUDENT_COUNT
FROM classes as c
LEFT JOIN enrollments as e on e.class_id = c.id
where c.teacher_id = 8 AND e.approved = 1
group by c.class_name;
What I try to do is to get all the classes and the number of students that have been approved in those classes. The sql query should return something like
ID CLASS STUDENT_COUNT
1 Math 0
2 Biology 2
3 Algebra 1
4 Literature 5
The problem is that because of e.approved = 1 i don't get any classes at all. Suggestions?
My schema is as follows
CLASSES table
id, teacher_id, class_name, grade
ENROLLMENTS table
id, class_id, student_id, approved
Change your query to use the e.approved in the left join condition:
SELECT c.id as ID, c.class_name as CLASS, COUNT(e.student_id) AS STUDENT_COUNT
FROM classes as c
LEFT JOIN enrollments as e on e.class_id = c.id
AND e.approved = 1
where c.teacher_id = 8
group by c.class_name;