How do I display columns from two common table expression? - sql

I have problems displaying columns from two common table expression. I created the first table by querying the student names and their mid-term grades and the other table the student names and their final-term grades.
CREATE TABLE MidTerm AS (SELECT Name, Score
FROM GRADE
WHERE TYPE = ''MidTerm
)
CREATE TABLE FinalTerm AS (SELECT Name, Score
FROM GRADE
WHERE TYPE = 'Final'
)
Both of the created have the same number of columns and the same variables. Now I want to display the Name, Score "MidTerm" and Score "FinalTerm", how can I achieve this? I manage to use UNION at the expense of SELECT * only. If I specify
Midterm table:
Name : Score
A : 50
B : 60
Finalterm table:
Name : Score
A : 70
B : 80
I want to join the CTE tables by displaying
Final Intended Result:
Name : Score "MidTerm" : Score "FinalTerm"
A : 50 : 70
B : 60 : 80
it would say invalid column identifier. How do I solve this?

A simple join will handle this:
SELECT m.NAME AS "Name",
m.SCORE AS "Score MidTerm",
f.SCORE AS "Score FinalTerm"
FROM MIDTERM m
LEFT OUTER JOIN FINALTERM f
ON f.NAME = m.NAME
db<>fiddle here

If you have two tables for midterm and final score as per comment in gordon's answer then just do join and you will get your result like this:
Select m.name,
M.score as midterm_score,
F.score as final_score
From midterm_table m
Join final_table f
on (m.name = f.name);
Cheers!!

I think that you are just looking for conditional aggregation:
select
name,
max(case when score = 'MidTerm' then score end) MidTerm,
max(case when score = 'Final' then score end) Final
from grade
where score in ('MidTerm', 'Final')
group by name

I am baffled. Use conditional aggregation:
SELECT Name,
MAX(CASE WHEN Type = 'MidTerm' THEN Score END) as midterm_score,
MAX(CASE WHEN Type = 'Final' THEN Score END) as final_score,
FROM GRADE
GROUP BY Name;
CTEs do not help with this query at all.
You could also do this using a JOIN:
select m.name, m.score as midterm_score, f.score as final_score
from grade m join
grade f
on m.name = f.name and
m.type = 'midterm' and
f.type = 'final';
Note that this only shows names with both scores.

Add student's id in those tables and use it to join them and gather the columns that you need.
I dont believe that create this two tables is realy a good idea,
but, ok, I don't know the complexity of your calculations to get the score.
anyway, I would suggest to you consider the creation of an view for that instead of create those table.

Related

Joining tables and displaying columns with different conditions

I have 2 tables:
CTU contains columns 'course_id', 'user_id', and 'date'
NU contains columns 'name' and 'id'
user_id in table CTU has the same info as 'id' in table NU.
I want my output to display the following:
name | date where course_id = 1 | date where course_id = 2
I can get the first 2 columns to appear with a simple query, but I don't understand SQL JOIN's and subqueries very well so I'm having a hard time getting the last column to display.
One way to pivot out the date like that is to use a group by and an aggregate function. How about something like this:
SELECT
n.name
,MAX(CASE WHEN c.course_id = 1 THEN c.date END) AS course_1_date
,MAX(CASE WHEN c.course_id = 2 THEN c.date END) AS course_2_date
FROM CTU as c
JOIN NU as n
ON c.user_id = n.id
GROUP BY n.name

SQL query using NULL

I have two tables, student and school.
student
stid | stname | schid | status
school
schid | schname
Status can be many things for temporary students, but NULL for permanent students.
How do I list names of schools which has no temporary students?
Using Conditional Aggregate you can count the number of permanent student in each school.
If total count of a school is same as the conditional count of a school then the school does not have any temporary students.
Using JOIN
SELECT sc.schid,
sc.schname
FROM student s
JOIN school sc
ON s.schid = sc.schid
GROUP BY sc.schid,
sc.schname
HAVING( CASE WHEN status IS NULL THEN 1 END ) = Count(*)
Another way using EXISTS
SELECT sc.schid,
sc.schname
FROM school sc
WHERE EXISTS (SELECT 1
FROM student s
WHERE s.schid = sc.schid
HAVING( CASE WHEN status IS NULL THEN 1 END ) = Count(*))
You can use not exists to only select schools that do not have temporary students:
select * from school s
where not exists (
select 1 from student s2
where s2.schid = s.schid
and s2.status is not null
)
You can use a regular join.
SELECT DISTINCT c.schName
FROM Students s
INNER JOIN Schools c ON s.schid = c.schid
WHERE s.status IS NULL

SQL Select entries where none of the entities have a value in a particular column

I have a table of data which has students and their subject results in it. The students will appear multiple times, once for each subject they have a result for.
**tableID,studentID,lastName,firstName,subject,grade**
1,1a,Student1,Name1,English,A
2,1a,Student1,Name1,Maths,A
3,1a,Student1,Name1,Science,A
4,2a,Student2,Name2,English,A
5,2a,Student2,Name2,Maths,B
6,2a,Student2,Name2,Science,A
7,3a,Student3,Name3,English,A
8,3a,Student3,Name3,Maths,A
Using Microsoft Access SQL, how can I select only the students who have received an A for all of their subjects? E.g. In the above table, I only want to select all instances of Student1 and Student3, I don't want Student2 as they have not received all A's.
Get all students with grade A except students with any other grade
SELECT
studentID,lastName,firstName
FROM
(SELECT
studentID,lastName,firstName
FROM
result
WHERE
grade = 'A'
GROUP BY
studentID,lastName,firstName) GradeA
LEFT OUTER JOIN
(SELECT
studentID,lastName,firstName
FROM
result
WHERE
grade <> 'A'
GROUP BY
studentID,lastName,firstName) GradeOther
ON GradeA.studentId = GradeOther.StudentID AND GradeA.LAstName = GradeOther.LastName AND GradeA.FirstName = GradeOther.FirstName
WHERE
GradeOther.StudentID IS NULL
One way is using GROUP BY and HAVING:
select StudentId
from t
group by StudentId
having max(grade) = min(grade) and max(grade) = 'A';
I was able to get the results I want by using a sub-query:
SELECT studentID, lastName, firstName
FROM table
WHERE grade = "A"
AND studentID NOT IN (SELECT studentID FROM table WHERE grade <> "A" GROUP BY studentID)
GROUP BY studentID, lastName, firstName
This seems to exclude all students who received a result other than an A.

Get distinct result

I was asked this trick question:
Table: Student
ID NAME
1 JOHN
2 MARY
3 ROBERT
4 DENNIS
Table: Grade
ID GRADE
1 A
1 A
1 F
2 B
3 A
How do you write SQL query to return DISTINCT name of all students who has never received grade 'F' OR who has never taken a course (meaning, their ID not present in Grade table)?
Trick part is, you're not allowed to use OUTER JOIN, UNION or DISTINCT. Also, why this is a big deal?
Expected result is MARY, ROBERT, DENNIS (3 rows).
SELECT name FROM Student
WHERE
NOT EXISTS (SELECT * FROM Grade WHERE Grade.id = Student.id AND grade = 'F')
OR
NOT EXISTS (SELECT * FROM Grade WHERE Grade.id = Student.id);
You may use GROUP BY in order to fake a distinct.
SELECT name FROM student
WHERE (SELECT COUNT(*) FROM grade WHERE grade = 'F'
AND id = student.id) = 0
at least this is the shortest answer so far ...
Something like this could work, if you're allowed to use subqueries.
SELECT `NAME`
FROM Student
WHERE 'F' NOT IN
(SELECT GRADE FROM Grade WHERE ID = Student.ID)
Hmm, my homework sense is tingling... Not that my questions have never related to homework though...
You could use the GROUP BY and aggregate functions in order to fake a distinct.
You want to exclude everyone who has both taken a course and received a grade of 'F'. Something like this might work:
SELECT NAME
FROM Student
WHERE 0 = (SELECT COUNT(*)
FROM Student
LEFT JOIN Grade
USING (ID)
WHERE GRADE='F')
GROUP BY NAME
SELECT name
FROM grade G, student S
WHERE (S.id = G.id AND 'F' NOT IN (SELECT G1.grade
FROM grade G1
WHERE G1.id = G.id))
OR
S.id NOT IN (SELECT id
FROM grade)
GROUP BY name
A reason why they might not want you to use UNION, JOIN or DISTINCT is that some of those queries might be slow if you try to force an "optimized" solution.
I'm not too familiar with query optimization techniques but usually if you use some of those aggregators and JOINs, you might slow down your query rather than just letting the query optimizations run through and organize your SQL based on your table structure and contents.

Need help with SQL Query

Say I have 2 tables:
Person
- Id
- Name
PersonAttribute
- Id
- PersonId
- Name
- Value
Further, let's say that each person had 2 attributes (say, gender and age). A sample record would be like this:
Person->Id = 1
Person->Name = 'John Doe'
PersonAttribute->Id = 1
PersonAttribute->PersonId = 1
PersonAttribute->Name = 'Gender'
PersonAttribute->Value = 'Male'
PersonAttribute->Id = 2
PersonAttribute->PersonId = 1
PersonAttribute->Name = 'Age'
PersonAttribute->Value = '30'
Question: how do I query this such that I get a result like this:
'John Doe', 'Male', '30'
SELECT p.name, p1.Value, p2.Value
FROM Person p, PersonAttribute p1, PersonAttribute p2
WHERE p.Id = p1.PersonId AND p.Id = p2.PersonId
AND p1.Name = 'Gender' AND p2.Name = 'Age'
I think you need redesign your schema.
Why not?
Person
- Id
- Name
- Gender
- Birthday
...
SELECT p.Name, g.Value, a.Value
FROM Person p INNER JOIN PersonAttribute g ON p.Id = g.Id AND g.Name = "Gender" INNER JOIN PersonAttribute a ON p.Id = a.Id AND a.Name = "Age"
Storing Name Value pairs does give flexibility but is very cumbersome to query.
Take a look at http://www.simple-talk.com/community/blogs/philfactor/archive/2008/05/29/56525.aspx
Leaving the design aside, you can always PIVOT the result but you need to know how many attributes you are selecting out in advance.
There's no easy way to do this.
The concept of a pivot table (already mentioned by another answer) is basically what you are looking for, except that pivot tables require you to know the names of the columns you wish to use. Clearly this is a problem when you want to exploit the power of such a table design!
In my previous life, I just settled on X number of columns, like 20-30, and if they didn't exist, then the row set included a bunch of null values. No big deal.
select piv.name,
max(case piv.a_name when 'Gender' then piv.a_value else null end) as Gender,
max(case piv.a_name when 'Age' then piv.a_value else null end) as Age,
max(case piv.a_name when 'Hobby' then piv.a_value else null end) as Hobby
from
(select p.name as name, pa.name as a_name, pa.value as a_value
from person p, personattribute pa
where p.id = pa.personid) piv
group by piv.name
This would generate output like so:
name | gender | age | hobby
-----------+--------+-----+---------
Bob Swift | Male | | Reading
John Doe | Male | 30 |
(2 rows)
Which is pretty damned close to what you are looking for. I would leave the rest of it up to your application-layer.
I also highly recommend that you include the attribute NAME as part of the return value, to provide context to the VALUEs.
These types of so-called Entity-Attribute designs often end up having to rely on a combination of server-specific functions, stored procedures, and hard-coded queries.
You need to JOIN the two tables. Wikipedia provides a pretty good explanation of JOIN: http://en.wikipedia.org/wiki/Join_%28SQL%29
SELECT Name, g.Value, a.Value
FROM Person,
PersonAttribute g INNER JOIN ON g.Name = "Gender",
PersonAttribute a INNER JOIN ON a.Name = "Age"