Joining tables and displaying columns with different conditions - sql

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

Related

How do I display columns from two common table expression?

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.

How to create a query to join three tables and make calculations in SQL?

I'm just at the beginning of my SQL studies and can't figure out how to resolve the next problem.
So, there are three tables:
! given tables
The task is: "Get number of pet type per owner"
Write a query to generate the result below:
! desired output
The best result I have for the moment:
SELECT owners.OWNER_NAME, COUNT(pets.OWNER_ID) AS pets
FROM owners
JOIN pets ON owners.ID = pets.OWNER_ID
JOIN pet_type ON pets.TYPE = pet_type.ID
GROUP BY owners.OWNER_NAME;
It returns first column with owner names and second column with the sum of particular owner pets.
Will appreciate any help.
You need conditional aggregation:
SELECT
o.OWNER_NAME,
SUM(CASE WHEN t.name = 'CAT' THEN 1 ELSE 0 END) CAT,
SUM(CASE WHEN t.name = 'DOG' THEN 1 ELSE 0 END) DOG,
SUM(CASE WHEN t.name = 'SNAKE' THEN 1 ELSE 0 END) SNAKE
FROM owners o
JOIN pets p ON o.ID = p.OWNER_ID
JOIN pet_type t ON p.TYPE = t.ID
GROUP BY o.OWNER_NAME;
I use name as the name of the column describing the type in table pet_type. Change it to the actual name of the column.
Check this. To Get number of pet type per owner, this is sufficient to join only Pets table with the owners table. A DISTINCT count of Pet.Type will give your desired output.
SELECT
owners.ID,
owners.OWNER_NAME,
COUNT(DISTINCT pets.TYPE) AS Num_Pet_Type
FROM owners
INNER JOIN pets ON owners.ID = pets.OWNER_ID
GROUP BY owners.ID,owners.OWNER_NAME;
If you wants number of Pet per type, use this below script-
SELECT
owners.ID,
owners.OWNER_NAME,
pets.TYPE,
COUNT(*) AS Num_Of_Pet
FROM owners
INNER JOIN pets ON owners.ID = pets.OWNER_ID
GROUP BY owners.ID,owners.OWNER_NAME,pets.TYPE;

Joining tables based on value

I'm having some hard time doing the join function on those two tables. I have simplified the example dataset as there are additional where-clauses involved for the first table however that doesn't seem to be a problem.
I would write the query for joining the two tables below:
select a.prod_code, a.prod_name, b.ref_value from Product_code a
left join Product_reference b on a.prod_code = b.pref_code
where a.prod_code <> 'CURTAIN' and b.ref_value = 'MAN'
The problem I'm facing is that I want to join tables kind of conditionally. I.e. if the ref_type value is 'MAN' in Product_reference table, I do want to join it, otherwise not.
For an example this query would not include "Chair" in the result as it does not have an ref_type 'MAN' available in the "Product_name". What I'd need though is still show it in the query result, just without joined value from the Product_reference table (given that value with ref_type 'MAN' does not exist for it), not leave it out altogether.
Meanwhile Product_name table record 'CURTAIN' should be left off (regardless if Product_reference ref_type 'MAN' exists or not)
Any recommendations?
Product_code
prod_code prod_name
A Table
B Chair
C Window
D Door
E Curtain
Product_reference
pref_code ref_type ref_value
A MAN x
A AUTO y
B AUTO z
C AUTO z1
C MAN x1
D AUTO zxc
E AUTO abc
E MAN cba
Move b.ref_value = 'MAN' to the join predicate:
SELECT a.prod_code, a.prod_name, b.ref_value
FROM Product_code a
LEFT JOIN Product_reference b ON a.prod_code = b.pref_code AND b.ref_value = 'MAN'
WHERE a.prod_code <> 'CURTAIN'
This will accomplish what you want, which is only left joining the data from table b where b.ref_value = 'MAN', instead of removing all other rows from the result set altogether.
Side note, thanks for including your query and sample data in your very well made question. We appreciate it.
you could use a inner join on the distinct product that have 'MAN'
select
a.prod_code
, a.prod_name
, b.ref_value
from Product_code a
inner join (
select distinct pref_code
from Product_reference
where ref_type = 'MAN') t2 on t2.pref_code = a.prod_code
and a.prod_code <> 'CURTAIN'

pad database out with NULL criteria

If I have the following sample table (order by ID)
ID Date Type
-- ---- ----
1 01/01/2000 A
2 22/04/1995 A
2 14/02/2001 B
Where you can immediate see that ID=1 does not have a Type=B, but ID=2 does. What I want to do, if fill in a line to show this:
ID Date Type
-- ---- ----
1 01/01/2000 A
1 NULL B
2 22/04/1995 A
2 14/02/2001 B
where there could potentially be 100's of different types, (so may need to end up inserting 100's rows per person if they lack 100's Types!)
Is there a general solution to do this?
Could I possibly outer join the table on itself and do it that way?
You can do this with a cross join to generate all the rows and a left join to get the actual data values:
select i.id, s.date, t.type
from (select distinct id from sample) i cross join
(select distinct type from sample) t left join
sample s
on s.id = i.id and
s.type = t.type;

Get latest record from second table left joined to first table

I have a candidate table say candidates having only id field and i left joined profiles table to it. Table profiles has 2 fields namely, candidate_id & name.
e.g. Table candidates:
id
----
1
2
and Table profiles:
candidate_id name
----------------------------
1 Foobar
1 Foobar2
2 Foobar3
i want the latest name of a candidate in a single query which is given below:
SELECT C.id, P.name
FROM candidates C
LEFT JOIN profiles P ON P.candidate_id = C.id
GROUP BY C.id
ORDER BY P.name;
But this query returns:
1 Foobar
2 Foobar3
...Instead of:
1 Foobar2
2 Foobar3
The problem is that your PROFILES table doesn't provide a reliable means of figuring out what the latest name value is. There are two options for the PROFILES table:
Add a datetime column IE: created_date
Define an auto_increment column
The first option is the best - it's explicit, meaning the use of the column is absolutely obvious, and handles backdated entries better.
ALTER TABLE PROFILES ADD COLUMN created_date DATETIME
If you want the value to default to the current date & time when inserting a record if no value is provided, tack the following on to the end:
DEFAULT CURRENT_TIMESTAMP
With that in place, you'd use the following to get your desired result:
SELECT c.id,
p.name
FROM CANDIDATES c
LEFT JOIN PROFILES p ON p.candidate_id = c.id
JOIN (SELECT x.candidate_id,
MAX(x.created_date) AS max_date
FROM PROFILES x
GROUP BY x.candidate_id) y ON y.candidate_id = p.candidate_id
AND y.max_date = p.created_date
GROUP BY c.id
ORDER BY p.name
Use a subquery:
SELECT C.id, (SELECT P.name FROM profiles P WHERE P.candidate_id = C.id ORDER BY P.name LIMIT 1);