PL/SQL -- Join Child Tables With Unrelated Rows - sql

I'm trying to perform a join in PL/SQL to get a list of parent
records with multiple child tables back as part of a single query.
I'm not an Oracle expert. If I wrote a query like this:
SELECT PEOPLE.PersonName, JOBS.JobName, CREDENTIALS.CredentialName
FROM PEOPLE
LEFT OUTER JOIN JOBS
ON PEOPLE.PersonName = JOBS.PersonName
LEFT OUTER JOIN CREDENTIALS
ON PEOPLE.PersonName = CREDENTIALS.PersonName
WHERE PEOPLE.PersonName = 'James'
I would get a table back that lists every combination of
job and credential like so:
RN PERSON JOB CREDENTIAL
1 James Developer MBA
2 James Developer PhD
3 James Developer MCAD
4 James QA MBA
5 James QA PhD
6 James QA MCAD
That's fine, and exactly how you expect a left outer
join to work. But what I need is for the JOB and
CREDENTIAL columns to only list each element once and
be unrelated to each other -- but still list James'
child records on, and only on, rows where PERSON is
James.
RN PERSON JOB CREDENTIAL
1 James Developer MBA
2 James QA PhD
3 James (null) MCAD
But I'm not sure how to write this join. (The
idea is that C# code will take the query results
and convert it into one parent PERSON object with
lists of references to two child JOB objects and
three child CREDENTIAL objects.
Outside this example, though, there's actually
a lot more than two child tables, so I can't
filter a result-set which is the product of all
the child-combinations; I need the columns to
be unrelated lists.
I'm willing to deal with hairy SQL to achieve
this, but it needs to be one query and not
multiple queries with the results combined on
the C# side.
Thanks to anyone who can help!

You need to enumerate each list and then use that for joining:
select p.PersonName, j.JobName, c.CredentialName
from Person p left outer join
(select j.*,
row_number() over (partition by PersonName order by jobname) as seqnum
from jobs
) j
on p.PersonName = j.PersonName full outer join
(select c.*,
row_number() over (partition by PersonName order by CredentialName) as seqnum
from Credentials
) c
on p.PersonName = c.PersonName and
j.seqnum = c.seqnum
WHERE p.PersonName = 'James'
This query is doing more work, because it assigns seqnum for all persons. You can put the WHERE clause in the subqueries to make it more efficient, if you are really only selecting one person at a time.

Related

Needing 2 different ID's from the same ID Table

I am pulling reports for my company and am needing to pull a specific report that I am having trouble with. We are using SQL Server 2012 and I am pulling the SQL reports.
What I need is to pull a simple report:
Group Name, List of Members in the group; Supervisor of the group.
However, the problem is that the supervisor as well as the members and the group name all come from one table in order to get the relevant information. Currently here is my SQL code below:
Use DATABASE
go
-- This is the select portion deciding the columns needed.
select
C.group_name
,C2.first_name
,C2.last_name
-- These are the tables that the query is pulling from.
FROM db..groups AS G
LEFT OUTER JOIN db..contact AS C
ON G.group_id=C.contact_id
INNER JOIN db..contact AS C2
ON G.member=C2.contact_id
go
This pulls the first portion:
The group name, then the first name of a member in that group, and then the last name of a member in that group.
However, I am having trouble getting the supervisor portion. This portion uses the table db.contact under the column supervisor_id as a foreign key. The supervisor_id uses the same unique id as the normal contact_id, but in the same table. Some contact_ids have supervisor_id's that are other contact_id's from the same table, hence the foreign key.
How can I make it so I can get the contact_id that is equal to the supervisor_id of the contact_id that is equal to the group_id?
Taking a quick stab at this while we wait for details
You know you need groups and I'm assuming you don't care about Groups that have no members. Thus Groups INNER JOINed to Contact. This generates your direct group membership. To get the supervisor, you then need to factor in the Supervisor on the specific Contact row.
You might not have a boss, or your boss might be yourself. It's always interesting to see how various HR systems record this. In my example, I'm assuming the head reports to no one instead of themselves.
SELECT
G.group_name
, C.first_name
, C.last_name
-- this may produce nulls depending on outer vs inner join below
, CS.first_name AS supervisor_first_name
, CS.last_name AS supervisor_last_name
FROM
dbo.Groups AS G
INNER JOIN
dbo.Contact AS C
ON C.contact_id = G.member
LEFT OUTER JOIN
dbo.Contact AS CS
ON CS.contact_id = C.supervisor_id;
Depending on how exactly you wanted that data reported, there are various tricks we could use to report that data. In particular, GROUPING SETS might come in handy.
SQLFiddle

Returning duplicated values only once from a join query

I'm trying to extract info from a table in my database based on a persons job. In one table i have all the clients info, in another table linked by ID_no their job title and the branches theyre associated with. the problem I'm having is when i join both tables I'm returning some duplicates because a person can be associated with more than one branch.
I would like to know how to return the duplicated values only once, because all I care about for the moment is the persons id number and what their job title is.
SELECT *
FROM dbo.employeeinfo AS ll
LEFT OUTER JOIN employeeJob AS lly
ON ll.id_no = lly.id_no
WHERE lly.job_category = 'cle'
I know Select Distinct will not work in this situation since the duplicated values return different branches.
Any help would be appreciated. Thanks
I'm using sql server 2008 by the way
*edit to show result i would like
------ ll. ll. lly. lly.
rec_ID --employeeID---Name-----JobTitle---Branch------
1 JX100 John cle london
2 JX100 John cle manchester
3 JX690 Matt 89899 london
4 JX760 Steve 12345 london
I would like the second record to not display because i'm not interested in the branch. i just need to know the employee id and his job title, but because of how the tables are structured it's returning JX100 twice because he's recorded as working in 2 different branches
You must use SELECT DISTINCT and specify you ONLY want person id number and job title.
I don't know exactly your fields name, but I think something like this could work.
SELECT DISTINCT ll.id_no AS person_id_number,
lly.job AS person_job
FROM dbo.employeeinfo AS ll LEFT OUTER JOIN
employeeJob AS lly ON ll.id_no = lly.id_no
WHERE lly.job_category = 'cle'

SQL query - Joining a many-to-many relationship, filtering/joining selectively

I find myself in a bit of an unworkable situation with a SQL query and I'm hoping that I'm missing something or might learn something new. The structure of the DB2 database I'm working with isn't exactly built for this sort of query, but I'm tasked with this...
Let's say we have Table People and Table Groups. Groups can contain multiple people, and one person can be part of multiple groups. Yeah, it's already messy. In any case, there are a couple of intermediary tables linking the two. The problem is that I need to start with a list of groups, get all of the people in those groups, and then get all of the groups with which the people are affiliated, which would be a superset of the initial group set. This would mean starting with groups, joining down to the people, and then going BACK and joining to the groups again. I need information from both tables in the result set, too, so that rules out a number of techniques.
I have to join this with a number of other tables for additional information and the query is getting enormous, cumbersome, and slow. I'm wondering if there's some way that I could start with People, join it to Groups, and then specify that if a person has one group that is in the supplied set of groups (which is done via a subquery), then ALL groups for that person should be returned. I don't know of a way to make this happen, but I'm thinking (hoping) that there's a relatively clean way to make this happen in SQL.
A quick and dirty example:
SELECT ...
FROM GROUPS g
JOIN LINKING_A a
ON g.GROUPID = a.GROUPID
AND GROUPID IN (subquery)
JOIN LINKING_B b
ON a.GROUPLIST = b.GROUPLIST
JOIN PEOPLE p
ON b.PERSONID = p.PERSONID
--This gets me all people affiliated with groups,
-- but now I need all groups affiliated with those people...
JOIN LINKING_B b2
ON p.PERSONID = b2.PERSONID
JOIN LINKING_A a2
ON b2.GROUPLIST = a.GROUPLIST
JOIN GROUPS g2
ON a2.GROUPID = g.GROUPID
And then I can return information from p and g2 in the result set. You can see where I'm having trouble. That's a lot of joining on some large tables, not to mention a number of other joins that are performed in this query as well. I need to be able to query by joining PEOPLE to GROUPS, then specify that if any person has an associated group that is in the subquery, it should return ALL groups affiliated with that entry in PEOPLE. I'm thinking that GROUP BY might be just the thing, but I haven't used that one enough to really know. So if Bill is part of group A, B, and C, and our subquery returns a set containing Group A, the result set should include Bill along with groups A, B, and C.
The following is a shorter way to get all the groups that people in the supplied group list are in. Does this help?
Select g.*
From Linking_B b
Join Linking_B b2
On b2.PersonId = b.PersonId
Join Group g
On g.GroupId = b2.GroupId
Where b.Groupid in (SubQuery)
I'm not clear why you have both Linking_A and Linking_B. Generally all you should need to represent a many-to-many relationship between two master tables is a single association table with GroupID and PersonId.
I often recommend using "common table expressions" [CTE's] in order to help you break a problem up into chunks that can be easier to understand. CTE's are specified using a WITH clause, which can contain several CTE's before starting the main SELECT query.
I'm going to assume that the list of groups you want to start with is specified by your subquery, so that will be the 1st CTE. The next one selects people who belong to those groups. The final part of the query then selects groups those people belong to, and returns the columns from both master tables.
WITH g1 as
(subquery)
, p1 as
(SELECT p.*
from g1
join Linking a1 on g1.groupID=a1.groupID
join People p on p.personID=a1.personID )
SELECT p1.*, g2.*
from p1
join Linking a2 on p2.personID=a2.personID
join Groups g2 on g2.groupID=a2.groupID
I think I'd build the list of people you want to pull records for first, then use that to query out all the groups for those people. This will work across any number of link tables with the appropriate joins added:
with persons_wanted as
(
--figure out which people are in a group you want to include
select p.person_key
from person p
join link l1
on p.person_key = l1.person_key
join groups g
on l1.group_key = g.group_key
where g.group name in ('GROUP_I_WANT_PEOPLE_FROM', 'THIS_ONE_TOO')
group by p.person_key --we only want each person_key once
)
--now pull all the groups for the list of people in at least one group we want
select p.name as person_name, g.name as group_name, ...
from person p
join link l1
on p.person_key = l1.person_key
join groups g
on l1.group_key = g.group_key
where p.person_key in (select person_key from persons_wanted);

SQL JOIN returning multiple rows when I only want one row

I am having a slow brain day...
The tables I am joining:
Policy_Office:
PolicyNumber OfficeCode
1 A
2 B
3 C
4 D
5 A
Office_Info:
OfficeCode AgentCode OfficeName
A 123 Acme
A 456 Acme
A 789 Acme
B 111 Ace
B 222 Ace
B 333 Ace
... ... ....
I want to perform a search to return all policies that are affiliated with an office name. For example, if I search for "Acme", I should get two policies: 1 & 5.
My current query looks like this:
SELECT
*
FROM
Policy_Office P
INNER JOIN Office_Info O ON P.OfficeCode = O.OfficeCode
WHERE
O.OfficeName = 'Acme'
But this query returns multiple rows, which I know is because there are multiple matches from the second table.
How do I write the query to only return two rows?
SELECT DISTINCT a.PolicyNumber
FROM Policy_Office a
INNER JOIN Office_Info b
ON a.OfficeCode = b.OfficeCode
WHERE b.officeName = 'Acme'
SQLFiddle Demo
To further gain more knowledge about joins, kindly visit the link below:
Visual Representation of SQL Joins
Simple join returns the Cartesian multiplication of the two sets and you have 2 A in the first table and 3 A in the second table and you probably get 6 results. If you want only the policy number then you should do a distinct on it.
(using MS-Sqlserver)
I know this thread is 10 years old, but I don't like distinct (in my head it means that the engine gathers all possible data, computes every selected row in each record into a hash and adds it to a tree ordered by that hash; I may be wrong, but it seems inefficient).
Instead, I use CTE and the function row_number(). The solution may very well be a much slower approach, but it's pretty, easy to maintain and I like it:
Given is a person and a telephone table tied together with a foreign key (in the telephone table). This construct means that a person can have more numbers, but I only want the first, so that each person only appears one time in the result set (I ought to be able concatenate multiple telephone numbers into one string (pivot, I think), but that's another issue).
; -- don't forget this one!
with telephonenumbers
as
(
select [id]
, [person_id]
, [number]
, row_number() over (partition by [person_id] order by [activestart] desc) as rowno
from [dbo].[telephone]
where ([activeuntil] is null or [activeuntil] > getdate()
)
select p.[id]
,p.[name]
,t.[number]
from [dbo].[person] p
left join telephonenumbers t on t.person_id = p.id
and t.rowno = 1
This does the trick (in fact the last line does), and the syntax is readable and easy to expand. The example is simple but when creating large scripts that joins tables left and right (literally), it is difficult to avoid that the result contains unwanted duplets - and difficult to identify which tables creates them. CTE works great for me.

Information from two rows from a query into 1 row

I have 3 database tables:
Workoutput which defines the work done by employees.
employeeinfo which defines relationships for each person. The three important fields are child, parent and relation. A person can be listed
a couple of times with different relationships i.e,
child parent relation
12 3 2
12 43 4
With the relation referring to whether the parent is mentor or manager
And finally a standard employee table which has comprehensive listings about the employee id, name, login etc.
I would like to have a results table which defines all the people above that one worker on a daily basis, but have it defined in one row
on a daily basis. At the moment I can only define them on separate days with the following query.
SELECT workoutput.Day, workoutput.Employee, employee.name
FROM workoutput INNER JOIN
employeeInfo ON workoutput.ID = [employeeinfo].son INNER JOIN
[employee] ON [employeeInfo].parent = [employee].id INNER JOIN
[employee] AS [employee1] ON workoutput.[Employee ID] = [employee1].id
This returns the relationships, but there will be two rows for each day, as the majority of people will have two people above them (Mentor and Manager).
29/03/2010 00:00:00 Employee1 Manager 3
29/03/2010 00:00:00 Employee1 Mentor 1
and I would like
29/03/2010 00:00:00 Employee1 Mentor 1 Manager3
I also have another table at my disposal, which define the relationships it has two rows, id and type of relation, with id referring to a relation id defined in the employeeinfo table.
Is that possible?
Thanks
Is there a maximum number of parents for a child (i.e. relations)? And is there one per relation?
If so, you can do this with a PIVOT to get from your current output to your desired output.
However, if there are a fixed number of roles, you could also include that in your joins from the outset:
SELECT wo.Day, wo.Employee, e.name, er1.name, er2.name, etc.
FROM workoutput AS wo
INNER JOIN [employee] AS e
ON workoutput.[Employee ID] = e.id
LEFT JOIN employeeInfo AS ei1
ON e.ID = ei1.child
AND ei1.relation = 1
LEFT JOIN [employee] AS er1
ON ei1.parent = er1.id
LEFT JOIN employeeInfo AS ei2
ON e.ID = ei2.child
AND ei2.relation = 2
LEFT JOIN [employee] AS er2
ON ei2.parent = er2.id
etc.
Not with standard SQL. The usual approach here would be to sort the table and then use it as input into a reporting tool or a custom program.
In SQL Server, it would be possible to write a stored procedure which did this. It depends on how badly you want it.
you can group on employee identifier and group_concat the parents.
http://explainextended.com/2010/06/21/group_concat-in-sql-server/
if your relation field is constant (i.e. only a set number of relationship types) you can write a CASE statement to transpose it like this...
SELECT workoutput.Day, workoutput.Employee,
CASE relation WHEN 2 THEN employee.name END AS relation2, CASE relation WHEN 4 THEN employee.name END AS relation4
FROM workoutput
INNER JOIN employeeInfo ON workoutput.ID = [employeeinfo].son
INNER JOIN [employee] ON [employeeInfo].parent = [employee].id
INNER JOIN [employee] AS [employee1] ON workoutput.[Employee ID] = [employee1].id
GROUP BY workoutput.Day, workoutput.Employee, employee.name
However, a rather glaring downside to this is that if you add new relations then you will start to get duplicate rows so this should only be used if you have rigorous version controlling in your SP's or similar
EDIT
Apologies, I missed off the GROUP BY clause :)