How to count multiple columns in SQL (Oracle) with criteria? - sql

I'm working on SMS-Gateway that holds multiple charged SMS-services with different numbers,
each SMS sent to the customer has 4 status as below (forwarded, delivered, expired,delivery failed)
Now I have the below first_table for the charging-system with the below details (TABLE-A)
and below (TABLE-B) which contain the status of each sent SMS with its ID
Below is my expected final result to forecast the details for each sms-service :
At first I thought it was easy all I need is just to use COUNT(Case when ...)
but in my case I have thousands of SMS-numbers(services) so if I use this approach it will be like that:-
COUNT(CASE WHEN a.SMS_SHORT_CODE='1111' AND B.STATUS='forwarded' )
COUNT(CASE WHEN a.SMS_SHORT_CODE='1111' AND B.STATUS='delivered' )
COUNT(CASE WHEN a.SMS_SHORT_CODE='1111' AND B.STATUS='expired' )
COUNT(CASE WHEN a.SMS_SHORT_CODE='1111' AND B.STATUS='delivery failed' )
COUNT(CASE WHEN a.SMS_SHORT_CODE='5000' AND B.STATUS='forwarded' )
COUNT(CASE WHEN a.SMS_SHORT_CODE='5000' AND B.STATUS='delivered' )
COUNT(CASE WHEN a.SMS_SHORT_CODE='5000' AND B.STATUS='expired' )
COUNT(CASE WHEN a.SMS_SHORT_CODE='5000' AND B.STATUS='delivery failed' )
...
...
...
...
...
...
...
The above approach not practical when you have many services also noting that CASE can handle only 250 conditions?
So what is the best approach to do left outer join for (Table A) on (Table B) using the SMS-ID and count each SMS-status and forecast it as below?

I would suggest conditional aggregation:
select b.SMS_SHORT_CODE,
sum(case when status = 'forwaded' then 1 else 0 end) as count_of_forwaded,
sum(case when status = 'delivered' then 1 else 0 end) as count_of_status,
sum(case when status = 'expired' then 1 else 0 end) as count_of_expired,
sum(case when status = 'delivery failed' then 1 else 0 end) as count_of_delivery_failed
from TABLEB b
group by b.SMS_SHORT_CODE ;
Note that no JOIN is necessary. All the data you want to aggregate is in TABLEB.

Please use below query,
select
A.SMS_SHORT_CODE,
case when status = 'forwaded' then count(status ) end as count_of_forwaded,
case when status = 'delivered' then count(status ) end as count_of_status,
case when status = 'expired' then count(status ) end as count_of_expired,
case when status = 'delivery failed' then count(status ) end as count_of_delivery_failed
from TABLEA A
inner join TABLEB B
on (A.SMS_ID = B.SMS_ID)
group by A.SMS_SHORT_CODE, status ;

You can use PIVOT clause (introduced in Oracle 11g version) for those status columns :
SELECT sms_short_code,
COUNT_OF_forwarded,
COUNT_OF_delivered,
COUNT_OF_expired,
COUNT_OF_delivery_failed
FROM tableB
PIVOT
(
COUNT(*) FOR status IN ( 'forwarded' AS COUNT_OF_forwarded,
'delivered' AS COUNT_OF_delivered,
'expired' AS COUNT_OF_expired,
'delivery failed' AS COUNT_OF_delivery_failed )
)
e.g. only using TableB is enough.
Demo

Related

Remove duplicates records from sum statement using SQL server

Hi i have a table in which it has some duplicate records i want to remove all duplicate records and get the total count for that condition . I have tried following query here in first query it will include duplicate records also .
1> select count(fd.FarmerRegNo) as TotalRecord,
sum(case when fd.TSStatus='Approved' and Status!=0 then 1 else 0 end) Approved,
sum(case when fd.TSStatus='Pending' and fd.Status=0 then 1 else 0 end) Pending,
from FarmerDetail as fd
2> select count(distinct FarmerRegNo) as [Approved] from FarmerDetail where TSStatus='Approved'
see the image i want to get the 2nd table value in first table approved column how to remove duplicates for this condition
Is this what you want?
select
count(distinct fd.FarmerRegNo) as TotalRecord,
count(distinct case when fd.TSStatus = 'Approved' and Status != 0 then fd.FarmerRegNo end) Approved,
count(distinct case when fd.TSStatus = 'Pending' and Status = 0 then fd.FarmerRegNoend) Pending,
from FarmerDetail as fd
Use a conditional count(distinct):
select count(fd.FarmerRegNo) as TotalRecord,
count(distinct case when fd.TSStatus = 'Approved' and fd.Status <> 0 then fd.FarmerRegNo end) as Approved,
count(distinct case when fd.TSStatus = 'Pending' and fd.Status = 0 then fd.FarmerRegNo end) as Pending,
from FarmerDetail fd ;
Use the below query with sub query
select count(fd.FarmerRegNo) as TotalRecord,
(select count(distinct FarmerRegNo) from FarmerDetail where TSStatus='Approved') as Approved,
sum(case when fd.TSStatus='Pending' and fd.Status=0 then 1 else 0 end) Pending,
from FarmerDetail as fd

Deriving values based on result of the query and grouping the data

I am writing a sql where I am trying to pull out information of the status of the courses the user has enrolled. I am trying to return single record for each user. Two fields in the select list would derive the value in the following manner
CourseResultStatusId -
If the status of all the courses is passed then return the status as passed otherwise.
If any status is fail, the overall status is fail.
If any of the status is expired, then overall status is expired.
If any of the status is in-progress then overall status is in-progress
ExpiryDateTime - Training expiring (nearest date)
I need to apply the following logic on courses he has been assigned.
cr.ExpiryDateTime > GetDate() and cr.ExpiryDateTime <= dateadd(dd,30,getdate()) )
If you see below , the query I have written so far pulls the courses that each user has been enrolled but it is not a cumulative result. Do I need to group, if yes would need help.
DECLARE #Rep1 INT;
SET #Rep1 = 13119;
SELECT
cr.[CourseID]
,cr.[UserID]
,u.[Code]
,u.[DisplayName]
,t.[Name]
,cr.[CourseResultStatusID] AS [CourseResultStatusID]
,crs.[Description] AS [CourseResultStatusDescription]
,c.[PointsRequired]
,cr.[ExpiryDateTime]
FROM [training].[CourseResult] cr
INNER JOIN [training].[Course] c
ON cr.[CourseID] = c.[ID] and c.[IsOptional] = 0 -- and cr.ExpiryDateTime > GetDate() and cr.ExpiryDateTime <= dateadd(dd,30,getdate())
INNER JOIN [training].[CourseResultStatus] crs
ON cr.[CourseResultStatusID] = crs.[ID]
INNER JOIN org.RepresentativeTierHistory rth on rth.RepresentativeID = cr.[UserID] and GetDate() between rth.StartDate and rth.EndDate
INNER JOIN org.tier t on t.ID = rth.TierID
LEFT JOIN [org].[User] u
ON u.[ID] = cr.[UserID]
WHERE cr.[UserID] IN (
SELECT hd.DescendantId FROM org.HierarchyDescendant hd WHERE hd.RepresentativeId = #Rep1 UNION ALL SELECT #Rep1 -- for management exchange info
)
order by UserID
The result of the query is as follows. I have circled to show you records that belong to a particular user and the columns that I am interested in . I need help in getting single record for each user based on the of logic that I mentioned above.
If I followed you correctly, you can implement the priorization rules on the overall result of each user using conditional aggregation.
Starting from your existing query, the logic would be:
select
cr.[UserID],
case
when min(case when crs.[Description] = 'Complete' then 1 else 0 end) = 1
then 'Complete'
when max(case when crs.[Description] = 'Fail' then 1 else 0 end) = 1
then 'Fail'
when max(case when crs.[Description] = 'Expired' then 1 else 0 end) = 1
then 'Expired'
when max(case when crs.[Description] = 'In Progress' then 1 else 0 end) = 1
then 'In Progress'
end as ResultStatus
from ...
where ...
group by cr.[UserID]
As for the date filtering logic, you should be able to implement it directly in the where clause.
It is possible that other parts of your query can be optimized - you might want to ask a new question for this, providing proper sample data and desired results.

GROUP BY SUM CASE expression

I want to group by account number, but I am running into problems if I get multiple RATE_CD's for an account - I get a NONCOMPLIANT_CNT of 2, but I want it to be only 1 per account even if there is more than 1 RATE_CD.
Below is the SQL I'm playing around with, any ideas on how I can return the NONCOMPLIANT_CNT per account, and not roll up the count if there is more than 1 RATE_CD?
SELECT ID
,ACCOUNT_NBR SUM(CASE
WHEN GROUP_CD = 'RED'
AND TYPE_CD IN ('CHK')
THEN 1
ELSE 0
END) AS 'COMPLIANT_CNT'
,SUM(CASE
WHEN GROUP_CD = 'RED'
AND TYPE_CD IN (
'CN'
,'RN'
)
AND RATE_CD <> 'BLK'
THEN 1
ELSE 0
END) AS 'NONCOMPLIANT_CNT'
,SUM(CASE
WHEN GROUP_CD = 'RED'
AND TYPE_CD IN (
'CN'
,'RN'
,'CHK'
)
THEN 1
ELSE 0
END) AS 'TOTAL_CNT'
FROM DETAIL
LEFT OUTER JOIN RATE_LOOKUP ACCOUNT_NBR = ACCOUNT_NBR
GROUP BY ID
,ACCOUNT_NBR
,RATE_CD
If you only want 1 instead of how many actual, change your SUM() to MAX(). So if they have 5 entries, it would still show as at least 1, otherwise will be 0 for the given column aggregate.

select query result filter using if conditions

I have folllowing select query
SELECT
Table.ID
SUM(CASE WHEN Table.Status = 1 THEN 1 ELSE null END) AS NormalCount,
SUM(CASE WHEN Table.status = 2 THEN 1 ELSE null END) AS AbnormalCount
FROM Table
GROUP BY Table.ID
I want to get above results and generate new result set with following conditions
IF(NormalCount > 0 or AbnormalCount == NULL)
SELECT
Table.ID
Table.Status AS "Normal"
FROM Table
GROUP BY Table.ID
ELSE IF ( AbnormalCount > 0)
SELECT
Table.ID
Table.Status AS "Abnormal"
SUM(CASE WHEN Header.status = 2 THEN 1 ELSE null END) AS AbnormalCount
FROM Table
GROUP BY Table.ID
I think the logic you want is to label each ID group as being abnormal if it has one or more abnormal observation. If so, then you can use another CASE statement to check the conditional abnormal sum and label the status appropriately. Normal groups would have the characteristic of having an abnormal count of zero, but this count would appear for all groups.
SELECT t.ID,
CASE WHEN SUM(CASE WHEN t.status = 2 THEN 1 ELSE 0 END) > 0
THEN "Abnormal"
ELSE "Normal" END AS Status,
SUM(CASE WHEN t.status = 2 THEN 1 ELSE 0 END) AS AbnormalCount
FROM Table t
GROUP BY t.ID

case statement doesn't go to else

I am wondering why the following query doesn't give 'N/A' when there are no rows for ENVIRON='Dev/Int'. It is returning null in the result of the query. I tried doing NVL(COUNT(*)) but that does't work either.
Any thoughts?
Thanks in advance.
SELECT G1.NAME,
(SELECT CASE
WHEN COUNT(*) > 0 AND ticket IS NOT NULL THEN 'Solved'
WHEN COUNT(*) > 0 AND ticket IS NULL THEN 'Done'
ELSE 'N/A'
END
FROM TABLE1
WHERE ENVIRON='Dev/Int' AND G1.NAME=NAME GROUP BY ENVIRON, ticket ) "Dev/Int"
FROM TABLE1 G1 group by G1.NAME
It doesn't give any rows because you are filtering them all out. The case is inside the query. When there are no rows to process, it returns NULL.
I think you just want conditional aggregation. The subqueries don't seem necessary:
SELECT G1.NAME,
(CASE WHEN SUM(CASE WHEN ENVIRON = 'Dev/Int' then 1 else 0 END) > 0 AND ticket IS NOT NULL
THEN 'Solved'
WHEN SUM(CASE WHEN ENVIRON = 'Dev/Int' then 1 else 0 END) > 0 AND ticket IS NULL
THEN 'Done'
ELSE 'N/A'
END) as "Dev/Int"
FROM TABLE1
group by G1.NAME;
EDIT:
Oops, the above left ticket out of the sum(). I think the logic you want has ticket in the sum() condition:
SELECT G1.NAME,
(CASE WHEN SUM(CASE WHEN ENVIRON = 'Dev/Int' AND ticket IS NOT NULL then 1 else 0 END) > 0
THEN 'Solved'
WHEN SUM(CASE WHEN ENVIRON = 'Dev/Int' AND ticket IS NULL then 1 else 0 END) > 0
THEN 'Done'
ELSE 'N/A'
END) as "Dev/Int"
FROM TABLE1
group by G1.NAME;
I'm surprised your original query worked at all and didn't get an error of the sort that subquery returned more than one row.