How to solve this unique SQL query? - sql

I have the following tables:
Student(Sid,Sname) Primary key: {sid}
Course(cid,cname,duration,fee) Primary key:{cid}
Enrolled(sid,cid) Foreighn key: {sid,cid}
Query: Find the maximum fees paid by each student where a student can
enroll in different courses.
My attempt:
SELECT ssid, max(fee) as MAX_FEES from (Select sid as ssid, C.cid asccid,
fee from Course C,Enrolled E where C.cid = E.cid) group by
rollup(ssid,ccid,fee)
However, this doesn't gives the desired output appropriately. How to output only the Highest fees paid by each student?

try
SELECT max(c.fee) from course c, student s, enrolled e where s.sid=e.sid and e.cid=c.cid group by e.sid;

You didn't say if you also needed to list the students who are not enrolled in any course, so I'll provide one more solution:
CREATE TABLE student (
sid NUMBER PRIMARY KEY,
sname VARCHAR2(40)
);
CREATE TABLE course (
cid NUMBER PRIMARY KEY,
cname VARCHAR2(40),
duration NUMBER,
fee NUMBER
);
CREATE TABLE enrolled (
sid NUMBER,
cid NUMBER,
PRIMARY KEY (sid, cid),
FOREIGN KEY (sid) REFERENCES student (sid),
FOREIGN KEY (cid) REFERENCES course (cid)
);
INSERT INTO student (sid, sname) VALUES (1, 'John');
INSERT INTO student (sid, sname) VALUES (2, 'Peter');
INSERT INTO student (sid, sname) VALUES (3, 'Jake');
INSERT INTO course (cid, cname, duration, fee) VALUES (1, 'Math', 1, 1000);
INSERT INTO course (cid, cname, duration, fee) VALUES (2, 'Physics', 1, 1500);
INSERT INTO enrolled (sid, cid) VALUES (1, 1); -- John taking Math
INSERT INTO enrolled (sid, cid) VALUES (1, 2); -- John taking Physics
-- Peter being lazy
INSERT INTO enrolled (sid, cid) VALUES (3, 1); -- Jake taking Math
COMMIT;
-- not taking lazy (not taking any courses) students under account
SELECT s.sid, MAX(c.fee)
FROM student s
JOIN enrolled e ON (e.sid = s.sid)
JOIN course c ON (e.cid = c.cid)
GROUP BY s.sid
;
-- all students
SELECT s.sid, NVL(MAX(c.fee), 0)
FROM student s
LEFT JOIN enrolled e ON (e.sid = s.sid)
LEFT JOIN course c ON (e.cid = c.cid)
GROUP BY s.sid
;

Related

Using where clause with not-equal condition after join

I am trying to use WHERE with not-equal condition after joining two tables but it does not work.
Example: I have a table with data on famous people and a separate table with their works. Some works can have several authors. So I want a table listing authors with their co-authors:
CREATE TABLE famous_people (id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
profession TEXT,
birth_year INTEGER);
INSERT INTO famous_people (name, profession, birth_year)
VALUES ("Landau", "physicist", 1908);
INSERT INTO famous_people (name, profession, birth_year)
VALUES ("Lifshitz", "physicist", 1908);
INSERT INTO famous_people (name, profession, birth_year)
VALUES ("Fisher", "statistician", 1908);
INSERT INTO famous_people (name, profession, birth_year)
VALUES ("Ginzburg", "physicist", 1916);
INSERT INTO famous_people (name, profession, birth_year)
VALUES ("A. Strugatsky", "writer", 1925);
INSERT INTO famous_people (name, profession, birth_year)
VALUES ("B. Strugatsky", "writer", 1933);
CREATE TABLE works (id INTEGER PRIMARY KEY AUTOINCREMENT,
person_id INTEGER,
work TEXT);
INSERT INTO works (person_id, work)
VALUES (1, "Theoretical Physics");
INSERT INTO works (person_id, work)
VALUES (2, "Theoretical Physics");
INSERT INTO works (person_id, work)
VALUES (1, "Theory of Superconductivity");
INSERT INTO works (person_id, work)
VALUES (4, "Theory of Superconductivity");
INSERT INTO works (person_id, work)
VALUES (3, "Fisher test");
INSERT INTO works (person_id, work)
VALUES (5, "Roadside Picnic");
INSERT INTO works (person_id, work)
VALUES (6, "Roadside Picnic");
INSERT INTO works (person_id, work)
VALUES (5, "Hard to Be a God");
INSERT INTO works (person_id, work)
VALUES (6, "Hard to Be a God");
/* Co-authors */
SELECT a.name AS author, b.name AS coauthor FROM works
JOIN famous_people a
ON works.person_id = a.id
JOIN famous_people b
ON works.person_id = b.id;
It is Ok, except each author also has themselves as their own co-author, so I am trying to filter it out by adding WHERE author <> coauthor as the last line. But what I get is a table with two columns: work and name. Same weird result with WHERE a.name <> b.name
Funny enough, WHERE author = coauthor works fine but this is not what I want.
Expected result: a table with 2 columns:
author co-author
Landau Lipshitz
A. Strugatsky B. Strugatsky
Fisher NULL
Find all works that have two authors (using inner join on same work but different authors) and find all works that have one author (using not exists). Then combine the results:
SELECT w1.work, p1.name AS author, p2.name AS coauthor
FROM works AS w1
JOIN works AS w2 ON w1.work = w2.work AND w1.person_id < w2.person_id
JOIN famous_people AS p1 ON w1.person_id = p1.id
JOIN famous_people AS p2 ON w2.person_id = p2.id
UNION ALL
SELECT w1.work, p1.name, null
FROM works AS w1
JOIN famous_people AS p1 ON w1.person_id = p1.id
WHERE NOT EXISTS (
SELECT 1
FROM works AS w2
WHERE w2.work = w1.work AND w2.person_id <> w1.person_id
)
Demo on DB<>Fiddle
Your query cannot work. Keep in mind that a join works on rows. So there is one works row with one person ID that you look at at a time in your where clause. Then you join the person to the works row and then you join the person to the works row. That is the same person twice of course, because one works row only refers to one person.
This shows another, minor, problem. You call this table works. I would consider "Theoretical Physics" a work. You do so too; you named the column work. But then, why is the same work twice in the works table? This must not be. A works table shall store works, i.e. one work per row. What you have is a work_author table actually, and a work is uniquely identified by its title. This kind of makes sense; a title may uniquely identify a work - as long as no other author happens to name their work "Theoretical Physics", too :-( And as long as there are no typos in the table either.
This would be a better model:
person (person_id, name, birth_year, ...)
work (work_id, title, year, ...)
work_author (work_id, person_id)
If you have a typo in a title in this model, there is one row where you correct it and the data stays intact.
Now you want to get the authors of a work. This is easily done with aggregation:
select w.*, group_concat(p.name) as authors
from work_author wa
join person p on p.person_id = wa.person_id
join work w on w.work_id = wa.work_id
group by w.work_id
order by w.work_id;
You forgot to tell us your DBMS. As you are using double quotes where it must be single quotes according to the SQL standard, and your DBMS doesn't complain, this may be MySQL. (You should still always use single quotes for string literals.) For MySQL the string aggregation function is GROUP_CONCAT, so guessing MySQL, I used that in my query. Other DBMS use STRING_AGG, LISTAGG or something else.
If you just want to show up to two authors per work, you can take the minimum and maximum name (and compare the two in order not to show the same author twice):
select
w.*,
min(p.name) as author1,
case when min(p.name) <> max(p.name) then max(p.name) end as author2
from ...
UPDATE
In the comments you say that for every author you want to know all authors who worked with them. For this you need to join authors to authors based on their works. Still assuming MySQL:
select p1.name, group_concat(distinct p2.name) as others
from work_author wa1
join work_author wa2 on wa2.work_id = wa1.work_id
and wa2.person_id <> wa1.person_id
join person p1 on p1.person_id = wa1.person_id
join person p2 on p2.person_id = wa2.person_id
group by p1.name
order by p1.name;
Or not aggregated:
select distinct p1.name as person1, p2.name as person2
from work_author wa1
join work_author wa2 on wa2.work_id = wa1.work_id
and wa2.person_id <> wa1.person_id
join person p1 on p1.person_id = wa1.person_id
join person p2 on p2.person_id = wa2.person_id
order by p1.name, p2.name;
I changed the model as proposed by Thorsten Kettner and solved the task of matching authors with their co-authors as follows:
CREATE TABLE famous_people (id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
profession TEXT,
birth_year INTEGER);
INSERT INTO famous_people (name, profession, birth_year)
VALUES ("Landau", "physicist", 1908);
INSERT INTO famous_people (name, profession, birth_year)
VALUES ("Lifshitz", "physicist", 1908);
INSERT INTO famous_people (name, profession, birth_year)
VALUES ("Fisher", "statistician", 1908);
INSERT INTO famous_people (name, profession, birth_year)
VALUES ("Ginzburg", "physicist", 1916);
INSERT INTO famous_people (name, profession, birth_year)
VALUES ("A. Strugatsky", "writer", 1925);
INSERT INTO famous_people (name, profession, birth_year)
VALUES ("B. Strugatsky", "writer", 1933);
CREATE TABLE works (id INTEGER PRIMARY KEY AUTOINCREMENT,
work TEXT,
subject TEXT);
INSERT INTO works (work, subject)
VALUES ("Theoretical Physics", "physics");
INSERT INTO works (work, subject)
VALUES ("Theory of Superconductivity", "physics");
INSERT INTO works (work, subject)
VALUES ("Fisher test", "statistics");
INSERT INTO works (work, subject)
VALUES ("Roadside Picnic", "scifi");
INSERT INTO works (work, subject)
VALUES ("Hard to Be a God", "scifi");
CREATE TABLE author_works (id INTEGER PRIMARY KEY AUTOINCREMENT,
work_id INTEGER,
author_id INTEGER);
INSERT INTO author_works (work_id, author_id) VALUES (1, 1);
INSERT INTO author_works (work_id, author_id) VALUES (1, 2);
INSERT INTO author_works (work_id, author_id) VALUES (2, 1);
INSERT INTO author_works (work_id, author_id) VALUES (2, 4);
INSERT INTO author_works (work_id, author_id) VALUES (3, 3);
INSERT INTO author_works (work_id, author_id) VALUES (4, 5);
INSERT INTO author_works (work_id, author_id) VALUES (4, 6);
INSERT INTO author_works (work_id, author_id) VALUES (5, 5);
INSERT INTO author_works (work_id, author_id) VALUES (5, 6);
/* List of authors and their works */
SELECT famous_people.name, works.work FROM author_works
JOIN famous_people
ON author_works.author_id = famous_people.id
JOIN works
ON works.id = author_works.work_id;
/* Authors and co-authors ids*/
SELECT DISTINCT a.name, b.name
FROM author_works aw1
JOIN author_works aw2
ON aw1.work_id = aw2.work_id
JOIN famous_people a
ON aw1.author_id = a.id
JOIN famous_people b
ON aw2.author_id = b.id
WHERE aw1.author_id <> aw2.author_id;

How to join tables together via Ids using SQLite?

I am having trouble joining parts of tables. I want first and last names of the people and whatever their interest is to be joined together. I get this error message: "[1] [SQLITE_ERROR] SQL error or missing database (ambiguous column name: pi.PersonID)"
CREATE TABLE people (
PersonID INTEGER PRIMARY KEY AUTOINCREMENT,
FirstName VARCHAR(100),
LastName VARCHAR(100)
);
INSERT INTO people (FirstName, LastName)
VALUES ('Walter', 'White'),
('Jesse', 'Pinkman'),
('Saul', 'Goodman');
SELECT * FROM people;
CREATE TABLE interests (
InterestID INTEGER PRIMARY KEY AUTOINCREMENT,
Interest VARCHAR(100)
);
INSERT INTO interests (Interest)
values ('Swimming'),
('Basketball'),
('Running');
SELECT * FROM interests;
CREATE TABLE persons_interests (
PersonID INTEGER,
InterestID INTEGER,
PRIMARY KEY (PersonID, InterestID),
FOREIGN KEY (PersonID) REFERENCES people,
FOREIGN KEY (InterestID) REFERENCES interests
);
DROP TABLE persons_interests;
INSERT INTO persons_interests (PersonID, InterestID)
VALUES (1, 3),
(2, 2),
(3, 3);
SELECT * FROM persons_interests;
SELECT FirstName, LastName, Interest FROM people p, interests i
JOIN persons_interests pi on p.PersonID = pi.PersonID
JOIN persons_interests pi on i.Interest = pi.InterestID;
Don't mix implicit an explicit joins! You seem to want:
select p.firstname, p.lastname, i.interest
from people p
inner join persons_interests pi on pi.personid = p.personid
inner join interests i on i.interestid = pi.interestid;
Here, each table appears just once in the from clause, with the relevant join conditions.

Figure out which employee has done more training

I have 2 tables, employees and classes_taken. I am trying to see which employees has taken more than 2 class. However, I am getting this error ERROR: aggregate functions are not allowed in WHERE Position: 87.
CREATE TABLE employees (
id integer primary key,
name text
);
CREATE TABLE classes_taken (
employee integer,
class text,
foreign key (employee) references employees(id)
);
INSERT INTO employees (id, name) VALUES
(1, 'bob'), (2, 'sam'), (3, 'mike');
INSERT INTO classes_taken (employee, class) VALUES
(1, 'swimming'), (1, 'dancing'), (2, 'swimming'), (2, 'tennis'), (3, 'golf'), (3, 'dancing');
My select statement.
select e.id, e.name
FROM employees e
JOIN classes_taken c
ON e.id = c.employee
WHERE count(c.class) > 2
GROUP BY c.class;
SQLFiddle: http://sqlfiddle.com/#!15/5296cb/4
You need to keep the count filter in a HAVING clause, because it is on an aggregated value.
select e.id, e.name
FROM employees e
INNER JOIN classes_taken c ON e.id = c.employee
GROUP BY e.id, e.name
HAVING count(c.class) > 2

How to write SQL Subquery queries

I have a database which has 5 table
COURSES (CourseID, CourseName, Credit) // CourseID is primary key
TEACHERS (SSN, Name) // SSN is primary key
OFFER (CourseID, SSN) // CourseID and SSN are foreign key
STUDENTS (StudentID, Name, Department) // StudentID is primary key
ENROLL (StudentID, CourseID, Semester)
And i want to find the names of the students who enroll to courses that are enrolled by student whose name is “Jack Lion”.
I tried the below statements but i can't figure out how to solve.
SELECT STUDENTS.Name from STUDENTS
WHERE STUDENTS.StudentID IN (SELECT ENROLL.StudentID from ENROLL
-- I don't know what to write here
)
I just got this far can u help me or at least give me some examples that would help me to solve this. I use Microsoft sql server by the way.
Nest your subqueries
SELECT STUDENTS.Name from STUDENTS
WHERE STUDENTS.StudentID IN (SELECT ENROLL.StudentID
from ENROLL
WHERE CourseID IN (SELECT CourseID
FROM ENROLL
WHERE StudentID IN (SELECT StudentID
FROM STUDENTS
WHERE Name = 'Jack Lion'
)
)
)

SQL Find all courses who have more students enrolled then the allowed enroll_limit for the course

I'm currently taking a database class and I am stuck on a homework problem due tonight
Find the courses given in the ‘Sloan’ building which have enrolled more students than their enrollment limit. Return the courseno, enroll_limit, and the actual enrollment for those courses.
I'm stuck trying to count how many students are in each course.
CREATE TABLE Course (
courseno VARCHAR(7),
credits INTEGER NOT NULL,
enroll_limit INTEGER,
classroom VARCHAR(10),
PRIMARY KEY(courseNo), );
CREATE TABLE Student (
sID CHAR(8),
sName VARCHAR(30),
major VARCHAR(10),
trackcode VARCHAR(10),
PRIMARY KEY(sID),
FOREIGN KEY (major,trackcode) REFERENCES Tracks(major,trackcode) );
CREATE TABLE Enroll (
courseno VARCHAR(7),
sID CHAR(8),
grade FLOAT NOT NULL,
PRIMARY KEY (courseNo, sID),
FOREIGN KEY (courseNo) REFERENCES Course(courseNo),
FOREIGN KEY (sID) REFERENCES Student(sID) );
My current very broken attempt is
SELECT sloancourse.courseno
FROM course sloancourse
WHERE
sloancourse.classroom = 'Sloan'
and sloancourse.courseno IN (
SELECT c.courseno
FROM student s, enroll e, course c
WHERE
c.courseno = e.courseno
and s.sid = e.sid
and sloancourse.courseno = c.courseno
)
;
Find the courses given in the ‘Sloan’ building which have enrolled more students than their enrollment limit. Return the courseno, enroll_limit, and the actual enrollment for those courses.
You would typically join courses and enrollment (using stanard join syntax, with the on keyword), group by course, and finally filter on courses whose student count with a having clause.
select
c.courseno,
c.enroll_limit,
count(*) actual_enrollment
from course c
inner join enroll e on e.courseno= c.courseno
where c.classroom = 'Sloan'
group by c.courseno, c.enroll_limit
having count(*) > c.enroll_limit
Note that you don't need to bring in the student table to get the desired results.
You could try something like this:
SELECT
Course.courseno,
Course.enroll_limit,
Couse.classroom
FROM Course, Enroll
WHERE Course.courseno = Enroll.courseno
AND Course.classroom = 'Sloan'
GROUP BY Course.courseno
HAVING count(*) > Course.enroll_limit
Postgres docs have an example on these aggregate functions: https://www.postgresql.org/docs/current/tutorial-agg.html
Basically, when you group by one or multiple properties, the resulting table will result in a unique row for the property/combination of properties. In this example, you would have one row for each of the courseno unique values (as they are grouped by this value).