Join one to many get one row, by priority - sql

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

Related

SQL get record when finding in 2 records

Hi I tried to build sql query to find id when is in 2 records (can be more). Let me explained by example
I have 2 tables
C
id
type_id
1
499
1
599
D
type_id
type_name
499
AN
599
DE
And I want to get id which has AN and DE
SELECT *
FROM C
INNER JOIN D
ON D.type_id = C.type_id
WHERE
EXISTS (SELECT 1 FROM D D1 WHERE D1.type_id = C.type_id AND D1.type_name = 'AN') AND
EXISTS (SELECT 1 FROM D D2 WHERE D2.type_id = C.type_id AND D2.type_name = 'DE');
But did not work .Than you for help
If you want all the data from the join then you can use analytic functions:
SELECT id,
type_id,
type_name
FROM (
SELECT c.id,
c.type_id,
d.type_name,
COUNT(CASE d.type_name WHEN 'AN' THEN 1 END) OVER (PARTITION BY c.id)
AS num_an,
COUNT(CASE d.type_name WHEN 'DE' THEN 1 END) OVER (PARTITION BY c.id)
AS num_de
FROM C
INNER JOIN D
ON D.type_id = C.type_id
WHERE d.type_name IN ('AN', 'DE')
)
WHERE num_an > 0
AND num_de > 0;
Which outputs:
ID
TYPE_ID
TYPE_NAME
1
599
DE
1
499
AN
If you just want the id then you can aggregate and use a HAVING clause:
SELECT c.id
FROM C
INNER JOIN D
ON D.type_id = C.type_id
WHERE d.type_name IN ('AN', 'DE')
GROUP BY c.id
HAVING COUNT(CASE d.type_name WHEN 'AN' THEN 1 END) > 0
AND COUNT(CASE d.type_name WHEN 'DE' THEN 1 END) > 0
Which outputs:
ID
1
fiddle
Get the distinct counts of type_name for each ID ensure count = two and limit type_name to 'AN' or 'DE'
SELECT C.ID
FROM C
INNER JOIN D
on C.type_id=D.type_id -- standard join on Type_ID
WHERE D.Type_name in ('AN','DE') -- limit records to only AN/DE since we need both.
GROUP BY C.ID -- group so we get just 1 ID
HAVING Count(Distinct Type_name) = 2 -- ensure distinct count is 2 for each C.ID.
We join the two tables
We limit to ID having either an 'AN' or DE type name
We group by ID's
We count the distinct types for each ID and if it's 2, we know we have an AN and DE type for that ID.
Count distinct is used since I'm unsure if a type_name could be duplicated for a C.ID. It looks possible given table structure. but unsure without known Pk/FK relations. distinct "Might" be able to be removed if we KNOW it's not possible.

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 group table by user with custom column on same row

I have 3 tables
PATIENT:
patientid name comment result
--------------------------------
1 davis test p
2 brown test p
3 mike test p
PHONE TYPE:
phone_id patient_id phone_type
-------------------------------
3324 1 1
3325 1 4
5467 2 1
PHONE DETAILS:
phone_id number
-----------------------
3324 8253322
3325 180040204
5467 5674543
The goal is to add a column on the select statement based on the value of phone type (1 = phone, 4 = fax) and to group the column so that the phone and
fax are on the same line, not alternating.
Desired result:
name phone fax comment result
---------------------------------------------
davis 8253322 180040204 test p
brown 5674543 null test p
mike null null test p
I have this so far
select
a.name, a.comment, a.result,
(CASE WHEN (b.phone_type = 1) THEN c.number ELSE NULL END) AS Phone
(CASE WHEN (b.phone_type = 4) THEN c.number ELSE NULL END) AS Fax
The problem with this is that it doesn't group the values by the user.
name phone fax comment result
-----------------------------------------------
davis 8253322 null test p
davis null 180040204 test p
mike null null test p
If we can assume 1 phone/fax per name.
select a.name
, a.comment
, a.result
, max(CASE WHEN (b.phone_type = 1) THEN c.number ELSE NULL END) AS Phone
, max((CASE WHEN (b.phone_type = 4) THEN c.number ELSE NULL END) AS Fax
FROM...
GROUP BY a.name
, a.comment
, a.result
If not... then break phone and fax into two data sets based the type and generate row number for each set. Then use a full outer join on patentId and that row number.
With Phone as (SELECT phone_id, patient_id, phone_type, PD.Number, row_number() over (partition by Patient_ID order by PD.Number) as RN
FROM Phone_type PT
INNER JOIN Phone_Details PD
ON PT.Phone_ID = PD.Phone_ID
WHERE Phone_Type = 1),
Fax as (SELECT phone_id, patient_id, phone_type, PD.Number, row_number() over (partition by Patient_ID order by PD.Number) as RN
FROM Phone_type PT
INNER JOIN Phone_Details PD
ON PT.Phone_ID = PD.Phone_ID
WHERE Phone_Type = 1)
SELECT *
FROM PHONE P
FULL OUTER JOIN FAX F
on P.Patent_Id =F.PatentID
and P.RN=F.RN
You can use an aggregation function if you are sure you will only ever have one value at most for phone or fax.
select a.name, a.comment, a.result,
Min((CASE WHEN (b.phone_type = 1) THEN c.number ELSE NULL END) AS Phone,
Min((CASE WHEN (b.phone_type = 4) THEN c.number ELSE NULL END)) AS Fax
FROM (...)
GROUP BY a.name, a.comment, a.result
You can use the following SQL statement
WITH patient_phone AS (
SELECT a.name,
a.comment,
a.result,
(CASE WHEN (b.phone_type = 1) THEN c.number ELSE NULL END) AS Phone,
(CASE WHEN (b.phone_type = 4) THEN c.number ELSE NULL END) AS Fax
FROM patient a
LEFT OUTER JOIN phone_type b
ON a.patient_id = b.patient_id
LEFT OUTER JOIN phone_details c
ON b.phone_id = c.phone_id
GROUP BY a.name,
a.comment,
a.result,
b.phone_type,
c.number
)
SELECT name,
MAX(phone) phone,
MAX(fax) fax,
comment,
result
FROM patient_phone
GROUP BY name,
comment,
result
Result
name phone fax comment result
brown 5674543 NULL test p
davis 8253322 180040204 test p
mike NULL NULL test p

Nested Oracle SQL - Multiple Values

I have a table structure like:
Table = contact
Name Emailaddress ID
Bill bill#abc.com 1
James james#abc.com 2
Gill gill#abc.com 3
Table = contactrole
ContactID Role
1 11
1 12
1 13
2 11
2 12
3 12
I want to select the Name and Email address from the first table where the person has Role 12 but not 11 or 13. In this example it should return only Gill.
I believe I need a nested SELECT but having difficulty in doing this. I did the below but obviously it isn't working and returning everything.
SELECT c.Name, c.Emailaddress FROM contact c
WHERE (SELECT count(*) FROM contactrole cr
c.ID = cr.ContactID
AND cr.Role NOT IN (11, 13)
AND cr.Role IN (12)) > 0
You can use a combination of EXISTS and NOT EXISTS
SELECT *
FROM contact c
WHERE
EXISTS(SELECT 1 FROM contactrole cr WHERE cr.ContactID = c.ID AND cr.Role = 12)
AND NOT EXISTS(SELECT 1 FROM contactrole cr WHERE cr.ContactID = c.ID AND cr.Role IN(11, 13))
Another option is to use GROUP BY and HAVING:
SELECT c.*
FROM contact c
INNER JOIN contactrole cr
ON cr.ContactID = c.ID
GROUP BY
c.ID, c.Name, c.Emailaddress
HAVING
SUM(CASE WHEN cr.Role = 12 THEN 1 ELSE 0 END) > 0
AND SUM(CASE WHEN cr.Role IN(11, 13) THEN 1 ELSE 0 END) = 0
Use conditional aggregation in Having clause to filter the records
Try this
SELECT c.NAME,
c.emailaddress
FROM contact c
WHERE id IN (SELECT contactid
FROM contactrole
GROUP BY contactid
HAVING Count(CASE WHEN role = 12 THEN 1 END) > 1
AND Count(CASE WHEN role in (11,13) THEN 1 END) = 0)
If you have only 11,12,13 in role then use can use this
SELECT c.NAME,
c.emailaddress
FROM contact c
WHERE id IN (SELECT contactid
FROM contactrole
GROUP BY contactid
HAVING Count(CASE WHEN role = 12 THEN 1 END) = count(*)
You can do this using JOINs:
SELECT c.*
FROM CONTACT c
INNER JOIN CONTACTROLE cr12
ON cr12.CONTACTID = c.ID AND
cr12.ROLE = 12
LEFT OUTER JOIN CONTACTROLE cr11
ON cr11.CONTACTID = c.ID AND
cr11.ROLE = 11
LEFT OUTER JOIN CONTRACTROLE cr13
ON cr13.CONTACTID = c.ID AND
cr13.ROLE = 13
WHERE cr11.ROLE IS NULL AND
cr13.ROLE IS NULL
The INNER JOIN CONTACTROLE cr12 requires that role 12 exist for the given contact ID; the LEFT OUTER JOIN CONTACTROLE cr11 and LEFT OUTER JOIN CONTRACTROLE cr13 check to see if roles 11 and 13 might exist for the given contact ID; and the WHERE clause verifies that neither roles 11 or 13 exist.
Best of luck.

Finding value when multiple rows by group by

Lets say we have the following data sets
tbl_building:
id -- name
1 -- building 1
2 -- building 2
tbl_rooms:
id -- building_id -- room_id -- light_status
1 ------ 1 ------------- 1 ----------- 0
2 ------ 1 ------------- 2 ----------- 1
3 ------ 1 ------------- 3 ----------- 0
4 ------ 2 ------------- 1 ----------- 1
How would I construct a single sql statement to find out which BUILDINGS have a light switched on in a YES/NO format Whilst grouping by Building name
Idealling I want something like the following:
SELECT b.name, if(light_status, 'yes', no) as light_status
FROM tbl_building b
JOIN tbl_rooms r on b.id = r.building_id
group by b.id
However, this seems to be random as to which room it will bring back for each buildinh
Select b.name, case when sum (a.light_status) > 0 then 'YES' else 'NO' end as LightStatus
From tbl_rooms a
Join tbl_buildings b
On a.building_id = b.building_id
Group by b.name
A simple case for a semi-join:
SELECT name
FROM tbl_building b
WHERE EXISTS (
SELECT 1
FROM tbl_rooms r
WHERE b.id = r.building_id
AND light_status = 1
)
This will return those buildings where at least one room has their light switched on.
select (case when tbl_rooms.light_status = 1 then building_id end) as building_id_on,
(case when tbl_rooms.light_status = 0 then building_id end) as building_id_on
from tbl_building inner join tbl_rooms on tbl_building.id = tbl_rooms.building_id
Try this:
SELECT DISTINCT B.name
FROM tbl_rooms A
INNER JOIN tbl_builiding B
ON A.building_id = B.id
To find buildings with at least one room with the lights turned on:
SELECT
B.name
FROM tbl_rooms A
INNER JOIN tbl_builiding B
ON A.building_id = B.id
GROUP BY B.name
HAVING MAX(light_status) = 1
To list all buildings and wether or not they have at least one room with the light turned on:
SELECT
B.name, IIF(MAX(light_status) = 1, 'YES', 'NO') as light_status
FROM tbl_rooms A
INNER JOIN tbl_builiding B
ON A.building_id = B.id
GROUP BY B.name