Retrieve value from column depending on another column - sql

From this table of football players, how would you select the players' names alongside their captains' names?
PlayerID | PlayerName | TeamCaptainID
=====================================
1 | Jay | 5
2 | John | 3
3 | James | 3
4 | Jack | 5
5 | Jeremy | 5
The result I'm after is:
Player | Captain
================
Jay | Jeremy
John | James
James | James
Jack | Jeremy
Jeremy | Jeremy

Applying inner join on the same table seems to be enough:
select t1.PlayerName as Player
, t2.PlayerName as Captain
from tbl t1
join tbl t2 on t1.TeamCaptainID = t2.PlayerID

To find the exact result you want, you have to use self-join, it is how this will work:
To achieve the desired outcome, we will use the same table twice and in order to do this we will use the alias of the table, a self join must have aliases.
To get the list of Players and their captain the following sql statement can be used :
SELECT a.PlayerName AS "Player",
b.TeamCaptainID AS "Captain"
FROM team a, team b
WHERE a.TeamCaptainID=b.PlayerName

Related

SQL exercise using a self Left Outer Join

This is the exercise:
How can you output a list of all members, including the individual who recommended them (if any)? Ensure that results are ordered by (surname, firstname).
The solution is below only I don't understand why it's 'ON recs.memid = mems.recommendedby' and not 'mems.memid = recs.recommendedby'. Why doesn't the latter work? I want to correct my thinking on how to use a Left Outer Join to itself.
CREATE TABLE members (
memid SERIAL PRIMARY KEY,
firstname VARCHAR(20),
surname VARCHAR(20),
recommendedby INTEGER References members(memid)
);
SELECT
mems.firstname AS memfname,
mems.surname AS memsname,
recs.firstname AS recfname,
recs.surname AS recsname
FROM
cd.members AS mems
LEFT OUTER JOIN cd.members AS recs
ON recs.memid = mems.recommendedby
ORDER BY memsname, memfname;
Consider this data:
MEMID | FIRSTNAME | SURNAME | RECOMMENDEDBY
------|-----------|---------|--------------
1 | John | Smith | null
2 | Karen | Green | 1
3 | Peter | Jones | 1
Here John recommended both Karen and Peter, but no one recommended John
'ON recs.memid = mems.recommendedby' (The one that "works")
You're getting a list of members and the ones that recommended them. Any member can only have been recommended by one member as per the table structure, so you'll get all the members just once. You're taking the recommendedby value and looking for it in the memid column in the "other table":
recommendedby --|--> memid of members that recommended them
MEMID | FIRSTNAME | SURNAME | RECOMMENDEDBY
------|-----------|---------|--------------
Karen | Green | John | Smith
Peter | Jones | John | Smith
John | Smith | null | null
The recommendedby column only has John (1), so when looking for the value 1, John comes up.
'ON mems.memid = recs.recommendedby' (The one that doesn't work)
You'll again get all the members. But here you're getting them as the ones doing the recommending, so to say. If they didn't recommend anyone, the paired record will be blank. This is because you're taking the memid value and looking to see if it matches the recommendedby column of the "other table". If a member recommended more than one, the record will appear multiple times:
memid --|--> recommendedby
MEMID | FIRSTNAME | SURNAME | RECOMMENDEDBY
------|-----------|---------|--------------
Karen | Green | null | null
Peter | Jones | null | null
John | Smith | Karen | Green
John | Smith | Peter | Jones
Karen and Peter didn't recommend anyone, but John recommended both the others.

Simple SQL Query to bring back null if no match found

EDIT
I've edited this question to make it a little more concise, if you see my edit history you will see my effort and 'what I've tried' but it was adding a lot of unnecessary noise and causing confusion so here is a summary of input and output:
People:
ID | FullName
--------------------
1 | Jimmy
2 | John
3 | Becky
PeopleJobRequirements:
ID | PersonId | Title
--------------------
1 | 1 | Some Requirement
2 | 1 | Another Requirement
3 | 2 | Some Requirement
4 | 3 | Another Requirement
Output:
FullName | RequirementTitle
---------------------------
Jimmy | Some Requirement
Jimmy | Another Requirement
John | Some Requirement
John | null
Becky | null
Becky | Another Requirement
Each person has 2 records, because that's how many distinct requirements there are in the table (distinct based on 'Title').
Assume there is no third table - the 'PeopleJobRequirements' is unique to each person (one person to many requirements), but there will be duplicate Titles in there (some people have the same job requirements).
Sincere apologies for any confusion caused by the recent updates.
CROSS JOIN to get equal record for each person and LEFT JOIN for matching records.
Following query should work in your scenario
select p.Id, p.FullName,r.Title
FROM People p
cross join (select distinct title from PeopleJobRequirements ) pj
left join PeopleJobRequirements r on p.id=r.personid and pj.Title=r.Title
order by fullname
Online Demo
Output
+----+----------+---------------------+
| Id | FullName | Title |
+----+----------+---------------------+
| 3 | Becky | Another Requirement |
+----+----------+---------------------+
| 3 | Becky | NULL |
+----+----------+---------------------+
| 1 | Jimmy | Some Requirement |
+----+----------+---------------------+
| 1 | Jimmy | Another Requirement |
+----+----------+---------------------+
| 2 | John | NULL |
+----+----------+---------------------+
| 2 | John | Some Requirement |
+----+----------+---------------------+
use left join, no need any subquery
select p.*,jr.*,jrr.*
from People p left join
PeopleJobRequirements jr on p.Id=jrPersonId
left join JobRoleRequirements jrr p.id=jrr.PersonId
according the explanation, People and PeopleJobRequirements tables have many to many relationship (n to n).
so first of all you'll need another table to relate these to table.
first do this and then a full join will make it right.

JOIN, aggregate and convert in postgres between two tables

Here are the two tables i have: [all columns in both tables are of type "text"], Table name and the column names are in bold fonts.
Names
--------------------------------
Name | DoB | Team |
--------------------------------
Harry | 3/12/85 | England
Kevin | 8/07/86 | England
James | 5/05/89 | England
Scores
------------------------
ScoreName | Score
------------------------
James-1 | 120
Harry-1 | 30
Harry-2 | 40
James-2 | 56
End result i need is a table that has the following
NameScores
---------------------------------------------
Name | DoB | Team | ScoreData
---------------------------------------------
Harry | 3/12/85 | England | "{"ScoreName":"Harry-1", "Score":"30"}, {"ScoreName":"Harry-2", "Score":"40"}"
Kevin | 8/07/86 | England | null
James | 5/05/89 | England | "{"ScoreName":"James-1", "Score":"120"}, {"ScoreName":"James-2", "Score":"56"}"
I need to do this using a single SQL command which i will use to create a materialized view.
I have gotten as far as realising that it will involve a combination of string_agg, JOIN and JSON, but haven't been able to crack it fully. Please help :)
I don't think the join is tricky. The complication is building the JSON object:
select n.name, n.dob, n.team,
json_agg(json_build_object('ScoreName', s.name,
'Score', s.score)) as ScoreData
from names n left join
scores s
ons.name like concat(s.name, '-', '%')
group by n.name, n.dob, n.team;
Note: json_build_object() was introduced in Postgres 9.4.
EDIT:
I think you can add a case statement to get the simple NULL:
(case when s.name is null then NULL
else json_agg(json_build_object('ScoreName', s.name,
'Score', s.score))
end) as ScoreData
Use json_agg() with row_to_json() to aggregate scores data into a json value:
select n.*, json_agg(row_to_json(s)) "ScoreData"
from "Names" n
left join "Scores" s
on n."Name" = regexp_replace(s."ScoreName", '(.*)-.*', '\1')
group by 1, 2, 3;
Name | DoB | Team | ScoreData
-------+---------+---------+---------------------------------------------------------------------------
Harry | 3/12/85 | England | [{"ScoreName":"Harry-1","Score":30}, {"ScoreName":"Harry-2","Score":40}]
James | 5/05/89 | England | [{"ScoreName":"James-1","Score":120}, {"ScoreName":"James-2","Score":56}]
Kevin | 8/07/86 | England | [null]
(3 rows)

How to establish join and self join on the same row

The situation is like this:
There is Teacher Performance and Student Performance entity, so teacher and student may attend same course have their score
Teacher can teach student and teacher
There is a Teaching Relation table, and yes both teacher and student can appear in attendance column, but only teacher can appear in teacher column.
The desired report table is list all teacher's performance, plus average performance of the students and teachers who are taught by the teacher.
In our system the id is numeric, I made it first name just for demo purpose.
Teacher Performance(tb_tp)
t_id | score
------------------
JOHN | 5
ASHLEY | 6
STEVEN |4.5
Student Performance(tb_sp)
s_id | score
------------------
SCOTT | 5
FRANK | 8
TIM | 7
Teaching Relation(tb_tr)
t_id (teacher) | a_id for attendance id | a_type attendance type
------------------------------------------------------------------
ASHLEY | JOHN | teacher
ASHLEY | FRANK | student
ASHLEY | TIM | student
JOHN | ASHLEY | teacher
JOHN | FRANK | student
Desired Report:
t_id | Score| avg_t AVG score from Teacher | avg_s AVG score from Student
--------------------------------------------------------------------------
ASHLEY | 6 | (5 from John) /1 | (8 from Frank + 7 from Tim)/2
--------------------------------------------------------------------------
JOHN | 5 | (6 from ASHLEY) /1 | (8 from Frank) / 1
How can we make this happen? I am thinking two joins together or union two queries, but either way cannot keep the result on the same row and be accurate
I think the technique you want is two outer joins with an aggregate. Here is my untested pass at it:
SELECT p.t_id, p.score, scores.avg_teacher_score, scores.avg_student_score
FROM teacher_performance p
JOIN (
SELECT t_id, AVG( t.score) AS avg_teacher_score, AVG( s.score ) AS avg_student_score
FROM teaching_relation tr
LEFT JOIN teacher_performance t ON t.t_id = tr.a_id AND tr.a_type='teacher'
LEFT JOIN student_performance s ON s.s_id = tr.a_id AND tr.a_type='student'
) scores
ON scores.t_id = p.t_id

SQL select / join performace (PHP, PDO)

What is the most efficient way to get data from two tables set up in the following way:
Table 1:
ID(PK) | Name | Age
--------------------------
1 | Jim | 44
2 | Jane | 35
3 | John | 22
Table 2
Name(PK) | Pet(PK)
-----------------
Jim | Cat
Jim | Dog
Jane | Fish
There is a constraint on "Name" with the FK in Table 2
Results
I want the age and all the pets for a specific person.
Name | Age | Pet
---------------------
Jim | 44 | Cat
Jim | 44 | Dog
As I see it these are my options:
1) Left join table 2 on Name and end up with redundant data in my resulting array for Name and Age (as above).
2) Use a function that turns the pets into a comma separated list.
3) Use 2 separate selects.
My question is relating to performance of the 3 options above. I don't need SQL (specifically, unless you want to suggest another method).
Thanks!
select
tb01.name, tb01.age, tb02.pet
from
table01 tb01
left join table02 tb02 on tb02.name = tb01.name