SQL exercise using a self Left Outer Join - sql

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.

Related

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.

SQL where two fields are similar using "GROUP BY"

I have a database of names, some of the names haven't been insert in the correct fashion. SecondName has sometimes been entered as FirstName.
+-----------------+--------------+
| FirstName | SecondName |
+-----------------+--------------+
| Bob | Smith |
| Gary | Rose |
| John | Jones |
| Smith | Bob |
| Gary | Oberstein |
| Adam | Sorbet |
| Jones | John |
+-----------------+--------------+
I've tried different grouping queries
select `FirstName`
, `SecondName`
from `names`
where ( `FirstName`
, `SecondName` )
in ( select `FirstName`
, `SecondName`
from `names`
group
by `FirstName`
, `SecondName`
having count(*) > 1
)
But I can't get anything to produce
+-----------------+--------------+---------+
| FirstName | SecondName | Count |
+-----------------+--------------+---------+
| Bob | Smith | 2 |
| John | Jones | 2 |
+-----------------+--------------+---------+
There is a trick to do this, you need to normalize your names, a quick way to do this is if you alphabetize first name and last name then group on the result.
SELECT name_normalized, count(*) as c
FROM (
SELECT CASE WHEN FIRSTNAME < LASTNAME THEN FIRSTNAME||LASTNAME
ELSE LASTNAME|| FIRSTNAME END as name_normalized
FROM names
) X
GROUP BY name_normalized
Notes:
This is the simple case, you could add the normalized result as a column if you want to see the original values.
You may need other normalization -- it depends on what your rules are. For example UPPER() to ignore case and TRIM() to remove whitespace.
You can add or ignore other columns as is required for matching normalization -- Birthday, Middle Initial etc.
Oten time a hash on the normalized string is faster to work with than the string -- your data model might require one or the other.
If the COUNT() itself isn't important, you can easily separate duplicates with an INNER JOIN
SELECT n.FirstName, n.SecondName, n2.FirstName, n2.SecondName
FROM Names n
INNER JOIN Names n2 on n.FirstName = n2.SecondName and n.SecondName = n2.FirstName

Retrieve value from column depending on another column

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

MS-Access - How to add records up when performing a query

OK so this is quite complicated but I'm very new to Access so maybe this isn't so hard.
I have 3 tables in my database:
Clients - this contains a list of all the clients for the fund. Some of thse clients have more than one account in the fund. Structure:
ID | CliName
------------
1 | Joe Blogs Holdings
2 | John Doe Investments
etc...
ClientMap - this basically has a list of all the accounts and another field contains which client it belongs to (this ID of the client who owns the account).
ID | AccountName | ClientName | ClientID (refs to ID in Clients)
------------
1 | Joe Blogs Safe Fund | Joe Blogs Holdings | 1
2 | Joe Blogs Risk Fund | Joe Blogs Holdings | 1
3 | John Doe Fund | John Doe Investments | 2
etc...
Valuations - this is a list of the values of each account at any given date.
ID | ValuationDate | Valuation | AccName
------------
1 | 30/09/2012 | 1,469,524 | Joe Blogs Safe Fund
2 | 30/09/2012 | 1,767,134 | Joe Blogs Risk Fund
3 | 30/09/2012 | 239,561 | John Doe Fund
4 | 30/06/2012 | 1,367,167 | Joe Blogs Safe Fund
5 | 30/06/2012 | 1,637,121 | Joe Blogs Risk Fund
6 | 30/06/2012 | 219,241 | John Doe Fund
OK, so I want to run a query that list a chosen client from the clients table and returns the sum of the valuations for all their accounts. E.g. John Doe only has one accounts so the query result would look like this:
ClientName | ValuationDate | Valuation
----
John Doe Investments | 30/09/2012 | 239,561
John Doe Investments | 30/06/2012 | 219,241
BUT, the query should return something like this for Joe Blogs:
ClientName | ValuationDate | Valuation
----
Joe Blogs Holdings | 30/09/2012 | 3,236,658
Joe Blogs Holdings | 30/06/2012 | 3,004,288
So for each date it adds finds the total of all Joe Blogs' accounts to give an overall valuation. How do I do this?
Hope this is clear and those access experts out there are able to help me out.
Thanks
I have this so far but it does not sum the accounts for each date, it just lists them individually:
SELECT ClientMap.AccName, Clients.ClientName, Valuations.ValuationDate, Valuations.Valuation
FROM (Clients INNER JOIN ClientMap ON Clients.ID = ClientMap.ClientID) INNER JOIN Valuations ON ClientMap.AccName = Valuations.AccName
WHERE (((Clients.ClientName) Like [Which Client?] & "*"));
You should start by reading http://www.w3schools.com/sql/
When you are done you will know where and how to use aggregate functions [like sum() in this case]
Good luck in your learning process

UNIQUE - way to have unique rows in table?

I have problem with unique rows in db table, now it is posible to do that:
id | Name | LastName | City
-------------------------------------
1 | John | Moore | London
2 | John | Moore | London
when i use UNIQUE attribute in all columns i have errors inserting second Moore even it is different Name :/
how use UNIQUE (or maybe INDEX?) to do something like that in my table in db:
id | Name | LastName | City
-------------------------------------
1 | John | Moore | London
2 | Jake | Moore | London
3 | John | Keen | London
4 | John | Moore | London //but good error when inserting the same row
Sorry if question is easy, but i am beginner at sql, and have problems with find some good example with using a UNIQUE like a want:/
or maybe I must just before inserting a new row selecting a table from db and check if it exist?
Remove the unique index on the individual column and make it on both columns together, like this:
CREATE UNIQUE INDEX ixFullName ON yourTable (LastName, Name);