joining a table on 2 fields - sql

I want to pull a person and their supervisor names from a table. The persons table has the supervisor_id and the person_id. The names table has name_id and a Full Name field. If I join Person ON either supervisor_id or person_id, how do I get the other to display as well?

You need to join twice, one for each relationship you have:
SELECT
-- Persons' columns
P.*,
-- Superviser name columns
SN.*,
-- Person name columns
PN.*
FROM
persons AS P
LEFT JOIN names AS SN ON P.supervisor_id = SN.name_id
LEFT JOIN names AS PN ON P.person_id = PN.name_id
Or you can join with an OR clause, but you won't be able to know which record did you join with unless you check with a CASE.
SELECT
-- Persons' columns
P.*,
-- name columns
N.*,
IsSupervisor = CASE WHEN P.supervisor_id = N.name_id THEN 'Yes' ELSE 'No' END
FROM
persons AS P
LEFT JOIN names AS N ON
P.supervisor_id = N.name_id OR
P.person_id = N.name_id
This last approach will display 2 rows as it will match either one or the other on different occasions, not both with the same persons row (as the first example).

A (self)join is what you need:
select p.*, supervisor=ps.name
from Person p join person ps on p.supervisor_id=ps.id

Related

How to work in case in join condition

How to find city when ContactID is provided and condition is if ContactID is coming as 123 then it will look whether it is P or C, If P then it will go to Person table and returns City(USA) as output and If C then it will go to Company table and gives City(AUS) as output.
NB: all tables contain thousands of record and City value comes from run time.
Unless you're dynamically generating the query (i.e. using some language other than SQL to execute it) then you need to join on both tables anyway. If you're joining on both tables then there's no need for a CASE statement:
select *
from contacts co
left outer join person p
on co.contactid = p.contactid
and co.person_company = 'P'
left outer join company c
on co.contactid = c.contactid
and co.person_company = 'C'
You'll start noting an issue here, for every column from PERSON and COMPANY you're going to have to add some business logic to work out which table you want the information from. This can get very tiresome
select co.contactid
, case when p.id is not null then p.name else c.name end as name
from contacts co
left outer join person p
on co.contactid = p.contactid
and co.person_company = 'P'
left outer join company c
on co.contactid = c.contactid
and co.person_company = 'C'
Your PERSON and COMPANY tables seem to have exactly the same information in them. If this is true in your actual data model then there's no need to split them up. You make the determination as to whether each entity is a person or a company in your CONTACTS table.
Creating additional tables to store data in this manner is only really helpful if you need to store additional data. Even then, I'd still put the data that means the same thing for a person or a companny (i.e. name or address) in a single table.
If there's a 1-2-1 relationship between CONTACTID and PID and CONTACTID and CID, which is what your sample data implies, then you have a number of additional IDs, which have no value.
Lastly, if you're not restricting that only companies can go in the COMPANY table and individuals in the PERSON table. You need the PERSON_COMPANY column to exist in both PERSON and COMPANY, though as a fixed string. It would be more normal to set up this data model as something like the following:
create table contacts (
id integer not null
, contact_type char(1) not null
, name varchar2(4000) not null
, city varchar2(3)
, constraint pk_contacts primary key (id)
, constraints uk_contacts unique (id, contact_type)
);
create table people (
id integer not null
, contact_type char(1) not null
, some_extra_info varchar2(4000)
, constraint pk_people primary key (id)
, constraint fk_people_contacts
foreign key (id, contact_type)
references contacts (id, contact_type)
, constraint chk_people_type check (contact_type = 'P')
);
etc.
you can LEFT JOIN all 3 tables and the using a CASE statement select the one that you need based on the P or C value
SELECT
CASE c.[Person/Company]
WHEN 'P' THEN p.NAME
WHEN 'C' THEN a.Name
END AS Name
FROM Contact c
LEFT JOIN Person p on p.ContactId = c.ContactId
LEFT JOIN Company a on a.ContachId = c.ContactId
Ben's answer is almost right. You might want to check that the first join has no match before doing the second one:
select c.*, coalesce(p.name, c.name) as p.name
from contacts c left outer join
person p
on c.contactid = p.contactid and
c.person_company = 'P' left join
company co
on c.contactid = co.contactid and
c.person_company = 'C' and
p.contactid is null;
This may not be important in your case. But in the event that the second join matches multiple rows and the first matches a single row, you might not want the additional rows in the output.

SQL JOIN on one field or the other

Trying to order a family by father's name or, if there is no father, then the mother's name where the names are in a separate "person" table, something like:
SELECT DISTINCT family.myid FROM family
JOIN person
ON family.father_id = person.myid OR
family.mother_id = person.myid
ORDER BY person.surname,
person.given_name;
In this version, the families without fathers end up unsorted at the bottom. Would like families without fathers to appear in the order by the mother's name. Sqlite SQL will suffice.
Basically, you need a separate join for the fathers and the mothers:
select f.*
from family f left join
person d
on f.father_id = d.myid left join
person m
on f.mother_id = m.myid
order by (case when d.myid is null then m.surname else d.surname end),
(case when d.myid is null then m.given_name else d.given_name end);
Because a value could be missing, this should be a left join.
COALESCE should work
ORDER BY COALESCE(NULLIF(b.surname, ''), c.surname),
COALESCE(NULLIF(b.given_name, ''), c.given_name)

Selecting from table with categories of people

I created a database in ms sql , in the database I have three category of persons namely staff, customers, suppliers whom I stored in different tables create serial unique id for each.
Now these persons id are stored under person_id and a column names person type which stores whether its a staff, custimer or supplier in the transaction table, The problem lies in selecting the records from the transaction table like this pseudo code
Select t.*,s.na as staff,sp.name as supplier, c.name as customer
From Trans t
left join Staff s on s.id = t.pid
left join Suppliers sp on sp.id = t.pid
left join Customers c on c.id = t.pid
This returns one row, instead of at least 3 or more, How do I solve this problem
My trans table
person_id Person_type Trans_id
1 staff 1
1 customer 2
2 customer 3
3 suppler 4
1 staff 5
Expected output
person_name Trans_id
james 1
mark 2
dan 3
jude 4
james 5
Staff, Customers, and suppliers are stored in their different tables
That's what the Join does, combine data from multiple tables into one result row. If you want to "keep the rows", not combine them, you can use UNION
(
Select t.* From Trans t
left join Staff s on s.id = t.pid
)
UNION
(
Select t.* From Trans t
left join Suppliers sp on sp.id = t.pid
)
UNION
(
Select t.* From Trans t
left join Customers c on c.id = t.pid
)
This will get you the multiple rows you want BUT still not sure you have defined it right. I see you are only taking columns from Trans, so you're not getting any data from the other tables. And you're doing left outer joins so the other tables won't affect the selection. So I think it's just that same as selecting from just Trans.
If what you want is data from Trans where there is corresponding entry in the other tables, then do the UNION, but also change the outer joins to inner.

Query to select related data from two tables in which one table has no related fields in third table

I have three tables in my Oracle db:
Peoples:
IdPerson PK
Name
Surname
Earnings:
IdEarning PK
IdPerson
EarningValue
Awards:
IdAward PK
IdPerson FK
AwardDescription
So one person can have many earnings, one earning or can have no any earnings. Also one person can have many awards, one award, or no any award.
I have to select Surname and AwardDescription but only for people who have any earnings, because it is possible to have some award but, also don't have any earning!
My problem is to make a correct group by statement. I use query posted below and I am selecting surname of person with a description of award, but it is duplicating for each row in Earnings for this person.
SELECT AwardDescription, Surname
FROM Awards
INNER JOIN People ON People.IdPerson= Awards.IdPerson
INNER JOIN Earnings ON Earnings .IdPerson= People.IdPerson;
How to group it and avoid duplicating rows for each earning of person?
One person can be in many rows, but with different awards.
You could add DISTINCT to your query:
SELECT DISTINCT AwardDescription, Surname
FROM Awards
INNER JOIN People ON People.IdPerson= Awards.IdPerson
INNER JOIN Earnings ON Earnings .IdPerson= People.IdPerson;
Or another option is to use EXISTS:
SELECT AwardDescription, Surname
FROM Awards
INNER JOIN People P ON P.IdPerson= Awards.IdPerson
WHERE EXISTS (
SELECT 1
FROM Earnings E
WHERE P.IdPerson = E.IdPerson);
Do a left outer join among the tables like
SELECT p.Surname, a.AwardDescription, e.EarningValue
FROM People p
LEFT JOIN Awards a ON p.IdPerson= a.IdPerson
LEFT JOIN Earnings e ON e.IdPerson= p.IdPerson
WHERE a.AwardDescription IS NOT NULL
OR e.EarningValue IS NOT NULL;

select distinct out of distinct

I have two table one has employees goals and the other has list of employees. i have to match one to another. Seems easy to do. but in the employee table employees can be entered more than once with more than one way of spelling their names. How can I pick only one name for each ID, it really doesn't matter which one I pick.
this is the code i used:
select distinct (etar.EmplKey ), emp.EmplFullName
FROM EmployeeTarget etar
inner join DimEmployee emp on emp.emplkey = etar.emplkey
inner join dimbranch br on br.BranchId = etar.BranchId
where etar.BranchId = 8
this is the results i get:
EmplKey EmplFullName
100260 Ida Patton
101488 Don Sheppard
101488 Donald Sheppard
101489 Teresa Coverdale
103121 Harjinder Aujla
How can I have that Don Sheppard guy listed only once?
The easiest way is to do aggreagtion:
select etar.EmplKey, min(emp.EmplFullName)
FROM EmployeeTarget etar
inner join DimEmployee emp on emp.emplkey = etar.emplkey
inner join dimbranch br on br.BranchId = etar.BranchId
where etar.BranchId = 8
group by etar.EmplKey