SQL Group By with multiple counts - sql

I'm trying to group a list of services together along with the number of applicants in each service, but I also need a count on the status each applicant is in.
Applicants table
serviceID clientID applicantID status
----------------------------------------------------
1 41 1 1 (Processing)
1 41 16 1 (Processing)
1 41 15 2 (Ready)
2 41 12 1 (Processing)
2 41 18 3 (Complete)
Service table:
serviceID serviceName
--------------------------
1 Full Service
2 Part Service
Results need to look like:
serviceName totalApplicants processingCount readyCount completeCount
---------------------------------------------------------------------------
Full Service 3 2 1 0
Part Service 2 1 0 1
I've got the following, but it's returning the same count in each of the columns:-
SELECT
Services.serviceName,
(COUNT(Applicants.applicantID)) AS totalApplicants,
ISNULL(SUM(CASE WHEN Applicants.status = 1 THEN 1 ELSE 0 END), 0) AS processingCount,
ISNULL(SUM(CASE WHEN Applicants.status = 2 THEN 1 ELSE 0 END), 0) AS readyCount,
ISNULL(SUM(CASE WHEN Applicants.status = 3 THEN 1 ELSE 0 END), 0) AS completeCount
FROM
Applicants
LEFT JOIN
Services ON Applicants.serviceID = Services.serviceID
WHERE
Applicants.clientID = #CompanyID
GROUP BY
Services.serviceName

You can do conditional aggregation:
select
s.serviceName,
count(s.serviceID) totalApplicants,
sum(case when status = 1 then 1 else 0 end) processingCount,
sum(case when status = 2 then 1 else 0 end) readyCount,
sum(case when status = 3 then 1 else 0 end) completeCount
from service s
left join applicants a on a.serviceID = s.serviceID AND a.clientID = #CompanyID
group by s.serviceID, s.serviceName
The conditional expression use standard case expression; depending on the database that you are actually using, neater alternatives may exists.

Your query should be fine, but it can be simplified to:
SELECT s.serviceName,
COUNT(a.AppicantId) AS totalApplicants,
SUM(CASE WHEN a.status = 1 THEN 1 ELSE 0 END) AS processingCount,
SUM(CASE WHEN a.status = 2 THEN 1 ELSE 0 END) AS readyCount,
SUM(CASE WHEN a.status = 3 THEN 1 ELSE 0 END) AS completeCount
FROM Services s LEFT JOIN
Applicants a
ON a.serviceID = s.serviceID AND
a.clientID = #CompanyID
GROUP BY s.serviceName ;
Notes:
It looks like you want a row for every service, so that should be the first table in the LEFT JOIN.
Hence the filtering on the company goes into the ON clause.
Table aliases make the query easier to write and to read.
No ISNULL() is needed (and I prefer COALESCE() over ISNULL()).

Related

Union with Group By

Had a look at other questions, tried different things but still returning more than one row.
Problem with Union on 2 tables, with group by clause. There should only be one row returned, grouped by the serviceID.
SELECT
serviceID,
serviceName,
FullCount,
WaitingCount,
InProgressCount
from (
select
a.serviceID,
serviceName,
count(applicantID) FullCount,
ISNULL(SUM(CASE WHEN status = 0 THEN 1 ELSE 0 END),0) AS WaitingCount,
ISNULL(SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END),0) AS InProgressCount
from Products s
left join Assigns a on a.serviceID = s.productID
WHERE s.clientID = #ClientID
group by serviceID, serviceName
UNION
select
s.serviceID,
p.serviceName,
count(s.ApplicantID) FullCount,
ISNULL(SUM(CASE WHEN s.status = 0 THEN 1 ELSE 0 END),0) AS WaitingCount,
ISNULL(SUM(CASE WHEN s.status = 1 THEN 1 ELSE 0 END),0) AS InProgressCount
from Legacies s
Left Join Products p on s.serviceID = p.productID
WHERE s.client = #CompanyName
group by serviceID, serviceName
) t
GROUP BY serviceID, serviceName
I'm always getting 2 rows returned, one from each of the tables. I need to group them both together so it only returns 1 row, based on the servicedID.
The data I'm trying to return is from the following tables..
Products Table
productID serviceName
-------------------------
1 Gold Service
2 Silver Service
3 Bronze Service
Assigns Table
ApplicantID serviceID status
-------------------------------------
1 1 0
2 1 0
3 1 1
4 2 0
5 1 1
Legacies Table
ApplicantID serviceID status
-------------------------------------
1 1 0
2 1 0
3 1 0
4 2 0
5 1 1
The result I'm trying to get is one row per serviceID, to show how many applicants are on this service in both the Legacies and Assigns table, something like:-
serviceID serviceName FullCount WaitingCount InProgressCount
----------------------------------------------------------------
1 Gold Service 8 5 3
2 Silver Service 2 2 0
3 Bronze Service 0 0 0
FullCount is a total number of applicants on each service, WaitingCount is the number of applicants on the service with a status of '0' and InProgressCount is the number on this service with a status of '1'
Based on additional information, I think you can just union all the Legacies and Assigns tables.
still untested
select serviceID, servicename, count(*) fullcount
,sum(case when status = 0 THEN 1 ELSE 0 END) AS WaitingCount
,SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) AS InProgressCount
from (
select ApplicantID, serviceID, status
from Assigns
WHERE clientID = #ClientID
union all
select ApplicantID, serviceID, status
from Legacies
WHERE clientID = #ClientID
) combined
left join Products P on P.productID = combined.serviceID
group by serviceID, servicename
below is before edit
It's hard to tell because you do not post enough information (no sample data, no table structures, no expected output). But I think you can probably combine it all into 1 query:
untested which should be obvious with the lack of information.
SELECT isnull(a.serviceID, L.serviceID) serviceID, p.serviceName
,count(*) FullCount, SUM(CASE WHEN isnull(a.status, L.status) = 0 THEN 1 ELSE 0 END) WaitingCount
,sum(CASE WHEN isnull(a.status, L.status) = 1 THEN 1 ELSE 0 END) InProgressCount
from Legacies L
full outer join Assigns a on a.serviceID = L.serviceID
right outer join Products P on P.productID = isnull(a.serviceID, L.serviceID)
where (P.clientID = #ClientID
or L.client = #CompanyName
)
group by isnull(a.serviceID, L.serviceID), p.serviceName

Counting Values based on certain criteria SQL

I am trying to Write a View for some reporting in our CRM program and I'm having trouble with the counts. We are trying to count the number of referrals based on certain criteria. I feel like the way I wrote the view is incorrect. I am trying to use a CASE statement to make sure it counts the values that I want, but I the numbers I am getting I am not able to validate with reverse engineering.
Did I structure the CASE statement correctly?
(Note: I do realize that client is misspelled, unfortunately whoever wrote the database before I got it had some spelling issues.)
This is what I have written so far:
SELECT Comp_Name, Count(Case WHEN comp_primaryreferralsource IS NOT NULL and Comp_type = 'Client' Then 1
ELSE NULL END) AS Client_Referrals,
Count(Case WHEN lead_companyprimaryreferralsource IS NOT NULL Then 1 Else Null End) AS Target_Referrals,
Count(Case WHEN comp_primaryreferralsource IS NOT NULL and Comp_type = 'Prospect' Then 1 Else Null End) As Prospective_Client_Referral,
Count(Case WHEN comp_primaryreferralsource IS NOT NULL and Comp_type = 'Lost_Clent' Then 1 Else Null End) AS Lost_Client_Referral,
Count(Case WHEN mcgr_companyid IS NOT NULL Then 1 Else NULL END) AS Intro_By_MCG
FROM Company
RIGHT JOIN Lead on Lead_PrimaryCompanyID = Comp_CompanyId
RIGHT JOIN MCGRelationships on mcgr_companyid = Comp_CompanyId
WHERE COMP_Name IS NOT NULL
Group By Comp_Name
Here is a Sample of What i'm getting:
Client1 0 0 0 0 1
Client2 0 0 0 0 2
Client3 0 0 0 0 1
Client4 0 0 0 0 1
Client5 0 0 0 0 2
Client6 0 0 0 0 2
Client7 0 0 4 0 4
Client8 0 0 0 0 2
Client9 0 2 2 0 2
Client10 12 6 0 0 12
Client11 0 0 0 0 2
When I just run a query on the table where Client 10 is the Primary Referral source, I get nothing. So I can't account for those 12 instances.
I think your problem may be in the join to MCGRelationships, if each company record can have more than one relationship record. If I'm following you correctly, you want Intro_By_MCG to be 1 if that record exists in MCGRelationships, regardless of the number of times it appears there. By joining the table, your query would be counting a lead once for each MCGRelationship associated with the company the lead comes from. Try this and let us know if the result is different:
SELECT Comp_Name, Count(Case WHEN comp_primaryreferralsource IS NOT NULL and Comp_type = 'Client' Then 1
ELSE NULL END) AS Client_Referrals,
Count(Case WHEN lead_companyprimaryreferralsource IS NOT NULL Then 1 Else Null End) AS Target_Referrals,
Count(Case WHEN comp_primaryreferralsource IS NOT NULL and Comp_type = 'Prospect' Then 1 Else Null End) As Prospective_Client_Referral,
Count(Case WHEN comp_primaryreferralsource IS NOT NULL and Comp_type = 'Lost_Clent' Then 1 Else Null End) AS Lost_Client_Referral,
Count(Case WHEN EXISTS (Select Comp_CompanyId FROM MCGRelationships WHERE mcgr_companyid = Comp_CompanyId) Then 1 Else NULL END) AS Intro_By_MCG
FROM Company
RIGHT JOIN Lead on Lead_PrimaryCompanyID = Comp_CompanyId
WHERE COMP_Name IS NOT NULL
Group By Comp_Name

Count Between three tables

I have three tables
Data
Service
Status
Data table
Serno | ServiceId | Status | Datetime
1 2 4 12/12/2014
2 1 3 08/12/2014
Service
ServiceId | Service Name
1 Deployment
2 Designing
Status
StatusId | Status
1 Done
2 Pending
3 20%done
4 Canceled
I want a Sql code for Count (that is the status count with respect to Services)
Designing 0 0 0 1
Deployment 0 0 1 0
I have tried this
SELECT COUNT(Service.Status) AS Expr1, Service.ServiceName, Status.Status
FROM Data INNER JOIN
Service ON Hcc_Service_Data.Hcc_Service_Category_Id = Service .ServiceId INNER JOIN
Status ON Data.StatusId = Status.Status_Serno
GROUP BY Service.ServiceName, Status.Status
Select S.ServiceName,
(CASE WHEN D.Status=1 then '1' ELSE '0' END) as S1,
(CASE WHEN D.Status=2 then '1' ELSE '0' END) as S2,
(CASE WHEN D.Status=3 then '1' ELSE '0' END) as S3,
(CASE WHEN D.Status=4 then '1' ELSE '0' END) as S4
FROM Data D, Service S where
D.serviceId = S.serviceId;

SQL query rewrite for prettification and or performance improvement

I have a query that essentially amounts to:
Select query 1
Union
Select query 2
where rowid not in query 1 rowids
Is there a prettier / more performant way to do this? I'm assuming the results of query 1 would be cached and thus utilized in the union... but it's also kinda oogly.
Update with the original query:
SELECT FruitType
, count(CASE WHEN Status = 0 THEN 1 ELSE 0 END) AS Fresh
, count(CASE WHEN Status = 1 THEN 1 ELSE 0 END) AS Ripe
, count(CASE WHEN Status = 2 THEN 1 ELSE 0 END) AS Moldy
FROM FruitTypes FT1
LEfT JOIN Fruits F on F.FTID = FT1.ID
where
Fruit.IsHighPriced = 0
GROUP BY FruitType
Union ALL
select FruitType, 0 as Fresh, 0 as Ripe, 0 as Moldy
FROM FruitTypes ft3
where
ft3.StoreID = #PassedInStoreID
and FruitType NOT IN
(
SELECT FruitType
, count(CASE WHEN Status = 0 THEN 1 ELSE 0 END) AS Fresh
, count(CASE WHEN Status = 1 THEN 1 ELSE 0 END) AS Ripe
, count(CASE WHEN Status = 2 THEN 1 ELSE 0 END) AS Moldy
FROM FruitTypes FT2
LEfT JOIN Fruits F on F.FTID = FT2.ID
where
Fruit.IsHighPriced = 0
GROUP BY FruitType
)
Thanks!
You don't need the second case statement in the NOT in clause. And not Exists is often faster in SQL Server.
SELECT FruitType
, count(CASE WHEN Status = 0 THEN 1 ELSE 0 END) AS Fresh
, count(CASE WHEN Status = 1 THEN 1 ELSE 0 END) AS Ripe
, count(CASE WHEN Status = 2 THEN 1 ELSE 0 END) AS Moldy
FROM FruitTypes FT1
LEfT JOIN Fruits F on F.FTID = FT1.ID
where
Fruit.IsHighPriced = 0
GROUP BY FruitType
Union ALL
select FruitType, 0 as Fresh, 0 as Ripe, 0 as Moldy
FROM FruitTypes ft3
where
ft3.StoreID = #PassedInStoreID
and NOT EXISTS
(
SELECT *
FROM FruitTypes FT2
LEfT JOIN Fruits F on F.FTID = FT2.ID
where
Fruit.IsHighPriced = 0
and ft3.FruitType = FT2.FruitType
)
The prettiest way of writing would probably be by turning query #1 into a view or a function, then using that view or function to call the repetitious code.
Performance could possibly be improved by using query #1 to fill a temp table or table variable, then using that temp table in place of the repititious code.

Need Help on Oracle Select Query

I am finding difficulty to frame a select query.
PFB, for the table and corresponding data:
ID DLS MATCH_STATUS LAST_UPDATE_TIME BO CH FT
1 0 0 09-07-2013 00:00:00 IT TE NA
1 1 1 09-07-2013 00:01:01 IT TE NA
2 0 0 09-07-2013 10:00:00 IP TE NA
3 0 0 09-07-2013 11:00:00 IT YT NA
3 2 2 09-07-2013 11:01:00 IT YT NA
Here
Match_Status 0-->Initial Record
1-->Singel Match
2-->Multi Match
For every record there will be a initial entry with match_status 0 and subsequent matching process end other number such as 1,2 will be update.
I am trying to retrieve records such as total record , waiting match ,single match and multi match group by BO, CH and FT
Below is the expected out put:
BO CH FT TOTAL_RECORD AWAITNG_MATCH SINGLE_MATCH MULTI_MATCH
IT TE NA 1 0 1 0
IP TE NA 1 1 0 0
IT YT NA 1 0 0 2
I have tried below query :
select BO,CH,FT,sum(case when match_status=0 then 1 else 0 end) as TOTAL_RECORD,
sum(case when match_status = 0 then 1 else 0 end) as AWAITING_MATCH,
sum(case when match_status = 1 then 1 else 0 end) as SINGLE_MATCH,
sum(case when match_status = 2 then 1 else 0 end) as MULTI_MATCH from
table1 where last_update_time >= current_timestamp-1
group by BO,CH,FT;
problem with the above query is, awaiting_match is getting populated same as total record as I understand because of match_status=0
Similarly I tried with
select BO,CH,FT,sum(case when match_status=0 then 1 else 0 end) as TOTAL_RECORD,
select (sum(case when t1.ms=0 then 1 else 0 end) from
(select max(match_status) as ms from table1 where last_update_time >= current_timestamp-1 group by id)t1) )awaiting_match,
sum(case when match_status = 1 then 1 else 0 end) as SINGLE_MATCH,
sum(case when match_status = 2 then 1 else 0 end) as MULTI_MATCH from
table1 where last_update_time >= current_timestamp-1
group by BO,CH,FT;
problem with the approach is awaiting_match is getting populated with the same value for subsequent row entry.
Please help me with a suitable query for the desired format.
Thanks a lot in advance.
It seems that you want the last match status. I am guessing that this is actually the maximum of the statuses. If so, the following solves the problem by first grouping on id and then doing the grouping to summarize:
select BO, CH, FT,
count(*) as TOTAL_RECORD,
sum(case when lastms = 0 then 1 else 0 end) as AWAITING_MATCH,
sum(case when lastms = 1 then 1 else 0 end) as SINGLE_MATCH,
sum(case when lastms = 2 then 1 else 0 end) as MULTI_MATCH
from (select id, bo, ch, ft, MAX(match_status) as lastms
from table1
where last_update_time >= current_timestamp-1
group by id, bo, ch, ft
) t
group by BO, CH, FT;
If you actually want the last update to provide the status for the id, then you can use row_number() to enumerate the rows for each id, order by update time descending, and choose the first one:
select BO, CH, FT,
count(*) as TOTAL_RECORD,
sum(case when lastms = 0 then 1 else 0 end) as AWAITING_MATCH,
sum(case when lastms = 1 then 1 else 0 end) as SINGLE_MATCH,
sum(case when lastms = 2 then 1 else 0 end) as MULTI_MATCH
from (select id, bo, ch, ft, match_status,
ROW_NUMBER() over (partition by id order by last_update_time desc) as seqnum
from table1
where last_update_time >= current_timestamp-1
) t
where seqnum = 1
group by BO, CH, FT;