How to print child, father, mother from these two tables? - sql

Below are the tables:
relation table
cid
pid
101
202
566
322
875
345
people table
id
name
gender
101
Riya
F
566
Aman
M
202
Rakesh
M
875
lucky
M
202
Reena
F
322
Raina
F
345
Rohit
M
322
Mohit
M
345
Meena
F
output
Child
Mother
Father
Riya
Reena
Rakesh
Aman
Raina
Mohit
Lucky
Rohit
Meena
I tried this:
SELECT mother,
father
FROM (
SELECT id,
name,
sum(
CASE
WHEN gender = 'F' THEN 1
ELSE 0) AS mother,
sum (
CASE
WHEN gender = 'M' THEN 1
ELSE 0) AS father
FROM people
INNER JOIN relation
ON people. id = relation.p_id
GROUP BY id,
name) t1
INNER JOIN relation
ON relation.p_id = t1.id
Please let me know the query, for how to fetch this output. this above query does not work, I am not able to figure how to output child also.

You must join relation to 2 copies of people.
The 1st copy will return the child's name and the 2nd copy will return the names of the parents.
Then group by child and use conditional aggregation to get the names of the parents in one row:
SELECT c.name Child,
MAX(CASE WHEN p.gender = 'F' THEN p.name END) Mother,
MAX(CASE WHEN p.gender = 'M' THEN p.name END) Father
FROM relation r
INNER JOIN people c ON c.id = r.cid
INNER JOIN people p ON p.id = r.pid
GROUP BY r.cid, c.name;
See the demo.

You can join twice and then PIVOT:
SELECT *
FROM (
SELECT c.name AS child,
p.name AS parent,
p.gender
FROM relations r
INNER JOIN people c
ON r.cid = c.id
INNER JOIN people p
ON r.pid = p.id
)
PIVOT (
MAX(parent) FOR gender IN (
'M' AS father,
'F' AS mother
)
)
Which, for the sample data:
CREATE TABLE people (id, name, gender) AS
SELECT 101, 'Riya', 'F' FROM DUAL UNION ALL
SELECT 566, 'Aman', 'M' FROM DUAL UNION ALL
SELECT 202, 'Rakesh', 'M' FROM DUAL UNION ALL
SELECT 875, 'lucky', 'M' FROM DUAL UNION ALL
SELECT 202, 'Reena', 'F' FROM DUAL UNION ALL
SELECT 322, 'Raina', 'F' FROM DUAL UNION ALL
SELECT 345, 'Rohit', 'M' FROM DUAL UNION ALL
SELECT 322, 'Mohit', 'M' FROM DUAL UNION ALL
SELECT 345, 'Meena', 'F' FROM DUAL;
CREATE TABLE relations (cid, pid) AS
SELECT 101, 202 FROM DUAL UNION ALL
SELECT 566, 322 FROM DUAL UNION ALL
SELECT 875, 345 FROM DUAL;
Outputs:
CHILD
FATHER
MOTHER
Riya
Rakesh
Reena
Aman
Mohit
Raina
lucky
Rohit
Meena
db<>fiddle here

Try this one
with cte as
(
select name as child, r.pid as pid
from people p
join relation r
on p.id=r.cid
)
select ee.child as child,
(select name from people PM where id=ee.pid and PM.gender='F') as Mother,
(select name from people PP where id=ee.pid and PP.gender='M') as Father
from cte ee

You can try below query -
SELECT P.name Child, p2.name mother, p3.name father
FROM relation R
JOIN people P ON R.cid = P.id
JOIN people P2 ON R.pid = P.id
AND P.gender = 'F'
JOIN people P3 ON R.pid = P.id
AND P.gender = 'M'

SELECT DISTINCT P1.NAME AS CHILD,(SELECT NAME FROM PEOPLE PM WHERE ID=T.PID AND PM.GENDER='F') AS MOTHER,
(SELECT NAME FROM PEOPLE PP WHERE ID=T.PID AND PP.GENDER='M') AS FATHER FROM
(SELECT R.CID,NAME,R.PID,P.GENDER FROM PEOPLE P,RELATIONS R
WHERE P.ID=R.PID) T,PEOPLE P1
WHERE T.CID=P1.ID;

select * from
(select * from
(select p.firstname as child, (select p1.firstname from persons p1 where r.p_id=p1.id and p1.gender='M') as father
from persons p inner join relations r on
p.id=r.id
) t
where t.father is not null
) as table1
inner join
(select * from
(select p.firstname as child, (select p1.firstname from persons p1 where r.p_id=p1.id and p1.gender='F') as mother
from persons p inner join relations r on
p.id=r.id
) t
where t.mother is not null ) as table2
on table1.child=table2.child

SELECT l.cid AS id,
r.NAME AS Child,
mother.NAME AS Mother,
father.NAME AS Father
FROM testdb.dbo.people r
JOIN testdb.dbo.relation l
ON l.cid = r.id
JOIN (SELECT l.cid AS id,
r.NAME
FROM testdb.dbo.people r
JOIN testdb.dbo.relation l
ON l.pid = r.id
AND gender = 'F') AS mother
ON l.cid = mother.id
JOIN (SELECT l.cid AS id,
r.NAME
FROM testdb.dbo.people r
JOIN testdb.dbo.relation l
ON l.pid = r.id
AND gender = 'M') AS father
ON l.cid = father.id

Query that prints the names of a child and his parents in individual columns respectively in order of the name of the child.
SELECT c.name as child,
MAX(CASE WHEN p.gender = 'F' THEN p.name END) AS mother,
MAX(CASE WHEN p.gender = 'M' THEN p.name END) AS father
FROM relations r
INNER JOIN public."People" c on c.id = r.cid
INNER JOIN public."People" p on p.id = r.pid
GROUP BY r.cid, c.name ORDER BY c.name;

select b.name as child,c.name as father,d.name as mother from relation a join people b on a.cid=b.id
join people c on a.pid=c.id
join people d on a.pid=d.id where c.gender='m' and d.gender='f'

Related

Recursive joins

I have two tables defined below (note that the Regions table is recursive and that the recursion can potentially have many levels).
Regions
Id
ParentId
Name
1
null
EU
2
1
Germany
3
1
France
Cities
Id
Name
RegionId
1
Berlin
2
2
Hamburg
2
3
Paris
3
4
Nice
3
I want to see how many cities there are in a particular region. Desired output below:
Region
CityCount
EU
4
Germany
2
France
2
This query gives me the count of cities in every child region, but how do I join in the recursive table to also get the parent (in this case EU) region?
select R.Name, count(C.Id)
from Regions R
join Cities C on C.RegionId = R.Id
group by R.Name
having count(C.Id) > 1
I've tried to simplify a real-world problem I'm facing, this is obviously the simplification.
This appears to be what you're after. You can use an rCTE to move through the hierachy to the root, but each iteration retains certain information; in this case the name of the original node. Then you can still JOIN on the RegionID:
WITH rCTE AS(
SELECT R.ID,
R.ParentID,
R.[Name]
FROM dbo.Regions R
UNION ALL
SELECT R.ID,
R.ParentID,
C.[Name]
FROM dbo.Regions R
JOIN rCTE C ON R.ParentID = C.ID)
SELECT r.[Name],
COUNT(*) AS CityCount
FROM rCTE r
JOIN dbo.Cities C ON r.ID = C.RegionID
GROUP BY r.[Name];
db<>fiddle
You can use a recursive CTE to flatten your regions tree:
with flatregions as
(
select t.ID, t.ParentID, t.Name, 1 as lvl
from regions t
union all
select t.ID, tt.ParentID, tt.Name, t.lvl+1
from flatregions t
inner join regions tt on tt.ID = t.ParentID
)
select * FROM flatregions;
ID
ParentID
Name
lvl
1
EU
1
2
1
Germany
1
3
1
France
1
3
EU
2
2
EU
2
Then use that CTE in the JOIN of your cities table:
with flatregions as
(
select t.ID, t.ParentID, t.Name, 1 as lvl
from regions t
union all
select t.ID, tt.ParentID, tt.Name, t.lvl+1
from flatregions t
inner join regions tt on tt.ID = t.ParentID
)
select R.Name, count(C.Id) as CityCount
from flatregions R
join Cities C on C.RegionId = R.Id
group by R.Name
having count(C.Id) > 1
Region
CityCount
EU
4
Germany
2
France
2
See this db<>fiddle.

Recursive query compute parent values

I have 2 tables Persons and Sales. In Person there is relation between child and parent, I want to compute 20 percent of parent values with the following condition
Persons
Id | ParentId | Name
1 NULL Tom
2 1 Jake
3 2 Kate
4 3 Neil
Sales
PersonId | Sale
4 500
I want to get result like this
Id | ParentId | Name | Sale
1 Null Tom 100 <-- (500*20)/100 left 400
2 1 Jake 80 <-- (400*20)/100 left 320
3 2 Kate 64 <-- (320*20)/100 left 256
4 3 Neil 256 <-- (320*80)/100
I wrote this query but it does not give appropriate result
;WITH cte_persons
AS
(
SELECT p.Id, p.ParentId, p.Name, s.Price FROM Persons AS p
INNER JOIN Sales AS s ON s.PersonId = p.Id
UNION ALL
SELECT p.Id, p.ParentId, p.Name, CAST((c.Price - (c.Price*80)/100) AS DECIMAL(6, 2)) FROM #Persons AS p
INNER JOIN cte_persons AS c ON c.ParentId = p.Id
)
SELECT * FROM cte_persons
This should be a two steps algorithm. First traverse the hierachy to get max level. Then apply the level in a reverse order.
WITH cte_persons
AS
(
SELECT 1 as level, p.Id, p.ParentId, p.Name, s.Price, p.Id AS base
FROM Persons AS p
INNER JOIN Sales AS s ON s.PersonId = p.Id
UNION ALL
SELECT level + 1, p.Id, p.ParentId, p.Name, c.Price, c.base
FROM Persons AS p
INNER JOIN cte_persons AS c ON c.ParentId = p.Id
)
SELECT Id, ParentId, Name,
CASE level WHEN 1
THEN price - sum(delta) over(partition by base order by level desc) + delta
ELSE delta END sale
FROM (
SELECT *,
(power (0.8000, max(level) over(partition by base) - level) * 0.2) * price delta
FROM cte_persons
) t
ORDER BY id;
db<>fiddle

Select one row from multiple rows based on availability

I have 3 tables:
Emp (Id(PK), Name)
Address (AddressId(PK), AddressType)
EmpAddress (EmpId(FK), AddresId(FK))
One employee may have multiple address.
Sample data:
Emp
1 abc
2 pqr
Address
1 a
2 b
3 c
EmpAddress
1 1
1 2
1 3
Here empid 1 has all 3 addresses.
I want the only one address at a time based on availability.
If adresstype a is available then display only a
If adresstype c is available then display only c
If adresstype b is available then display only b
Priority is a->c->b
If only one available then display that without any Priority .
I wrote this query, but it is not working:
select *
from Emp
inner join EmpAddress on Emp.Id = .EmpAddress .Emp
inner join Address on Address.Id = EmpAddress.Address_Id
where AddressType is NOT NULL
and AddressType = case
when AddressType = 'a' then 'a'
when AddressType = 'c' then 'c'
when AddressType = 'b' then 'b'
end
One approach would be to use ROW_NUMBER() to assign a numerical priority to each address type based on the ordering a > c > b. Then, subquery to retain only the highest ranking address for each employee.
SELECT Id, Emp, AddressType
FROM
(
SELECT e.Id, ea.Emp, a.AddressType,
ROW_NUMBER() OVER (PARTITION BY e.Id
ORDER BY CASE WHEN a.AddressType = 'a' THEN 1
WHEN a.AddressType = 'b' THEN 2
WHEN a.AddressType = 'c' THEN 3 END) rn
FROM Emp e
INNER JOIN EmpAddress ea
ON e.Id = ea.Emp
INNER JOIN Address a
ON a.Id = ea.Address_Id
) t
WHERE t.rn = 1;
select *
from Emp e
cross apply
(
select top 1 ea.AddresId, a.AddressType
from EmpAddress ea
inner join Address a on ea.AddresId = a.AddresId
where ea.EmpId = e.Id
order by case a.AddressType
when 'a' then 1
when 'c' then 2
when 'b' then 3
end
) a
You could also retrieve the records based on Priority via TOP(1) with TIES
SELECT
TOP(1) with TIES e.Id, *
FROM Emp e
INNER JOIN EmpAddress ea ON e.Id = ea.Emp
INNER JOIN Address a ON a.Id = ea.Address_Id
ORDER BY ROW_NUMBER() OVER (PARTITION BY e.Id ORDER BY
CASE (a.AddressType) WHEN 'a' THEN 1
WHEN 'c' THEN 2 WHEN 'b' THEN 3 END)

SQL Server: select same column 3 times into 1 column without UNION ALL

I have a table called placeTable totally have three columns: id, parent_id, name, it included country, state and city table.
The parent_ID of the city is id of state, and parent_ID of state is id of country.
And I have another table which called cityList has specified city which id is same as placeTable's ID.
All I want is only select the city stated at Table2 cityList inluding its state and country in Table 1. For example below, Table 2 showed Sydney, so I want to get id, parent_ID and name of Sydney this city, and its state NSW and its country Australia in table1.
Table1: placeTable
id parent_ID name
-------------------
1 0 Australia
2 0 UK
33 1 NSW
34 1 Western Australia
55 33 Sydney
70 34 Perth
Table2: cityList
id name
-------------------
55 Sydney
The output I want:
id parent_ID name
-------------------
1 0 Australia
33 1 NSW
55 33 Sydney
I can get my desired results by doing below but too long, I am thinking there may be other smarter ways available:
Select distinct pt.id,pt.parent_id,pt.name
from placeTable AS pt
join cityList as cl on cl.id = pt.id
UNION ALL
Select distinct ly2.id,ly2.parent_id,ly2.name
from placeTable AS pt
join cityList as cl on cl.id = pt.id
join placeTable AS ly2 on pt.parent_id = ly2.id
UNION ALL
Select distinct ly3.id,ly3.parent_id,ly3.name
from placeTable AS pt
join cityList as cl on cl.id = pt.id
join placeTable AS ly2 on pt.parent_id = ly2.id
join placeTable as ly3 on ly2.parent_id = ly3.id
I have tried below, but this can give me only city:
Select distinct pt.id,pt.parent_id,pt.name
from placeTable AS pt
join cityList as cl on cl.id = pt.id
join placeTable AS ly2 on pt.parent_id = ly2.id
join placeTable as ly3 on ly2.parent_id = ly3.id
Please Try this:
create table #placeTable (ID int, ParentID int, Name varchar(20))
insert into #placeTable (ID, ParentID, Name)
values (1,0,'Australia'), (2,0,'UK'), (33,1,'NSW'), (34,1,'Western Australia'),
(55,33,'Sydney'),(70,34,'Perth');
create table #cityList (ID int, Name varchar(20))
insert into #cityList (ID, Name)
values (55, 'Sydney'), (70, 'Perth');
WITH Selects AS (
SELECT p.*, c.ID as 'GroupID'
FROM #placeTable p
INNER JOIN #cityList c on p.ID = c.ID
UNION ALL
SELECT p.*, s.GroupID
FROM #placeTable p
INNER JOIN Selects s ON p.ID = s.ParentID
)
SELECT ID, ParentID, Name FROM Selects ORDER BY GroupID, ID

Join one to many get one row, by priority

I have a contacts table:
ID NAME
--- ----
1 KK
2 JKI
3 HU
And I have a phone table:
ID ContactID Phone Type
--- --------- ----- --------
1 1 569 Business
2 1 896 Mobile
3 1 258 Fax
4 2 369 Mobile
5 3 124 Fax
6 2 496 Fax
I want to get all contacts with at least one phone number. The phone number to be displayed should be Business, if there are no Busniess Type available, then Mobile, if there are no Mobile type available then Fax else null
Sample Result:
ID NAME PHONE
--- ------ ------
1 KK 569 -- Business present
2 JKI 369 -- Business not present but mobile present
3 HU 124 -- only fax present
;WITH [prior](i,t) AS
(
SELECT 1, 'Business'
UNION ALL SELECT 2, 'Mobile'
UNION ALL SELECT 3, 'Fax'
),
x AS
(
SELECT c.ID, c.Name, p.Phone,
rn = ROW_NUMBER() OVER (PARTITION BY c.ID ORDER BY r.i)
FROM dbo.Contacts AS c
LEFT OUTER JOIN dbo.Phone AS p
ON p.ContactID = c.ID
LEFT OUTER JOIN [prior] AS r
ON r.t = p.[Type]
)
SELECT ID, Name, Phone FROM x
WHERE rn = 1;
If you want to eliminate contacts with no phones, just change both instances of LEFT OUTER to INNER.
select c.ID
, c.Name
, coalesce(business.Phone, mobile.Phone, fax.Phone) as Phone
from Contacts c
left join
Phone business
on business.ContactID = c.ID
and business.type = 'Business'
left join
Phone mobile
on mobile.ContactID = c.ID
and mobile.type = 'Mobile'
left join
Phone fax
on fax.ContactID = c.ID
and fax.type = 'Fax'
where coalesce(business.Phone, mobile.Phone, fax.Phone) is not null
Your data model is not great for querying this efficiently, but this may do the trick:
SELECT C.ID, C.Name, COALESCE(
(SELECT TOP 1 P.Phone FROM Phones P WHERE P.ContactID = C.ID AND P.[Type] = 'Business'),
(SELECT TOP 1 P.Phone FROM Phones P WHERE P.ContactID = C.ID AND P.[Type] = 'Mobile'),
(SELECT TOP 1 P.Phone FROM Phones P WHERE P.ContactID = C.ID AND P.[Type] = 'Fax')
) Phone
FROM Contacts C
The most compact solution I can think of is,
; WITH CTE AS (
SELECT
c.ID, c.NAME, p.Phone
, r = ROW_NUMBER()OVER(PARTITION BY c.ID ORDER BY CASE p.[TYPE] WHEN 'Business' THEN 1 WHEN 'Mobile' THEN 2 ELSE 3 END)
FROM Contacts c
LEFT JOIN Phone p ON p.ContactID = c.ID AND p.[TYPE] IN ('Business','Mobile','Fax')
WHERE EXISTS(SELECT 1 FROM Phone WHERE ContactID = c.ID)
)
SELECT ID, NAME, Phone
FROM CTE
WHERE r = 1;
This solution returns,
Contacts with the first matching phone in the specified order
Contact with NULL phone # if a phone # exists but none of the specified types
No result for Contacts having no phone at all