inner join and partial matches - sql

I'm not sure if the title's correct, so here goes.
The following script returns the correct result: currently Roy Brown teaches Math 101. Roy Brown's TeacherID is 1225, but then they later added the prefix 001- for other purposes.
What I would like to include in the result is some identification that Roy Brown did have another course at one point in time, since Math 101 Old has 1225. It doesn't even have to show how many other courses he had; just something that will let me know that there is more than one row in #coursesCsv. But the result should remain to 2 rows.
What I don't want is to display an extra row for Roy Brown, which is why I'm not doing the commented inner join (ie. right(t.teacherid,4) = right(c.teacherid,4)).
There are no relationships between these two tables since the data in #coursesCSV comes from a csv file.
IF OBJECT_ID('tempdb..#teacher') IS NOT NULL DROP TABLE #teacher
IF OBJECT_ID('tempdb..#coursesCsv') IS NOT NULL DROP TABLE #coursesCsv
create table #teacher
(
TeacherID varchar(10),
FullName varchar(30)
)
insert into #teacher select '001-1225', 'Roy Brown'
insert into #teacher select '001-1230', 'Woody Boyd'
create table #coursesCsv
(
CourseName varchar(30),
TeacherID varchar(10)
)
insert into #coursesCsv select 'Math 101', '001-1225'
insert into #coursesCsv select 'Math 101 Old', '002-1225'
insert into #coursesCsv select 'History 101', '001-1230'
select t.teacherid, c.coursename from
#teacher t inner join #coursesCsv c
on t.teacherid = c.teacherid
--on right(t.teacherid,4) = right(c.teacherid,4)

Do a "Group Count" of courses for the same right(teacherid,4) within a Derived Table (or a Common Table Expression = WITH) and join to it:
select t.teacherid, c.coursename, c.coursecnt
from teacher t
inner join
(
select
teacherid,
coursename,
count(*)
over (partition by right(teacherid,4)) as coursecnt
from coursesCsv
) as c
on t.teacherid = c.teacherid

Related

Simple SQL query from table

For my CIS class, I have SQL project, and this is the first time I'm working with SQL, doing okay but I can't do one of the queries. Any help would be greatly appreciated.
This is what I tried:
Select cno, cname
from customer
where
)
)
)
Just use two IN clauses, one for 'Financial Accounting', one for 'Cost Accounting'.
Select cno, cname
from customer
where cno in
(
select cno
from salesorder
where ono in
(
select ono
from orderline
where bno in (select bno from book where bname = 'Financial Accounting')
)
)
and cno in
(
select cno
from salesorder
where ono in
(
select ono
from orderline
where bno in (select bno from book where bname = 'Cost Accounting')
)
);
Rob M's solution is more elegant, whereas above query should be more along the lines what you've learned so far.
You aren't exactly clear on whether the customer needs to have ordered both books in one order or across multiple orders. I'll assume the latter, based on the data provided.
Your answer returns customers who ordered either (or both) of those books. You say you want customers who order both books. Also, it's hard to keep your code straight when nesting several subqueries. Most people prefer joins.
This query should work for you.
select cno,cname from customer where cno in (
select o.cno
from salesorder o
inner join orderline ol on o.ono=ol.ono
inner join book b on b.bno=ol.bno
where b.bname in ('Financial Accounting', 'Cost Accounting')
group by o.cno
having count(distinct b.bno) = 2 -- Require both separate books, not 2 of one book or the other
)
Here is a complete example in SQL Server:
declare #book table (bno int, bname varchar(50))
insert #book (bno,bname) values
(10501,'Forensic Accounting')
,(10704,'Financial Accounting')
,(10933,'Cost Accounting')
declare #orderline table (ono int, bno int)
insert #orderline (ono,bno) VALUES
(1020,10501)
,(1020,10502)
,(1020,10503)
,(1020,10504)
,(1021,10605)
,(1022,10605)
,(1022,10704)
,(1023,10879)
,(1023,10988)
,(1024,10502)
,(1024,10988)
,(1026,10933)
,(1027,10933)
,(1028,10933)
,(1028,10965)
,(1029,10933)
,(1029,10965)
,(1029,10988)
,(1030,10965)
declare #salesorder table (ono int, cno int)
insert #salesorder (ono,cno) VALUES
(1020,23511)
,(1021,23513)
,(1022,23513)
,(1023,23512)
,(1024,23511)
,(1025,23511)
,(1026,23511)
,(1027,23512)
,(1028,23512)
,(1029,23513)
,(1030,23511)
declare #customer table (cno int, cname varchar(20))
insert #customer (cno,cname) values
(23511,'a')
,(23512,'b')
,(23513,'c')
,(23514,'d')
,(23515,'e')
,(23516,'f')
,(23517,'g')
,(23518,'h')
select cno,cname from #customer where cno in (
select o.cno
from #salesorder o
inner join #orderline ol on o.ono=ol.ono
inner join #book b on b.bno=ol.bno
where b.bname in ('Financial Accounting', 'Cost Accounting')
group by o.cno
having count(distinct b.bno) = 2 -- Require both separate books, not 2 of one book or the other
)

Inner join an inner join with another inner join

I'm wondering if it is possible to inner join an inner join with another inner join.
I have a database of 3 tables:
people
athletes
coaches
Every athlete or coach must exist in the people table, but there are some people who are neither coaches nor athletes.
What I am trying to do is find a list of people who are active (meaning play or coach) in at least 3 different sports. The definition of active is they are either coaches, athletes or both a coach and an athlete for that sport.
The person table would consist of (id, name, height)
the athlete table would be (id, sport)
the coaching table would be (id, sport)
I have created 3 inner joins which tell me who is both a coach and and an athlete, who is just a coach and who is just an athlete.
This is done via inner joins.
For example,
1) who is both a coach and an athlete
select
person.id,
person.name,
coach.sport as 'Coaches and plays this sport'
from coach
inner join athlete
on coach.id = athlete.id
and coach.sport = athlete.sport
inner join person
on athlete.id = person.id
That brings up a list of everyone who both coaches and plays the same sport.
2) To find out who only coaches sports, I have used inner joins as below:
select
person.id,
person.name,
coach.sport as 'Coaches this sport'
from coach
inner join person
on coach.id = person.id
3) Then to find out who only plays sports, I've got the same as 2) but just tweaked the words
select
person.id,
person.name,
athlete.sport as 'Plays this sport'
from athlete
inner join person
on athlete.id = person.id
The end result is now I've got:
1) persons who both play and coach the same sport
2) persons who coach a sport
3) persons who play a sport
What I would like to know is how to find a list of people who play or coach at least 3 different sports? I can't figure it out because if someone plays and coaches a sport like hockey in table 1, then I don't want to count them in table 2 and 3.
I tried using these 3 inner joins to make a massive join table so that I could pick the distinct values but it is not working.
Is there an easier way to go about this without making sub-sub-queries?
What I would like to know is how to find a list of people who play /
coach at least 3 different sports? I can't figure it out because if
someone plays and coaches a sport like hockey in table 1, then I don't
want to count them in table 2 and 3.
you can do something like this
select p.id,min(p.name) name
from
person p inner join
(
select id,sport from athlete
union
select id,sport from coach
)
ca
on ca.id=p.id
group by p.id
having count(ca.sport)>2
CREATE TABLE #person (Id INT, Name VARCHAR(50));
CREATE TABLE #athlete (Id INT, Sport VARCHAR(50));
CREATE TABLE #coach (Id INT, Sport VARCHAR(50));
INSERT INTO #person (Id, Name) VALUES(1, 'Bob');
INSERT INTO #person (Id, Name) VALUES(2, 'Carol');
INSERT INTO #person (Id, Name) VALUES(2, 'Sam');
INSERT INTO #athlete (Id, Sport) VALUES(1, 'Golf');
INSERT INTO #athlete (Id, Sport) VALUES(1, 'Football');
INSERT INTO #coach (Id, Sport) VALUES(1, 'Tennis');
INSERT INTO #athlete (Id, Sport) VALUES(2, 'Tennis');
INSERT INTO #coach (Id, Sport) VALUES(2, 'Tennis');
INSERT INTO #athlete (Id, Sport) VALUES(2, 'Swimming');
-- so Bob has 3 sports, Carol has only 2 (she both coaches and plays Tennis)
SELECT p.Id, p.Name
FROM
(
SELECT Id, Sport
FROM #athlete
UNION -- this has an implicit "distinct"
SELECT Id, Sport
FROM #coach
) a
INNER JOIN #person p ON a.Id = p.Id
GROUP BY p.Id, p.Name
HAVING COUNT(*) >= 3
-- returns 1, Bob
I have created a SQL with some test data - should work in your case:
Connecting the two results in the subselect with UNION:
UNION will return just non-duplicate values. So every sport will be just counted once.
Finally just grouping the resultset by person.Person_id and person.name.
Due to the HAVING clause, just persons with 3 or more sports will be returned-
CREATE TABLE person
(
Person_id int
,name varchar(50)
,height int
)
CREATE TABLE coach
(
id int
,sport varchar(50)
)
CREATE TABLE athlete
(
id int
,sport varchar(50)
)
INSERT INTO person VALUES
(1,'John', 130),
(2,'Jack', 150),
(3,'William', 170),
(4,'Averel', 190),
(5,'Lucky Luke', 180),
(6,'Jolly Jumper', 250),
(7,'Rantanplan ', 90)
INSERT INTO coach VALUES
(1,'Football'),
(1,'Hockey'),
(1,'Skiing'),
(2,'Tennis'),
(2,'Curling'),
(4,'Tennis'),
(5,'Volleyball')
INSERT INTO athlete VALUES
(1,'Football'),
(1,'Hockey'),
(2,'Tennis'),
(2,'Volleyball'),
(2,'Hockey'),
(4,'Tennis'),
(5,'Volleyball'),
(3,'Tennis'),
(6,'Volleyball'),
(6,'Tennis'),
(6,'Hockey'),
(6,'Football'),
(6,'Cricket')
SELECT person.Person_id
,person.name
FROM person
INNER JOIN (
SELECT id
,sport
FROM athlete
UNION
SELECT id
,sport
FROM coach
) sports
ON sports.id = person.Person_id
GROUP BY person.Person_id
,person.name
HAVING COUNT(*) >= 3
ORDER BY Person_id
The coaches & athletes, ie people who are coaches or athletes, are relevant to your answer. That is union (rows in one or another), not (inner) join rows in one and another). (Although outer join involves a union, so there is a complicated way to use it here.) But there's no point in getting that by unioning only-coaches, only-athletes & coach-athletes.
Idiomatic is to group & count the union of Athletes & Coaches.
select id
from (select * from Athletes union select * from Coaches) as u
group by id
having COUNT(*) >= 3
Alternatively, you want ids of people who coach or play a 1st sport and coach or play a 2nd sport and coach or play a 3rd sport where the sports are all different.
with u as (select * from Athletes union select * from Coaches)
select u1.id
from u u1
join u u2 on u1.id = u2.id
join u u3 on u2.id = u3.id
where u1.sport <> u2.sport and u2.sport <> u3.sport and u1.sport <> u3.sport
If you wanted names you would join that with People.
Is there any rule of thumb to construct SQL query from a human-readable description?](https://stackoverflow.com/a/33952141/3404097)

SQL statement to replace TSQL

Please see the DDL below:
CREATE Table Person (PersonID int)
INSERT INTO Person (1)
INSERT INTO Person (2)
INSERT INTO Person (3)
INSERT INTO Person (4)
INSERT INTO Person (5)
CREATE TABLE PersonCrime (PersonID in,CrimeID int)
INSERT INTO PersonCrime (1,1)
INSERT INTO PersonCrime (2,1)
INSERT INTO PersonCrime (3,2)
INSERT INTO PersonCrime (9,3)
INSERT INTO PersonCrime (4,3)
I want to return all the crimes from PersonCrime where all the persons are in the person table. For example, crime 1 is returned because persons 1 and 2 are in the person table. Crime 2 is also returned because person 3 is in the person table. Crime 3 is not returned because person 9 is not in the person table even though person 4 is.
I have done this using TSQL i.e. looping around all the crimes and checking all persons. However, this takes far to long. I am trying to do it with SQL.
You can do this with GROUP BY and HAVING:
SELECT CrimeID
FROM PersonCrime PC
LEFT JOIN Person P
ON PC.PersonID = P.PersonID
GROUP BY CrimeID
HAVING MIN(CASE WHEN P.PersonID IS NULL THEN 1 ELSE 2 END) <> 1
Demo: SQL Fiddle
With a table variable for clarity:
DECLARE #IgnoreCrime TABLE (CrimeID int);
INSERT INTO #IgnoreCrime SELECT CrimeID FROM PersonCrime WHERE PersonID NOT IN (SELECT PersonID FROM Person)
SELECT DISTINCT(CrimeID) FROM PersonCrime WHERE CrimeID NOT IN (SELECT CrimeID FROM #IgnoreCrime)
without:
SELECT DISTINCT(CrimeID)
FROM #PersonCrime
WHERE CrimeID NOT IN
(SELECT CrimeID
FROM #PersonCrime
WHERE PersonID NOT IN (SELECT PersonID FROM #Person))
Defined another way - "give me all CrimeIDs where there is not a PersoinID that is not in the Person table":
SELECT DISTINCT CrimeID
FROM PersonCrime pc
WHERE NOT EXISTS
(
SELECT 1 FROM PersonCrime
WHERE CrimeID = pc.CrimeID
AND PersonID NOT IN (SELECT PersonID FROM PERSON)
)
Use Exists Operator to find the personID present in Person try this.
SELECT DISTINCT CrimeID
FROM PersonCrime PC
WHERE EXISTS (SELECT 1
FROM Person P
WHERE p.PersonID = pc.PersonID)
GROUP BY CrimeID
HAVING Count(CrimeID) = (SELECT Count(1)
FROM PersonCrime PC1
WHERE PC.CrimeID = PC1.CrimeID)
SQLFIDDLE DEMO

SQL Simple SELECT Query

create table Person(
SSN INT,
Name VARCHAR(20),
primary key(SSN)
);
create table Car(
PlateNr INT,
Model VARCHAR(20),
primary key(PlateNr)
);
create table CarOwner(
SSN INT,
PlateNr INT,
primary key(SSN, PlateNR)
foreign key(SSN) references Person (SSN),
foreign key(PlateNr) references Car (PlateNr)
);
Insert into Person(SSN, Name) VALUES ('123456789','Max');
Insert into Person(SSN, Name) VALUES ('123456787','John');
Insert into Person(SSN, Name) VALUES ('123456788','Tom');
Insert into Car(PlateNr, Model) VALUES ('123ABC','Volvo');
Insert into Car(PlateNr, Model) VALUES ('321CBA','Toyota');
Insert into Car(PlateNr, Model) VALUES ('333AAA','Honda');
Insert into CarOwner(SSN, PlateNr) VALUES ('123456789','123ABC');
Insert into CarOwner(SSN, PlateNr) VALUES ('123456787','333AAA');
The problem I'm having is the SELECTE query I wanna make. I wan't to be able to SELECT everything from the Person and wan't the include the PlateNr of the car he's the owner of, an example:
PERSON
---------------------------------
SSN NAME Car
123456789 Max 123ABC
123456787 John 3338AAA
123456788 Tom
----------------------------------
So, I want to be able to show everything from the Person table and display the content of CarOwner aswell if the person is in fact a CarOwner. What I have so far is: "SELECT * from Person, CarOwner WHERE Person.SSN = CarOwner.SSN;". But this obviously results in only showing the person(s) that are CarOwners.
Hope I explained me well enough, Thanks.
Try this:
SELECT p.*, c.*
FROM Person p
LEFT OUTER JOIN CarOwner co
ON p.SSN = co.SSN
LEFT OUTER JOIN Car c
ON co.PlateNr = c.PlateNr
Show SQLFiddle
P.S. I've changed the type of your primary key PlateNr (in varchar and not in int)
select ssn, name, car
from Person p
LEFT OUTER JOIN CarOwner co
ON p.SSN = co.SSN
LEFT OUTER JOIN Car c
ON co.PlateNr = c.PlateNr

SQL - Find duplicates with equivalencies

I'm having trouble wrapping my mind around developing this SQL query. Given the following two tables:
ACADEMIC_HISTORY ( STUDENT_ID, TERM, COURSE_ID, COURSE_GRADE )
COURSE_EQUIVALENCIES ( COURSE_ID, COURSE_ID_EQUIVALENT )
What would be the best way to detect if students have taken the same (or an equivalent) course in the past with a passing grade (C or better)?
Example
Student #1 took the course ABC001 and received a grade of C. Ten years later, the course was renamed ABC011 and the appropriate entry was made in COURSE_EQUIVALENCIES. The student retook the course under this new name and received a grade of B. How can I construct a SQL query that will detect the duplicate courses and only count the first passing grade?
(The actual case is significantly more complicated, but this should get me started.)
Thanks in advance.
EDIT:
It's not even necessary to keep or discard any information. A query that simply shows classes with duplicates will be sufficient.
you could use something like:
SELECT
STUDENT_ID
,MIN (COURSE_GRADE)
FROM (
SELECT * FROM
ACADEMIC_HISTORY
WHERE COURSE_ID =1
UNION
SELECT
h.STUDENT_ID
,h2.COURSE_ID
,h2.COURSE_GRADE
FROM
ACADEMIC_HISTORY AS h
LEFT OUTER JOIN COURSE_EQUIVELANCIES as e
ON e.COURSE_ID = h.COURSE_ID
LEFT OUTER JOIN ACADEMIC_HISTORY as h2
ON h.STUDENT_ID = h2.STUDENT_ID
AND h2.COURSE_ID = e.COURSE_ID_EQUIVELANT
WHERE
h.COURSE_ID =1
) AS t
WHERE STUDENT_ID =1
GROUP BY STUDENT_ID
http://sqlfiddle.com/#!3/d608f/20
Sorry posted with a bug.. it preferred the score of the actual course requested over any equivalencies - fixed now
this only looks for one level of equivalencies.. but maybe you want to enforce that and have that part of the data entry process.. review all possible equivalencies and enter the valid ones
EDIT: for first pass of qualifying course (using numbered terms..)
SELECT TOP 1
STUDENT_ID
,MIN (COURSE_GRADE)
FROM (
SELECT * FROM
ACADEMIC_HISTORY
WHERE COURSE_ID =1
UNION
SELECT
h.STUDENT_ID
,h2.COURSE_ID
,h2.TERM
,h2.COURSE_GRADE
FROM
ACADEMIC_HISTORY AS h
LEFT OUTER JOIN COURSE_EQUIVELANCIES as e
ON e.COURSE_ID = h.COURSE_ID
LEFT OUTER JOIN ACADEMIC_HISTORY as h2
ON h.STUDENT_ID = h2.STUDENT_ID
AND h2.COURSE_ID = e.COURSE_ID_EQUIVELANT
WHERE
h.COURSE_ID =1
) AS t
WHERE STUDENT_ID =1
GROUP BY STUDENT_ID, TERM
ORDER BY TERM ASC
http://sqlfiddle.com/#!3/fdded/6
(note TOP is a t-sql command for MySQL you need LIMIT)
The data (in LOWERCASE)
DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp;
SET search_path='tmp';
CREATE TABLE academic_history
( student_id INTEGER NOT NULL
, course_id CHAR(6)
, course_grade CHAR(1)
, PRIMARY KEY(student_id,course_id)
);
INSERT INTO academic_history ( student_id,course_id,course_grade) VALUES
(1, 'ABC001' , 'C' )
, (1, 'ABC011' , 'B' )
, (2, 'ABC011' , 'A' )
;
CREATE TABLE course_equivalencies
( course_id CHAR(6)
, course_id_equivalent CHAR(6)
);
INSERT INTO course_equivalencies(course_id,course_id_equivalent) VALUES
( 'ABC011' , 'ABC001' )
;
The query:
-- EXPLAIN ANALYZE
WITH canon AS (
SELECT ah.student_id AS student_id
, ah.course_id AS course_id
, COALESCE (eq.course_id_equivalent,ah.course_id) AS course_id_equivalent
FROM academic_history ah
LEFT JOIN course_equivalencies eq ON eq.course_id = ah.course_id
)
SELECT h.student_id
, c.course_id_equivalent
, MIN(h.course_grade) AS the_grade
FROM academic_history h
JOIN canon c ON c.student_id = h.student_id AND c.course_id = h.course_id
GROUP BY h.student_id, c.course_id_equivalent
ORDER BY h.student_id, c.course_id_equivalent
;
The output:
NOTICE: drop cascades to 2 other objects
DETAIL: drop cascades to table tmp.academic_history
drop cascades to table tmp.course_equivalencies
DROP SCHEMA
CREATE SCHEMA
SET
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "academic_history_pkey" for table "academic_history"
CREATE TABLE
INSERT 0 3
CREATE TABLE
INSERT 0 1
student_id | course_id_equivalent | the_grade
------------+----------------------+-----------
1 | ABC001 | B
2 | ABC001 | A
(2 rows)