First and foremost, this is part of an assignment, but I'm trying to get more clarification on how the count() function works when querying a db.
The question is: List the name, SSN and the number of courses the student has taken (courses with the same CourseNumber taken in different quarters are counted as different courses).
I've been querying using this:
SELECT S.Name, S.SSN, COUNT(*)
FROM Student S, Enrollment E
WHERE S.SSN = E.SSN
GROUP BY S.SSN
which seems to return the correct answer, but I'm not sure why. As a result, I can't seem to get the next questions (assuming courses with the same CourseNumber taken in different quarters are considered as one course) correct.
Here are the create table commands so you can see which table holds what info:
CREATE TABLE Student(
SSN INT(9),
Name VARCHAR(20),
Major VARCHAR(30),
PRIMARY KEY (SSN)
);
CREATE TABLE Course(
CourseNumber INT(5),
PrerequisiteCourseNumber INT(10),
CourseTitle VARCHAR(10),
NumberUnits INT(2),
PRIMARY KEY (CourseNumber)
);
CREATE TABLE Section(
CourseNumber INT(5),
Quarter VARCHAR(10),
RoomNumber INT(5),
DayTime VARCHAR(20),
PRIMARY KEY (CourseNumber,Quarter),
FOREIGN KEY (CourseNumber) REFERENCES Course(CourseNumber)
);
CREATE TABLE Enrollment(
SSN INT(9),
CourseNumber INT(5),
Quarter VARCHAR(10),
Grade VARCHAR(1),
PRIMARY KEY (SSN,CourseNumber,Quarter),
FOREIGN KEY (SSN) REFERENCES Student(SSN),
FOREIGN KEY (CourseNumber) REFERENCES Course(CourseNumber),
FOREIGN KEY (Quarter) REFERENCES Section(Quarter)
);
Any pointers?
what you are doing is using old join syntax (pre ansi syntax) where you do a cross join of tables and use the where to turn it into a join. an equivalent query would be this.
SELECT S.Name, S.SSN, COUNT(*)
FROM Student S
JOIN Enrollment E ON S.SSN = E.SSN
GROUP BY S.SSN
Now to answer your question about what the count is doing and stuff..
COUNT() returns a count of every row that is returned.
GROUP BY groups all of the records by a common ground aka your SSN field. so if a student has 5 SSN rows in the table then his count will be 5.
Now for the part you are looking for with course numbers.. you need to JOIN those tables on appropriate fields and add a field to your group by for each quarter
aka add to the previous code..
WHERE E.quarter = whatever_quarter_you_want
you can add more to this query if you need to. but add data to your question if you want a more full answer.
Your current query is:-
SELECT S.Name, S.SSN, COUNT(*)
FROM Student S
INNER JOIN Enrollment E
ON S.SSN = E.SSN
GROUP BY S.SSN
What this is doing is joining student to enrollment, so giving multiple rows for each student, one for each course / quarter they are enrolled on. It is then grouped by SSN (student number?) to count up the number of course / quarters they are enrolled on. You should really group by S.Name as well (while MySQL won't object, most flavours of SQL would).
Note that COUNT(*) counts the number of rows. You could use COUNT(E.CourseNumber) which would count the number of rows where course number is not null. Not really useful here, but can be useful with LEFT OUTER JOINs. You can also use COUNT(DISTINCT CourseNumber) to count the number of unique non null course numbers for the student.
A LEFT OUTER JOIN might also be better as this would enable you to return 0 as the counts for students who exist but who are not enrolled in any courses:-
SELECT S.Name, S.SSN, COUNT(DISTINCT E.CourseNumber)
FROM Student S
LEFT OUTER JOIN Enrollment E
ON S.SSN = E.SSN
GROUP BY S.Name, S.SSN
SELECT S.Name, S.SSN, COUNT(*)
FROM Student S, Enrollment E, Section Sec, Course C
WHERE S.SSN = E.SSN AND E.CourseNumber = Sec.CourseNumber
AND Sec.CourseNumber = C.CourseNumber AND Sec.Quarter like E.Quarter
GROUP BY S.Name, S.SSN
Related
So I'm having an issue with trying to return records that have two conditions which are not equal.
I have three tables: Person, Student, and Instructor. I'm trying to return a list of persons who are neither students or instructors.
Here are the tables (they are all populated with values that fit all criteria):
CREATE TABLE Person (
Name char (20),
ID char (9) not null,
Address char (30),
DOB date,
Primary key (ID)
);
CREATE TABLE Instructor (
InstructorID char (9) not null references Person (ID),
Rank char (12),
Salary integer,
PRIMARY KEY (InstructorID)
);
CREATE TABLE Student (
StudentID char (9) not null REFERENCES Person (ID),
Classification char (10),
GPA DOUBLE,
MentorID char (9) REFERENCES Instructor (InstructorID),
CreditHours integer,
PRIMARY KEY (StudentID)
);
And here is my answer/query:
SELECT Person.ID, Person.Name, Person.DOB
FROM Person
RIGHT JOIN Student ON Person.ID = Student.StudentID
RIGHT JOIN Instructor ON Person.ID = Instructor.InstructorID
WHERE NOT Person.ID = Student.StudentID AND Person.ID =
Instructor.InstructorID;
Did I screw up something on my joins? I'm not getting any errors, but the query isn't returning anything either. I've tried all joins (left, right, full) to no avail.
Any help is greatly appreciated.
You may use NOT IN to determine if the records are not in both of those tables
SELECT id,
name,
dob
FROM Person
WHERE id NOT IN (SELECT studentid
FROM student)
AND id NOT IN (SELECT instructorid
FROM instructor);
You are pretty close. First, use LEFT JOIN so you keep everything in the first table. Second, use comparisons to NULL to see if there are matches:
SELECT p.ID, p.Name, p.DOB
FROM Person p LEFT JOIN
Student s
ON p.ID = s.StudentID LEFT JOIN
Instructor i
ON p.ID = i.InstructorID
WHERE s.StudentID IS NULL AND i.InstructorID IS NULL;
Don't judge me, I'm new to SQL querying. I got scheme, like the one shown on picture below. So, there are 2 tables, first one Employees contains EmployeeID, FirstName, LastName, DateOfBirth and DepartmentID. The second one is called Department and contains DepartmentID and DepartmentName .
I want to return FirstName, LastName and DepartmentName for the oldest employee from each department containing more than 20 employees.
My solution is the following query :
SELECT FirstName, LastName, DepartmentName
FROM employees
LEFT JOIN department
ON employees.DepartmentID = department.DepartmentID
WHERE (employees.DateOfBirth =
(SELECT MIN(employees.DateOfBirth ) FROM (
SELECT *FROM employees WHERE employees.DepartmentID IN (
SELECT employees.DepartmentID FROM employees GROUP BY DepartmentID HAVING COUNT(*) > 20)));
I think that logic is fine, because inner SELECT statement will return ID's of every department with more than 20 employees, and the outer should return to oldest employee.
The problem that I have is when I try to execute this query, it is returning SQL error every derived table must have it's own alias.
I've tried putting alias on each derived table, but outcome is still the same.
Please, help me with this one.
Also, if someone has different solution, please share it.
Thank You.
Addition which strawberry asked for, Create queries
CREATE TABLE Employees
(
EmployeeID int,
FirstName varchar(10),
LastName varchar(15),
DateOfBirth date,
DeparmentID int
)
CREATE TABLE Department
(
DepartmentID int,
DepartmentName varchar(15)
)
Your query is tricky to read due to inconsistent formatting. So I'll clean it up as follows:
SELECT FirstName, LastName, DepartmentName
FROM employees
LEFT JOIN department
ON employees.DepartmentID = department.DepartmentID
WHERE (employees.DateOfBirth =
(
SELECT MIN(employees.DateOfBirth)
FROM (
SELECT *
FROM employees
WHERE employees.DepartmentID IN (
--Departments with more than 20 employees
SELECT employees.DepartmentID
FROM employees
GROUP BY DepartmentID
HAVING COUNT(*) > 20)
) -- You need an alias here.
-- Also from this point you were missing closing brackets.
Problems with your query:
Obviously the missing alias and closing brackets meant you couldn't even test your query.
Also SELECT MIN(employees.DateOfBirth) returns only a single value. Not a value per department.
So your overall result includes only the oldest employee across all the 'big' departments. (Unless the oldest employee in each department happened to have the same birth date.)
It could also include results from a smaller department if any employee happened to have the same birth date the oldest from the big departments. And that employee needn't even be the oldest in their department!
You also have some inefficiencies by using more sub-queries than necessary.
CTEs (common table expressions) are great at simplifying complex queries. But I don't know if mysql supports them. So this solution still uses sub-queries.
SELECT e.FirstName, e.LastName, d.DepartmentName
FROM employees e -- I prefer short aliases
INNER JOIN (
-- This sub-query returns the earliest birth date within each
-- big department. This needs to be an aliased query so you
-- can join to other tables for your desired columns.
SELECT DepartmentID, MIN(DateOfBirth) AS MinDOB -- Must alias column
FROM employees
WHERE DepartmentID IN (
-- Big departments
SELECT DepartmentID
FROM employees
GROUP BY DepartmentID
HAVING COUNT(*) > 20
)
GROUP BY DepartmentID
) ddob -- Alias Department Date of Birth
-- As a result of inner joining to ddob your employees
-- will be filtered to only those that match the relevant
-- ones identified in the query.
ON e.DepartmentID = ddob.DepartmentID
AND e.DateOfBirth = ddob.MinDOB
INNER JOIN Department d
ON d.DepartmentID = e.DepartmentID
Something to note in the above solution, if 2 employees are tied for being oldest in a department, both will be returned.
This approach is structurally similar to yours, but you could also approach the problem from another direction.
Start out getting oldest employees in ALL departments.
And only at the end filter the result according to department size.
I'll leave that to you to try. I suspect the query would be a little simpler.
The following SQL script correspond to your Class Diagram:
CREATE TABLE Departments (
DepartmentID int AUTO_INCREMENT PRIMARY KEY,
DepartmentName varchar(15)
);
CREATE TABLE Employees (
EmployeeID int AUTO_INCREMENT PRIMARY KEY,
FirstName varchar(10),
LastName varchar(15),
DateOfBirth date,
DepartmentID int,
FOREIGN KEY (DepartmentID) REFERENCES Departments(DepartmentID)
);
Following your classes diagram, there is a department for each employee. So, is the reason of using INNER JOIN. I think the following query do what you want:
SELECT ee.FirstName, ee.LastName, ee.DateOfBirth, t.DepartmentName
FROM
(
SELECT e.DepartmentID, d.DepartmentName, MIN(e.DateOfBirth) AS DateOfBirth
FROM Employees AS e
INNER JOIN Departments AS d ON e.DepartmentID = d.DepartmentID
WHERE e.DepartmentID IN (
SELECT DepartmentID
FROM Employees
GROUP BY DepartmentID HAVING COUNT(DepartmentID) > 20
)
GROUP BY e.DepartmentID
) AS t
INNER JOIN Employees AS ee
WHERE ee.DepartmentID = t.DepartmentID AND ee.DateOfBirth = t.DateOfBirth
Example of output:
FirstName LastName DateOfBirth DepartmentName
fisrt14 last14 02/01/2000 SI
fisrt31 last31 12/01/2003 Finance
You improve its performance!
CREATE TABLE Institutions
(
Institution_ID INTEGER PRIMARY KEY,
Institution_Name VARCHAR(200))
CREATE TABLE Contact_Persons
(
Contact_No INTEGER PRIMARY KEY,
First_Name VARCHAR(60) NOT NULL,
Last_Name VARCHAR(60) NOT NULL,
Institution_ID INTEGER,
FOREIGN KEY (Institution_ID) REFERENCES Institutions(Institution_ID))
How can I write a mysql query that shows the Institutions that have more than one one Contact Person at them? The Query should show Institution_ID and Institution_Name from Table Institutions. The following Query works but It only shows Institution_ID. I want to see Institution_Name too. I think there must be a join between these two tables. Please help.
SELECT Institution_ID
FROM Contact_Persons
GROUP BY Institution_ID
HAVING COUNT(*)>1;
You were right, you just need to join your two tables, and make sure you also group by everything you want to select (unless it is an aggregate), so in this case add Institution_Name to the group by clause:
SELECT i.Institution_ID, i.Institution_Name
FROM Contact_Persons AS cp
INNER JOIN Institutions AS i
ON i.Institution_ID = cp.Institution_ID
GROUP BY i.Institution_ID, i.Institution_Name
HAVING COUNT(*) > 1;
Try this
SELECT I.Institution_ID,I.Institution_name
FROM Contact_Persons as C inner join Institutions as I on
C.Institution_ID=I.Institution_ID
GROUP BY I.Institution_ID,I.Institution_name
HAVING COUNT(*)>1;
I have four schemas:
takes(ID,course_id,sec_id,semester,year)
student(ID,name,dept_name,tot_credit)
course(course_id,title,dept_name,credits)
department(dept_name,building,budget)
I want to create a query that finds the name and id of each Astronomy student whose name begins with the letter âTâ and who has not taken at least 16 Astronomy courses.
What's the easiest way I could do this?
I already wrote this beginning bit
SELECT name, id
FROM student
WHERE dept_name='Astronomy' AND name LIKE '%T%'
I'm not quite sure how to finish this off.
Any help would be greatly appreciated :)
Here's the result
NAME ID CLASS_TAKEN
-------------------- ----- -----------
Tolle 38279 12
Teo 62268 13
Tolle 93223 13
Tsukamoto 17707 5
Titi 11576 9
Teo 91772 12
Toraichi 50387 11
Tewari 80754 14
Tiroz 64091 14
9 rows selected
I need Teo with the id 91772 and Tewari 80754 to be gone
Given my reading of the requirements and the comments it's pretty clear that the question is not very clear. :-) What you're looking for are students where (total # of courses given by the Astronomy department) - (# of Astronomy courses taken by student) >= 16. So, how do we find these values? First, let's start with the total number of courses given by the Astronomy department. This is pretty simple:
SELECT COUNT(*) AS ASTRONOMY_COURSE_COUNT
FROM COURSE
WHERE DEPT_NAME = 'ASTRONOMY'
Now, the second part is to determine how many courses given by the Astronomy department each student has taken. To do this we need to start with the student, join to the courses the student has taken (the TAKES table), then join to the COURSES table to find out which department each course is part of. Something like the following should do it:
SELECT s.ID, s.NAME, COUNT(*) AS STUDENT_ASTRO_COUNT
FROM STUDENT s
INNER JOIN TAKES t
ON t.ID = s.ID
INNER JOIN COURSE c
ON c.COURSE_ID = t.COURSE_ID
WHERE c.DEPT_NAME = 'ASTRONOMY' AND
s.NAME LIKE 'T%'
GROUP BY s.ID, s.NAME;
OK, now we need to put this together. You've tagged this question for both Oracle and MySQL so I'm going to guess you'll accept valid syntax for either database; thus I'll use Oracle Common Table Expression syntax to pull everything together:
WITH ASTRONOMY_COURSES AS (SELECT COUNT(*) AS ASTRONOMY_COURSE_COUNT
FROM COURSE
WHERE DEPT_NAME = 'ASTRONOMY'),
STUDENT_ASTRO_COURSES AS (SELECT s.ID,
s.NAME,
COUNT(*) AS STUDENT_ASTRO_COUNT
FROM STUDENT s
INNER JOIN TAKES t
ON t.ID = s.ID
INNER JOIN COURSE c
ON c.COURSE_ID = t.COURSE_ID
WHERE c.DEPT_NAME = 'ASTRONOMY' AND
s.NAME LIKE 'T%'
GROUP BY ID)
SELECT s.ID,
s.NAME,
s.STUDENT_ASTRO_COUNT,
a.ASTRONOMY_COURSE_COUNT - s.STUDENT_ASTRO_COUNT AS UNTAKEN_COUNT
FROM STUDENT_ASTRO_COURSES s
CROSS JOIN ASTRONOMY_COURSES a
WHERE a.ASTRONOMY_COURSE_COUNT - s.STUDENT_ASTRO_COUNT >= 16;
Note here that a CROSS JOIN is used to put together the subqueries. This means that all the rows of each subquery are joined to all the rows of the other subquery - but since in this case the ASTRONOMY_COURSES subquery will only return a single row what we're doing is appending the ASTRONOMY_COURSE_COUNT value onto each row returned by the STUDENT_ASTRO_COURSES subquery.
That should at least get you pretty close. Amend as needed.
Not tested on animals - you'll be first! :-)
Share and enjoy.
Do you need to use all tables?
Table department has no links with the student,
Table takes has no links with the student,
Table coursehas no links with the student.
If student lists total credits that are all Astronomy I think this can be used:
select name, id, MAX(tot_credit) as credits
from student
where dept_name='Astronomy' and name like 'T%'
group by name, id
having MAX(tot_credit)<=16
PS - you schema is not good; PK-FK references are missing
Your query will need to reference more tables than just the student table.
Your tables seem be missing some important information, which student has taken which course. There's a table named takes, but there doesn't appear to be any relationship between takes and student.
So first, figure out how to list the students along with the Astronomy courses they have taken. Each row will identify the student and a course.
SELECT s.id AS student_id
, s.name AS student_name
, t.???
FROM student s
JOIN ??? t
ON t.student_id = s.id
WHERE ...
You may also need to include another "join" to an additional table, in order to identify which course a student has taken is an Astronomy course.
To also include students that have not take any Astronomy courses, you can use an outer join, rather than an inner join. (That would mean including the LEFT keyword before JOIN, and relocating predicates from the WHERE clause to the ON clause. (A predicate in the WHERE clause that can only be satisfied by non-NULL values will negate the outer-ness of the join.)
Once you have a query that returns that set (students along with any astronomy courses they've taken), you can then add a GROUP BY clause to "collapse" a set of rows into a single row. (Looks like you want the rows "grouped" by student.)
And then an aggregate function like COUNT() or SUM() can be used to get a count of rows for each group. (If you don't want to count any re-takes of a course (a "duplicate" course for a student) you may be able to make use of the COUNT(DISTINCT t.foo) form.
And then a HAVING clause can be added to the query, to compare the value returned from the aggregate expression to a constant value, to return only rows that satisfy a specific condition.
FOLLOWUP
Assuming:
CREATE TABLE course
( id INT UNSIGNED NOT NULL COMMENT 'PK'
, title VARCHAR(30) NOT NULL COMMENT 'course title'
, dept_name VARCHAR(30) NOT NULL COMMENT 'FK ref dept.name'
, credits DECIMAL(5,2) COMMENT 'credit hours'
, PRIMARY KEY (id)
);
CREATE TABLE student
( id INT UNSIGNED NOT NULL COMMENT 'PK'
, name VARCHAR(30) NOT NULL COMMENT 'student name'
, dept_name VARCHAR(30) NOT NULL COMMENT 'FK ref dept.name'
, tot_credit INT COMMENT '?'
, PRIMARY KEY (id)
);
CREATE TABLE takes
( student_id INT UNSIGNED NOT NULL COMMENT 'FK ref student.id'
, course_id INT UNSIGNED NOT NULL COMMENT 'FK ref course.id'
, sec_id INT UNSIGNED NOT NULL COMMENT '?'
, semester INT UNSIGNED NOT NULL COMMENT '?'
, year INT UNSIGNED NOT NULL COMMENT '?'
, PRIMARY KEY (student_id, course_id, sec_id, semester, year)
, CONSTRAINT FK_takes_course FOREIGN KEY (course_id) REFERENCES course (id)
, CONSTRAINT FK_takes_student FOREIGN KEY (student_id) REFERENCES student (id)
);
Query to get a list of students...
SELECT s.id
, s.name
FROM student s
WHERE s.name LIKE 'T%'
AND s.dept_name = 'ASTRONOMY'
Get list of students along with the courses they've taken, returning the id of the ASTRONOMY courses they've taken...
SELECT s.id AS student_id
, s.name AS student_name
, c.id AS course_id
FROM student s
LEFT
JOIN takes t
ON t.student_id = t.id
LEFT
JOIN course c
ON c.id = t.course_id
AND c.dept_name = 'ASTRONOMY'
WHERE s.name LIKE 'T%'
AND s.dept_name = 'ASTRONOMY'
Collapse the rows to one per student using a GROUP BY, and use aggregate functions to get counts or totals.
SELECT s.id AS student_id
, s.name AS student_name
, SUM(c.credits) AS total_astronomy_credits_taken
, COUNT(c.id) AS count_astronomy_courses_taken
, COUNT(DISTINCT c.id) AS count_distinct_astronomy_courses_taken
FROM student s
LEFT
JOIN takes t
ON t.student_id = t.id
LEFT
JOIN course c
ON c.id = t.course_id
AND c.dept_name = 'ASTRONOMY'
WHERE s.name LIKE 'T%'
AND s.dept_name = 'ASTRONOMY'
GROUP
BY s.id
, s.name
To omit rows from this resultset, add a HAVING clause. For example, to exclude rows where total_astronomy_credits_taken is greater than or equal to 12...
HAVING total_astronomy_credits_taken >= 12
If you want the rows returned in a certain sequence, specify that in an ORDER BY clause
ORDER BY s.id
If you want to replace NULL values from the aggregates with zeroes, you can warp the aggregate expression in an IFNULL(foo,0) function, e.g.
, IFNULL(COUNT(c.id),0) AS count_astronomy_courses_taken
Try this :
select a.name, a.id, count(b.ID) as class_taken
from student a inner join takes b
on a.ID = b.ID
inner join course c
on b.course_id = c.course_id
where a.dept_name='Astronomy' and substring(a.name,1,1) = 'T'
group by a.name, a.id
having count(b.ID) < 17
I have two tables that I am trying to query from, Enrollment and Course. In the course Table, there is just one entry for each course, but in the Enrollment table, there is an entry for each student that is enrolled in any course, so there may be 30 entries for one course. My task is to find The course that has the most enrollments, and print out that course name, as well as the number of enrollments for that course. Here is my query so far
select c.CourseCode ,(SELECT count( * ) FROM Enrollment WHERE CourseCode = c.CourseCode) as test from Course c ;
this gives me the results:
CS227 - 29
CS228 - 34
CS309 - 31
CS311 - 25
, ect, which is good, but NOW, how do I print out only the class that has the most enrollments(in this case, CS228). I have tried using the max(), but I can't get anything to work.
Here is the table structure
create table Course(
CourseCode char(50),
CourseName char(50),
PreReq char (6));
create table Enrollment (
CourseCode char(6) NOT NULL,
SectionNo int NOT NULL,
StudentID char(9) NOT NULL references Student,
Grade char(4) NOT NULL,
primary key (CourseCode,StudentID),
foreign key (CourseCode, SectionNo) references Offering);
Just take the top 1 after ordering by the count.
That is:
Select Top 1 A.CourseCode, Count(*) From Course A inner join Enrollment B on (A.CourseCode=B.CourseCode)
Group By A.CourseCode
Order By Count(*) DESC
Also - use an inner join as I've shown here rather than a subquery. I do tend to like SubQueries and this one will work but it is just not appropriate in this kind of query!
Based on your comment, I think the blow query is what you want, although it is untested and I am not entirely sure on the HAVING clause being valid. From the documentation on MySQL's page, it seems it should work.
SELECT A.CourseCode, COUNT(*) AS count FROM Course A
JOIN Enrollment B ON A.CourseCode = B.CourseCode
GROUP BY A.CourseCode
HAVING count = MAX(count)
As for performance, I cannot tell if it's a good idea to run a MAX on an aggregate function (probably not).
Otherwise, just use the other query to return the top X and simply run through comparing to the previous number.
SELECT
c.CourseCode,
c.CourseName,
COUNT(*) AS cnt
FROM
Course AS c
INNER JOIN
Enrollment AS e
ON c.CourseCode = e.CourseCode
GROUP BY
c.CourseCode
HAVING
COUNT(*) =
( SELECT
COUNT(*) AS cnt
FROM
Enrollment AS e
GROUP BY
e.CourseCode
ORDER BY
cnt DESC
LIMIT 1
)