merge multiple tables (belonging to group 'x') - sql

I have three tables in the database:
customer -table
customername
customerid
group -table
groupid
groupname
customer-group table
customerid
groupid
What kind of SQL generates the output:
customername, 'X' if the customer belongs to a group 1, 'X' if customer belongs to a group 2, ...
(Group number is limited to 7.)
For example:
Matt, 'X', '' , 'X', '' , 'x', '' , ''
Mark, '' , 'X', '' , 'X', '' , 'X', ''
Thanks

You can do conditional aggregation:
select c.customername,
max(case when cg.groupid = 1 then 'X' end) has_grp1,
max(case when cg.groupid = 2 then 'X' end) has_grp2,
max(case when cg.groupid = 3 then 'X' end) has_grp3,
max(case when cg.groupid = 4 then 'X' end) has_grp4,
max(case when cg.groupid = 5 then 'X' end) has_grp5,
max(case when cg.groupid = 6 then 'X' end) has_grp6,
max(case when cg.groupid = 7 then 'X' end) has_grp7
from customer c
inner join customer_group cg on cg.customerid = c.customerid
group by c.customerid, c.customername
Notes:
You did not tell which database you are running; the above query uses standard case expressions, that (almost) all databases support. Your database may have neater alternatives though
It does not look like we need table group to generate the desired result; if that's needed for some reason (say you want to pivot over the group name rather than over the group id), you can just add another join to the query

In the code below I am joining the main table which is the customer with the navigation table customer-group multiple times once for each possible group.
Since I am using left joins, I am ensuring the the left part each time will always be returned and the if the right part does not exist then it will be returned as a null value
select
customername,
case when cg1.groupid is null then '' else 'X' end as 'ExistsInGroup1',
case when cg2.groupid is null then '' else 'X' end as 'ExistsInGroup2',
case when cg3.groupid is null then '' else 'X' end as 'ExistsInGroup3',
case when cg4.groupid is null then '' else 'X' end as 'ExistsInGroup4',
case when cg5.groupid is null then '' else 'X' end as 'ExistsInGroup5',
case when cg6.groupid is null then '' else 'X' end as 'ExistsInGroup6',
case when cg7.groupid is null then '' else 'X' end as 'ExistsInGroup7'
from customer c
left join [customer-group] cg1
on c.customerid = cg.customerid
and groupid = 1
left join [customer-group] cg2
on c.customerid = cg.customerid
and groupid = 2
left join [customer-group] cg3
on c.customerid = cg.customerid
and groupid = 3
left join [customer-group] cg4
on c.customerid = cg.customerid
and groupid = 4
left join [customer-group] cg5
on c.customerid = cg.customerid
and groupid = 5
left join [customer-group] cg6
on c.customerid = cg.customerid
and groupid = 6
left join [customer-group] cg7
on c.customerid = cg.customerid
and groupid = 7
As many problems in the world this is also able to be solved with many different approaches. For instance you could use many subqueries for each field like so:
select
customername,
case when exists (select top 1 1 from [customer-group] cg where c.customerid = cg.customerid and groupid = 1) then 1 else 0 end as 'ExistsInGroup1',
case when exists (select top 1 1 from [customer-group] cg where c.customerid = cg.customerid and groupid = 2) then 1 else 0 end as 'ExistsInGroup2',
case when exists (select top 1 1 from [customer-group] cg where c.customerid = cg.customerid and groupid = 3) then 1 else 0 end as 'ExistsInGroup3',
case when exists (select top 1 1 from [customer-group] cg where c.customerid = cg.customerid and groupid = 4) then 1 else 0 end as 'ExistsInGroup4',
case when exists (select top 1 1 from [customer-group] cg where c.customerid = cg.customerid and groupid = 5) then 1 else 0 end as 'ExistsInGroup5',
case when exists (select top 1 1 from [customer-group] cg where c.customerid = cg.customerid and groupid = 6) then 1 else 0 end as 'ExistsInGroup6',
case when exists (select top 1 1 from [customer-group] cg where c.customerid = cg.customerid and groupid = 7) then 1 else 0 end as 'ExistsInGroup7'
from customer c
Another example of a solution would be to utilize a pivot. A bit more advanced case. You may check documentation for SqlServer PIVOT here

select c.customername,
iif(g.groupname=1, 'x', ''),
iif(g.groupname=2, 'x', ''),
iif(g.groupname=3, 'x', '') -- etc etc
from Customer c inner join customer-Group cg on c.customerid = cg.customerid
inner join group g on cg.groupid=g.groupid

Related

Add flag for row existence in another table with group by

For below query I want to have a flag called isHold that will evaluate to 0
if there is no billNo from the view viewBills exists in onHold table and
1 otherwise
select max(t.id) TrackingID , max(vb.billNo) billNo, cb.id ,
max(case when vb.[count] > 1 then 1 else 0 end) isMultiple ,
max(case when t.TrackingID = 31 then 1 else 0 end) IsCancelled,
max(case when exists (select 1 from OnHold oh
where oh.billNo = billNo) then 1 else 0 end) IsHold
from viewBills vb
join tracking t on vb.billNo = t.billNo
join customerBills cb on vb.billNo = cb.billNo
join customerPieces cp on cb.id = cp.customerBillId
where cb.statusid <> 3
group by cb.id
I got this error when executing
Cannot perform an aggregate function on an expression
containing an aggregate or a subquery.
It's reasonable but how can achieve that?
You can use outer apply or a left join to move the logic to the FROM clause:
select max(t.id) as TrackingID , max(vb.billNo) as billNo, cb.id ,
max(case when vb.[count] > 1 then 1 else 0 end) as isMultiple,
max(case when t.TrackingID = 31 then 1 else 0 end) as IsCancelled,
max(case when oh.billNo is not null then 1 else 0 end) as IsHold
from viewBills vb join
tracking t
on vb.billNo = t.billNo join
customerBills cb
on vb.billNo = cb.billNo join
customerPieces cp
on cb.id = cp.customerBillId outer apply
(select top (1) oh.*
from OnHold oh
where oh.billNo = cb.billNo
) oh
where cw.statusid <> 3
group by cb.id;
You can go for LEFT OUTER JOIN and do the aggregation as given below:
select max(t.id) TrackingID , max(vb.billNo) billNo, cb.id ,
max(case when vb.[count] > 1 then 1 else 0 end) isMultiple ,
max(case when t.TrackingID = 31 then 1 else 0 end) IsCancelled,
max(case when oh.billNo IS NOT NULL then 1 else 0 end) IsHold
from viewBills vb
join tracking t on vb.billNo = t.billNo
join customerBills cb on vb.billNo = cb.billNo
join customerPieces cp on cb.id = cp.customerBillId
LEFT OUTER JOIN OnHold oh ON oh.billNo = vb.billNo
where cb.statusid <> 3
group by cb.id

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.

how to get a name from a group by id in sql

I have a table of customers groups and I would like to select the group's names (and another value called Notes) but group by doesn't allow me to do that even though the entire group has the same value of names and notes.
select cg.idCustomerGroup,
sum(case when c.type = 'child' then 1 else 0 end) as Children,
sum(case when c.type = 'adult' then 1 else 0 end) as Adults,
sum(case when c.type = 'senior' then 1 else 0 end) as Seniors
from CustomersGroups cg
inner Join CustomersInGroup cig
on cg.idCustomerGroup = cig.idCustomerGroup
inner Join Customers c
on c.idCustomer = cig.idCustomer
Group by cg.idCustomerGroup
You have to include the group's name and notes in the GROUP BY:
select cg.idCustomerGroup, cg.Name, cg.Notes,
sum(case when c.type = 'child' then 1 else 0 end) as Children,
sum(case when c.type = 'adult' then 1 else 0 end) as Adults,
sum(case when c.type = 'senior' then 1 else 0 end) as Seniors
from CustomersGroups cg
inner Join CustomersInGroup cig
on cg.idCustomerGroup = cig.idCustomerGroup
inner Join Customers c
on c.idCustomer = cig.idCustomer
Group by cg.idCustomerGroup, cg.Name, cg.Notes

Combining unrelated queries into one query to produce counts

I would like a stored procedure to run daily that produces a report of counts.
For example, the .csv would look something like this:
Daily,1
Deaths,0
In-House EKG,4
In-House Xray,2
Suicidal Patients,12
HIV,0
Their individual queries look something like this:
-- Daily and Death Counts
select
SUM(CASE WHEN location != '[OUT]' THEN 1 ELSE 0 END) as 'Daily',
SUM(CASE WHEN death = 1 THEN 1 ELSE 0 END) as 'Deaths'
from
patient_data
-- In-House Tasks
select
SUM(CASE WHEN cat_id = 72 THEN 1 ELSE 0 END) as 'In-House EKG',
SUM(CASE WHEN cat_id = 73 THEN 1 ELSE 0 END) as 'In-House XRay',
from
organizer_tasks
-- Suicidal Patients
select
count(distinct(pid)) as 'Suicidal Inmates'
from
problems pr
inner join problem_list pl on pl.id = pr.problem_list_id
where
pr.status = 'open'
and pl.title like '%suicide%'
-- HIV
select
count(distinct(pid)) as 'HIV'
from
problems pr
inner join problem_list pl on pl.id = pr.problem_list_id
inner join patient_data pd on pr.pid = pd.pid
where
pr.status = 'open'
and pl.title like '%hiv%'
As you can see, each set of data comes from a different table, and has no relation. How can I accomplish my desired result set?
Thanks.
-- Daily and Death Counts
select * from (
select
SUM(CASE WHEN location != '[OUT]' THEN 1 ELSE 0 END) as 'Daily',
SUM(CASE WHEN death = 1 THEN 1 ELSE 0 END) as 'Deaths'
from
patient_data
) tmp unpivot (Number for Type in ([Daily], [Deaths])) t
union all
-- In-House Tasks
select * from (
select
SUM(CASE WHEN cat_id = 72 THEN 1 ELSE 0 END) as 'In-House EKG',
SUM(CASE WHEN cat_id = 73 THEN 1 ELSE 0 END) as 'In-House XRay'
from
organizer_tasks
) tmp unpivot (Number for Type in ([In-House EKG], [In-House XRay])) t
union all
-- Suicidal Patients
select 'Suicidal Inmates',
count(distinct(pid))
from
problems pr
inner join problem_list pl on pl.id = pr.problem_list_id
where
pr.status = 'open'
and pl.title like '%suicide%'
union all
-- HIV
select 'HIV',
count(distinct(pid))
from
problems pr
inner join problem_list pl on pl.id = pr.problem_list_id
inner join patient_data pd on pr.pid = pd.pid
where
pr.status = 'open'
and pl.title like '%hiv%'
Try this with Union which is form in one Query :
select
SUM(CASE WHEN location != '[OUT]' THEN 1 ELSE 0 END) as 'Daily'
from
patient_data
UNION ALL
select
SUM(CASE WHEN death = 1 THEN 1 ELSE 0 END) as 'Deaths'
from
patient_data
UNION ALL
-- In-House Tasks
select
SUM(CASE WHEN cat_id = 72 THEN 1 ELSE 0 END) as 'In-House EKG'
from
organizer_tasks
UNION ALL
select
SUM(CASE WHEN cat_id = 73 THEN 1 ELSE 0 END) as 'In-House XRay'
from
organizer_tasks
UNION ALL
-- Suicidal Patients
select
count(distinct(pid)) as 'Suicidal Inmates'
from
problems pr
inner join problem_list pl on pl.id = pr.problem_list_id
where
pr.status = 'open'
and pl.title like '%suicide%'

sql query with percent and aggregation functions

I'm trying to write a sql query where I need number of scores > 3 by county, then for counties on that list I need to produce a percentage of rooms with scores < 3. So I need three columns, County Name, # of scores > 3 by county, % of rooms with scores < 3 by county
SELECT County = c.Description, [Score > 3] = count(s.Score),
((select count(room.Name) where s.Score< 3) /( select count(room.Name) ) * 100)
FROM Sites AS s
inner join Profiles as p on s.Profile_Id = p.Id
inner join Counties as c on p.County_Id = c.Id
inner join Rooms as room on s.Id = room.Site_Id
where s.Score > 3
Group By c.Description
I think you are over complicating the issue, rather than subselects you can just limit the data returned by using the HAVING clause, then use CASE in the COUNT:
SELECT County = c.Description,
[Score > 3] = COUNT(CASE WHEN Sites.Score > 3 THEN 1 END),
[% Score < 3] = 100.0 * COUNT(CASE WHEN Sites.Score < 3 THEN 1 END) / COUNT(1)
FROM Sites
INNER JOIN Profiles
ON Sites.Profile_Id = Profiles.Id
INNER JOIN Counties
ON Profiles.County_Id = Counties.Id
INNER JOIN Rooms
ON Sites.Id = Rooms.Site_Id
GROUP BY c.Description
HAVING COUNT(CASE WHEN Sites.Score > 3 THEN 1 END) > 0;
Demo on SQL Fiddle
EDIT
SELECT County = c.Description,
[Score > 3] = COUNT(CASE WHEN Sites.Score > 3 THEN 1 END),
[% Score < 3] = 100.0 * SUM(CASE WHEN Sites.Score < 3 THEN 1 END) / COUNT(*),
[Score > 3] = SUM(CASE WHEN Sites.Score > 3 THEN RoomCount ELSE 0 END),
[% Score < 3] = 100.0 * SUM(CASE WHEN Sites.Score < 3 THEN RoomCount ELSE 0 END) / SUM(RoomCount)
FROM Sites
INNER JOIN Profiles
ON Sites.Profile_Id = Profiles.Id
INNER JOIN Counties
ON Profiles.County_Id = Counties.Id
INNER JOIN
( SELECT Site_Id, RoomCount = COUNT(*)
FROM Rooms
GROUP BY Site_Id
) Rooms
ON Sites.Id = Rooms.Site_Id
GROUP BY c.Description
HAVING COUNT(CASE WHEN Sites.Score > 3 THEN 1 END) > 0;
Use Cast
SELECT County = c.Description, [Score > 3] = count(s.Score),
( Cast(select count(room.Name) where s.Score < 3 ) as float / ( select count(room.Name) ) * 100)
FROM Sites AS s
inner join Profiles as p on s.Profile_Id = p.Id
inner join Counties as c on p.County_Id = c.Id
inner join Rooms as room on s.Id = room.Site_Id
where s.Score > 3
Group By c.Description
[Score > 3] = count(s.Score)
...
where s.Score > 3
Both statements are not needed. In fact your Where clause limits all operations to s.score >3, which is not ideal when you are also trying to pull data from scores <3.
If you are trying to count both the cases where s.Score>3 and where it is <3, you need to use the CASE statement
SELECT SUM(CASE WHEN s.score < 3 THEN 1 ELSE 0 END) AS Hiscores,
SUM(CASE WHEN s.score > 3 THEN 1 ELSE 0 END) /count(s.scores) AS percentLowScores
This should do it
SELECT c.Description County,
SUM(CASE WHEN s.score < 3 THEN 1 ELSE 0 END) AS Hiscores,
SUM(CASE WHEN s.score > 3 THEN 1 ELSE 0 END) /count(s.scores) AS percentLowScores
FROM Sites AS s
inner join Profiles as p on s.Profile_Id = p.Id
inner join Counties as c on p.County_Id = c.Id
inner join Rooms as room on s.Id = room.Site_Id
Group By c.Description