Are there way/s for me to extract data that only contain a certain values in sql? - sql

Are there way/s for me to extract data that only contain a certain values.
Ex:
Contact Asset Status
AB 1 Cancelled
AB 2 Cancelled
AB 3 Cancelled
AB 4 Cancelled
CD 5 Cancelled
CD 6 Active
CD 7 Cancelled
CD 8 Active
What I want to get are only those contacts that does contain cancelled assets ONLY (like Contact AB). And not those with both cancelled and active assets (like Contact CD).

You can do this with group by and a having clause:
select contact
from table t
group by contact
having min(status) = 'Cancelled' and max(status) = 'Cancelled';
This works for the data in your example. If status could be NULL and you want to count that as a different value, then the logic would be slightly more complicated.

The pure relational logic way is easier to understand, but will perform less well, requiring some kind of join to work. Let's satisfy the conditions
There is at least one status Cancelled for each Contact
But there are 0 statuses for that same Contact that aren't Cancelled
in a query like so:
SELECT DISTINCT
CS.Contact
FROM
ContactStatus CS
WHERE
CS.Status = 'Cancelled' -- at least one cancelled
AND NOT EXISTS ( -- but there are none of...
SELECT *
FROM ContactStatus CS2 -- contacts in the same table
WHERE
CS.Contact = CS2.Contact -- for that same contact
AND CS.Status <> 'Cancelled' -- that aren't cancelled
)
;
But we can do this with an aggregate, that will take only a single scan of the table, by using a little thought:
SELECT
Contact
FROM
ContactStatus
GROUP BY
Contact
HAVING
Count(*) = Count(CASE WHEN Status = 'Cancelled' THEN 1 END)
;
Other aggregate expressions in the HAVING clause are possible, such as:
Count(CASE WHEN Status <> 'Cancelled' THEN 1 END) = 0 -- or Min()
Min(Status) = 'Cancelled' AND Max(Status) = 'Cancelled'
Max(CASE WHEN Status = 'Cancelled' THEN 0 ELSE 1 END) = 0
Sum(SELECT 1 WHERE Status <> 'Cancelled') = 0
All of these would do the trick in this case; pick the one that makes the most sense to you.

Select contact from contacts a
where a.status = 'Canceled' AND contact = 'AB'

Related

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.

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

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

SQL: get a list of accounts that have "Work requests" that have not been completed

I'm tring to come up with a query that gets a list of accounts that have "Work requests" that have not been completed.
My query below gets me all of the accounts with "work request" "received" and "completed" but I only want the ones with received more times the completed.
SELECT n.incident_no,
e.event_sub_type,
COUNT (*) qty
FROM tb_event e,
tb_incident n,
vius_def d
WHERE e.incident_id = n.incident_id
AND n.incident_id = d.incident_id
AND EXISTS
(SELECT 0
FROM TB_EVENT e
WHERE e.event_type = 'wrk req'
AND e.event_sub_type = 'received'
AND e.incident_id = n.incident_id)
AND EXISTS
(SELECT 0
FROM TB_EVENT e
WHERE e.event_type = 'wrk req'
AND e.event_sub_type IN ('received',
'completed')
AND e.incident_id = n.incident_id)
AND e.event_type = 'wrk req'
AND d.incident_status_pd='o'
GROUP BY n.incident_no,
e.event_sub_type
ORDER BY n.incident_no
This is the current answer to the query above
------------------------------------
incident_no event_sub_type qty
------------------------------------
2008099999 COMPLETED 2
2008099999 RECEIVED 1
2013123456 RECEIVED 1
2014141414 COMPLETED 1
2014141414 RECEIVED 2
2016111111 RECEIVED 1
I only want to ones that have received more times than completed
This is what I would like as the answer to the query above
------------------------------------
incident_no event_sub_type qty
------------------------------------
2013123456 RECEIVED 1
2014141414 RECEIVED 2
2016111111 RECEIVED 1
I think this is another case where using old style joins make it harder to understand what is actually going on -- as you can see the sub-query here is functionally the same as yours -- but much simpler (and by simpler I mean simpler to read) without both the EXISTS. Then using it as a sub-query to compare the totals is trivial.
SELECT incident_no, 'RECEIVED' as event_sub_type, R_COUNT as qty
FROM
(
SELECT n.incident_no,
e.event_sub_type,
SUM(CASE WHEN lower(e.event_sub_type) = 'recieved' THEN 1 ELSE 0 END) AS R_COUNT,
SUM(CASE WHEN lower(e.event_sub_type) = 'completed' THEN 1 ELSE 0 END) AS C_COUNT
FROM tb_event e
JOIN tb_incident n ON e.incident_id = n.incident_id AND lower(e.event_sub_type) IN ('received', 'completed')
JOIN vius_def d ON n.incident_id = d.incident_id
WHERE e.event_type = 'wrk req' AND d.incident_status_pd='o'
GROUP BY n.incident_no
) X
WHERE R_COUNT > C_COUNT

How can I write this select query in SQL Server?

I need to extract some data to analyse exceptions/logs, and I'm stuck at a point.
I have a table with a column called CallType, and a status which can be Success or Failure. This table also has a column called SessionId.
I need to do this:
Select all the SessionId's where all the CallType = 'A' are marked as Success, but there is at least one CallType = 'B' having a Failure for that session.
There will be a where clause to filter out some stuff.
I'm thinking something like:
select top 10 *
from Log nolock
where ProviderId=48 -- add more conditions here
group by SessionId
having --? what should go over here?
I would do this with conditional aggregation in the having clause:
select top 10 *
from Log nolock
where ProviderId=48 -- add more conditions here
group by SessionId
having sum(case when CallType = 'A' and Status = 'Failure' then 1 else 0 end) = 0 and
sum(case when CallType = 'B' and Status = 'Failure' then 1 else 0 end) > 0 and
sum(case when CallType = 'A' and Status = 'Success' then 1 else 0 end) > 0;
The having clause checks for three conditions by counting the number of rows that meet each one. If = 0, then no records are allowed. If > 0 then records are required.
That CallType A has no failures.
That CallType B has at least one failure.
That at least one CallType A success exists.
The third condition is ambiguous -- if is not clear if you actually need CallType As to be in the data, based on the question.
SELECT *
FROM Log L WITH(NOLOCK)
WHERE L.CallType='A'
AND L.[Status] = 'Success'
AND L.ProviderId = 48
AND EXISTS (SELECT 1
FROM Log
WHERE L.SessionID = SessionID
AND CallType='B'
AND [Status] = 'Failure')
Having clause can only operate on aggregates within the group so this isn't the correct way to go about it since you are filtering out other rows you want to check against. I'd use EXISTS for this e.g.
edit: corrected the query
SELECT *
FROM Log L WITH(NOLOCK)
WHERE ProviderId = 48
AND CallType = 'A'
AND Status = 'Success'
AND EXISTS(SELECT * FROM Log WHERE L.SessionId = SessionId AND CallType = 'B' AND Status = 'Failure')
You can essentially filter out rows in the EXISTS part of the query using the aliased Log table (aliased L), matching all rows with the same session ID and seeing if any match the filters you required (failed with call type B)

Count over different tables

I want to make a blacklist for my system. When someone cancelled 3 times he must be blacklisted. So a reservation has a booker_id so I have to count all this booker id's with the status canceled. The status canceled is in the other column reservation_status.
Can someone help me with this query please? I have something like this but don't know if it is in the good way
select count(case
when reservation_status = 'Canceled' then 1
else null end) as booker_id
select booker_id, count(reservation_status)
from YourTable
where reservation_status = 'Canceled'
group by booker_id
having count(reservation_status) >= 3