I have these tables
person table with following data
person_id description
1 first in the family
2 second in the family
3 third in the family
4 fourth int the family
5 fifth in the family
person_name table with following data
person_id first_name
1 Santiago
2 Lautaro
3 Lucas
4 Franco
5 Agustín
father table with following data
person_father_id description
1 father of Lautaro
2 father of Lucas
3 father of Franco
4 father of Agustín
children table with following data
person_child_id person_father_id
2 1
3 2
4 3
5 4
how to get full name (Agustín Franco Lucas Lautaro Santiago) of person where person_id 4 in select pl/sql query. the core table is person
You could use a hierarchical query with an inline view that joins the relevant tables together first. The query for the inline view could be:
select p.person_id, pn.first_name, c.person_father_id
from person p
join person_name pn on pn.person_id = p.person_id
left join children c on c.person_child_id = p.person_id;
PERSON_ID FIRST_NAME PERSON_FATHER_ID
---------- ---------- ----------------
2 Lautaro 1
3 Lucas 2
4 Franco 3
5 Agustín 4
1 Santiago
and with that as the basis for a hierarchical query:
select trim(sys_connect_by_path(first_name, ' ')) as whole_name
from (
select p.person_id, pn.first_name, c.person_father_id
from person p
join person_name pn on pn.person_id = p.person_id
left join children c on c.person_child_id = p.person_id
)
where connect_by_isleaf = 1
start with person_id = 4
connect by person_id = prior person_father_id;
WHOLE_NAME
--------------------------------------------------
Franco Lucas Lautaro Santiago
Or you could make the hierarchical query itself a further subquery, and then join to the names afterwards and aggregate that:
select listagg(pn.first_name, ' ') within group (order by lvl) as whole_name
from (
select person_id, level as lvl
from (
select p.person_id, c.person_father_id
from person p
left join children c on c.person_child_id = p.person_id
)
start with person_id = 4
connect by person_id = prior person_father_id
) t
join person_name pn on pn.person_id = t.person_id;
WHOLE_NAME
--------------------------------------------------
Franco Lucas Lautaro Santiago
Notice that with both of those you have to join the tables before you can filter based on your starting ID (start with rather than where, as this is hierarchical). That means that with larger tables it could end up doing more work than you really need or expect.
Or you could do the same thing with recursive subquery factoring (a recursive CTE) if you prefer, and you are using Oracle 11gR2 or higher:
with r (person_id, person_father_id, lvl) as (
select p.person_id, c.person_father_id, 1
from person p
left join children c on c.person_child_id = p.person_id
where p.person_id = 4
union all
select p.person_id, c.person_father_id, r.lvl + 1
from r
join person p on p.person_id = r.person_father_id
left join children c on c.person_child_id = p.person_id
)
select listagg(pn.first_name, ' ') within group (order by lvl) as whole_name
from r
join person_name pn on pn.person_id = r.person_id;
WHOLE_NAME
--------------------------------------------------
Franco Lucas Lautaro Santiago
which looks more complicated but can at least put the filter in the anchor member of the recursive CTE.
Read more about hierarchical queries and recursive subquery factoring.
Related
Firstly, I'd like to apologise for the ambiguous title (I promise to revise it once I'm actually aware of the problem I'm trying to solve!)
I have two tables, player and match, which look like the following:
player:
id name
-- ----
1 John
2 James
3 April
4 Jane
5 Katherine
match:
id winner loser
-- ------ -----
1 1 2
2 3 4
Records in the match table represent a match between two players, where the id column is generated by the database, and the values in the winner and loser columns reference the id column in the player table.
I want to run a query which spits out the following:
player.id player.name total_wins total_matches
--------- ----------- ---------- -------------
1 John 1 1
2 James 0 1
3 April 1 1
4 Jane 0 1
5 Katherine 0 0
I currently have a query which retrieves total_wins, but I'm not sure how to get the total_matches count on top of that.
select p.id, p.name, count(m.winner)
from player p left join match m on p.id = m.winner
group by p.id, p.name;
Thanks for your help!
Try
select p.id, p.name,
sum(case when m.winner = p.id then 1 end ) as total_wins,
count(m.id) as total_matches
from player p
left join match m on p.id in ( m.winner, m.loser )
group by p.id, p.name;
One method splits the match match table, so you have a single row for each win and loss. The rest is just a left join and aggregation:
select p.id, p.name, coalesce(sum(win), 0) as win, count(m.id) as total_matches
from player p left join
(select match, winner as id, 1 as win, 0 as loss from match
union all
select match, loser as id, 0 as win, 1 as loss from match
) m
on p.id = m.id
group by p.id, p.name;
excuse the bad title but I couldn't find a good way to express what I want in abstract terms.
Anyway I have 3 tables
tbl_product:
PID | productname
1 | product 1
2 | product 2
3 | product 3
4 | product 4
..
tbl_categories, motherCategory allows me to nest categories:
CID | categoriename | motherCategory
1 | electronics | NULL
2 | clothing | NULL
3 | Arduino | 1
4 | Casings, extra's | 3
..
tbl_productInCategory PID and CID are foreign keys to PID and CID in tbl_product and tbl_categories respectively. A product can have multiple categories assigned to it so PID can occur more than once in this table.
PID | CID
1 | 1
2 | 1
3 | 3
4 | 4
Now I have a query that returns all categories if I give the mothercategory.
What I want to do is show ONLY the categories that have products in them recursively.
for instance on the example data above I show all categories(motherCategory is null), I want it to return only electronics since there are no products category 2, clothing.
However the problem I am having is that I also want this to work recursively. Consider this tbl_productInCategory:
PID | CID
1 | 2
2 | 2
3 | 2
4 | 4
Now it should return both clothing and electronics even though there are no products in electronics, because there are products in the nested category arduino->Casings, extra's. If I show all categories with motherCategory, electronics it should also return arduino.
I can't figure out how to do this and any help or pointers are appreciated.
First you should select all categories where products exist. On the next steps select mother categories.
WITH CTE AS
(
SELECT tbl_categories.*
FROM
tbl_categories
JOIN tbl_productInCategory on tbl_productInCategory.CID = tbl_categories.CID
UNION ALL
SELECT tbl_categories.*
FROM tbl_categories
JOIN CTE on tbl_categories.CID = CTE.motherCategory
)
SELECT DISTINCT * FROM CTE
Use a recursive CTE to get a derived table of your category tree, and then INNER JOIN it to your ProductCategory table.
It's not something I've done before, but some googling indicates it is possible.
https://technet.microsoft.com/en-us/library/ms186243(v=sql.105).aspx
The semantics of the recursive execution is as follows:
Split the CTE expression into anchor and recursive members.
Run the anchor member(s) creating the first invocation or base result set (T0).
Run the recursive member(s) with Ti as an input and Ti+1 as an output.
Repeat step 3 until an empty set is returned.
Return the result set. This is a UNION ALL of T0 to Tn.
USE AdventureWorks2008R2;
GO
WITH DirectReports (ManagerID, EmployeeID, Title, DeptID, Level)
AS
(
-- Anchor member definition
SELECT e.ManagerID, e.EmployeeID, e.Title, edh.DepartmentID,
0 AS Level
FROM dbo.MyEmployees AS e
INNER JOIN HumanResources.EmployeeDepartmentHistory AS edh
ON e.EmployeeID = edh.BusinessEntityID AND edh.EndDate IS NULL
WHERE ManagerID IS NULL
UNION ALL
-- Recursive member definition
SELECT e.ManagerID, e.EmployeeID, e.Title, edh.DepartmentID,
Level + 1
FROM dbo.MyEmployees AS e
INNER JOIN HumanResources.EmployeeDepartmentHistory AS edh
ON e.EmployeeID = edh.BusinessEntityID AND edh.EndDate IS NULL
INNER JOIN DirectReports AS d
ON e.ManagerID = d.EmployeeID
)
-- Statement that executes the CTE
SELECT ManagerID, EmployeeID, Title, DeptID, Level
FROM DirectReports
INNER JOIN HumanResources.Department AS dp
ON DirectReports.DeptID = dp.DepartmentID
WHERE dp.GroupName = N'Sales and Marketing' OR Level = 0;
GO
OK, I'm an absolute beginner in SQL and I got one task to solve and I'm stuck, so I need help on your ideas how to get the required results.
I have 2 tables - first one is PARENTS, with the following data:
ID Name Age
1 John 25
2 Peter 28
3 Anny 30
4 Jack 32
and the second table is CHILDRENS, with the following data:
children_id parent_id name age
1 1 mary 5
2 1 Susanne 4
3 2 stephen 12
4 4 Kevin 7
What SQL command can be used to get following result:
id parent name number of childrens
1 John 2
2 Peter 1
3 Anny 0
4 Jack 1
Thanks in advance!
Try like this
select PARENTS.id,PARENTS.name,count(CHILDRENS.name)
from PARENTS left join CHILDRENS on PARENTS.id=CHILDRENS.parent_id
group by PARENTS.id,PARENTS.name
As mentioned by Jarlh in comments, Use LEFT OUTER JOIN + Group by
SELECT p.id,
p.name,
Count(parent_id) as number_of_childrens
FROM PARENTS p
LEFT OUTER JOIN CHILDRENS c
ON c.parent_id = p.ID
Group by p.id,
p.name
select p.id, p.name, count(p.id) as number_of_childrens
from parents p
left join childrens c
on p.parent_id = c.parent_id
group by p.id, p.name
SELECT A.ID
, A.name AS parent_name
, COUNT(B.children_id) AS number_of_children
FROM PARENTS AS A
LEFT JOIN CHILDRENS AS B
ON A.ID = B.parent_id
GROUP BY A.ID
, A.name
A left join, count and group by.
if you have parent table
create table parent (
pid int not null,
name varchar(255)
)
and a parent-child join table
create table parent_child (
pid int not null,
cid int not null,
foreign key (pid) references parent(pid),
foreign key (cid) references child(cid)
)
create table child(
cid int not null,
name varchar(255)
)
How can I find all parent's names where all their children have names in the following list ('dave','henry','myriam','jill').
I don't want to see a parent if they have a child with a different name, but if they have 1 or more children and all their children have names in the list I want to see the parent's name.
I did find this https://stackoverflow.com/a/304314/1916621 that will help me find a parent with children of the exactly those names, but I can't figure out how to the parents who only have children with names in a subset of that list.
Extra points if someone knows performance tradeoff for different approaches.
SELECT
p.pid,
p.name
FROM
parent p
WHERE NOT EXISTS (
SELECT *
FROM
parent_child pc
JOIN child c
ON pc.cid = c.cid
AND c.name NOT IN ('dave','henry','myriam','jill')
WHERE
p.pid = pc.pid
) AND EXISTS (
SELECT *
FROM
parent_child pc
JOIN child c
ON pc.cid = c.cid
AND c.name IN ('dave','henry','myriam','jill')
WHERE
p.pid = pc.pid
)
Another method... no sub-queries, but additional DISTINCT needed to eliminate duplication of parent records from joining to the parent_child table.
SELECT DISTINCT
p.pid,
p.name
FROM
parent p
JOIN parent_child pc_exists ON pc_exists.pid = p.pid
JOIN child c_exists
ON c_exists.cid = pc_exists.cid
AND c_exists.name IN ('dave','henry','myriam','jill')
LEFT JOIN parent_child pc_notExists ON pc_notExists.pid = p.pid
LEFT JOIN child c_notExists
ON c_notExists.cid = pc_notExists.cid
AND c_notExists.name NOT IN ('dave','henry','myriam','jill')
WHERE
c_notExists.cid IS NULL
Here is my moderate bet:
Sample tables:
Parent
PID NAME
1 dad john
2 mum sandy
3 dad frank
4 mum kate
5 mum jean
Child
CID NAME
11 dave
22 maryam
33 henry
44 maryam
16 jill
17 lina
23 jack
34 jill
55 dave
Parent_Child
PID CID
1 11
1 16
1 17
2 22
3 33
4 44
2 23
5 55
3 34
Query:
select p.pid, p.name,
group_concat(c.name) as children
from parent as p
inner join parent_child as pc
on p.pid = pc.pid
join child as c
on pc.cid = c.cid
where c.name
in ('dave','henry','maryam','jill')
group by p.pid
;
Results:
PID NAME CHILDREN
1 dad john dave,jill
2 mum sandy maryam
3 dad frank henry,jill
4 mum kate maryam
5 mum jean dave
SQLFIDDLE
Using REGEXP and GROUP_CONCAT
It is something much better than in or find_in_set for SQL. The change I did, I used the list as a comma delimitted string ;)
*But the issue here: that group_concat string order has to be found in the comman delimitted string.* Unless we make the REGEXP much efficient :)
Query:
select x.pid, x.name,
x.children from(
select p.pid, p.name,
group_concat(c.name) as children,
count(c.name) as counts
from parent as p
inner join parent_child as pc
on p.pid = pc.pid
join child as c
on pc.cid = c.cid
group by p.pid) as x
where 'dave,maryam,henry,jill'
REGEXP x.children
;
Results:
PID NAME CHILDREN
3 dad frank henry,jill
4 mum kate maryam
5 mum jean dave
*SQLFIDDLE
Currently I am performing a left join on two tables. The first table has an id and a persons name, the second table has an id, the id of a person from table 1, and then a timestamp (of the last flight they had).
People Flights
id | name id | person_id | time
------------ ---------------------------
1 Dave 1 1 1284762115
2 Becky 2 1 1284787352
3 2 1284772629
4 2 1286432934
5 1 1289239480
When I perform my left join, I get a list of people and their flight times, but what I would like is just the list of people with their last flight times.
So SELECT p.id, p.name, f.time FROM People p LEFT JOIN Flights f ON p.id = f.person_id
Returns
1 Dave 1284762115
1 Dave 1284787352
1 Dave 1289239480
2 Becky 1284772629
2 Becky 1286432934
I would like to see just:
1 Dave 1289239480
2 Becky 1286432934
So I need to return only the match with the highest f.id or the highest f.time
SELECT
p.id, p.name, MAX(f.time) AS LastFlight
FROM
People p
LEFT JOIN Flights f ON p.id = f.person_id
GROUP BY
p.id, p.name
SELECT p.id, p.name, f.time FROM People p LEFT JOIN
(select person_id, max(time) time from flights group by person_id) f
ON p.id = f.person_id
Try this:
;with LastFlightTimes as
(
select person_id, max(id) maxid
from Flights f
group by person_id
)
SELECT p.id, p.name, f.time FROM People p
LEFT JOIN LastFlightTimes lft ON p.id = lft.person_id
left join Flights f on f.id = lft.maxid