Check multiple case expressions result at once - sql

I have a case expression inside an aggregate function in a select statement that looks something like this.
select person_id,
sum(case status = 'approved' then hours else 0.0 end) as hours
sum(case status = 'cancelled' then void_hrs else 0.0 end) as void_hrs
sum(case status = 'forwarded' then fwd_hrs else 0.0 end) as fwd_hrs
from table
Now, how do I check if all of the cases returns 0.0? So that I can exclude it on the result set?

I would just add a where clause:
select person_id,
sum(case status = 'approved' then hours else 0.0 end) as hours
sum(case status = 'cancelled' then void_hrs else 0.0 end) as void_hrs
sum(case status = 'forwarded' then fwd_hrs else 0.0 end) as fwd_hrs
from table
where status in ('approved', 'cancelled', 'forwarded')
group by person_id;
As a bonus, this might improve performance if you have a lot of rows with other statuses.
Alternatively, you can add a having clause to your query:
having sum(case when status in ('approved', 'cancelled', 'forwarded') then 1 else 0 end) > 0

The simplest approach is probably to turn your query to a subquery, then filter in the outer query:
select *
from (
select person_id,
sum(case status = 'approved' then hours else 0.0 end) as hours
sum(case status = 'cancelled' then void_hrs else 0.0 end) as void_hrs
sum(case status = 'forwarded' then fwd_hrs else 0.0 end) as fwd_hrs
from mytable
group by person_id
) t
where hours + void_hrs + fwd_hrs > 0
Note that your original query was missing a group by clause, I added that.
The alternative would be to use a lengthy having clause:
select person_id,
sum(case status = 'approved' then hours else 0.0 end) as hours
sum(case status = 'cancelled' then void_hrs else 0.0 end) as void_hrs
sum(case status = 'forwarded' then fwd_hrs else 0.0 end) as fwd_hrs
from mytable
group by person_id
having sum(
case status
when 'approved' then hours
when 'cancelled' then void_hrs
when 'forwarded' then fwd_hrs
else 0.0
end
) > 0
Side note: this is called a case expression, not a case statement. The latter is a flow control structure, while the former is conditional logic.

Related

Exclude records that have sum greater than 1

I have query returning details of customers that are subscribed to channel xyz or all other channels.
To generate this results i am using the following query:
select customerID
,sum(case when channel='xyz' then 1 else 0 end) as 'xyz Count'
,sum(case when channel<>'xyz' then bundle_qty else 0 end) as 'Other'
From temptable
So my Question is, how do i Exclude customers that are subscribed to 2 channels, where one is xyz and one is another channel.
select customerID
,sum(case when channel='xyz' then 1 else 0 end) as 'xyz Count'
,sum(case when channel<>'xyz' then bundle_qty else 0 end) as 'Other'
From temptable
group by customerID
having sum(case when channel= 'xyz' then 1 else 0 end) > 0
and sum(case when channel<>'xyz' then 1 else 0 end) > 0
First, your query is not correct. It needs a group by. Second, you can do what you want using having:
select customerID,
sum(case when channel = 'xyz' then 1 else 0 end) as xyz_Count,
sum(case when channel<>'xyz' then bundle_qty else 0 end) as Other
From temptable
group by customerID
having count(*) = 2 and
sum(case when channel = 'xyz' then 1 else 0 end) = 1;
If customers can subscribe to the same channel multiple times, and you still want only "xyz" and another channel, then:
having count(distinct channel) = 2 and
(min(channel) = 'xyz' or max(channel) = 'xyz')

Full outer join with "case when" and subquery

I have a full outer join query with case when and sub query in oracle. What I am trying to accomplish is aggregating current year data and previous year data from the same table in order to compare them. However my FULL OUTER JOIN is acting as an inner join not returning the null values from both the current year and the previous year.
Here is my code:
SELECT
SQ1.CHANNEL,
SQ1.SHORT,
SQ1.NAME,
SQ1.RDC,
SQ1.CY_APPROVED_COUNT,
SQ2.PY_APPROVED_COUNT,
SQ1.CY_APPROVED_VOLUME,
SQ2.PY_APPROVED_VOLUME,
SQ1.CY_DECLINED_COUNT,
SQ2.PY_DECLINED_COUNT,
SQ1.CY_DECLINED_VOLUME,
SQ2.PY_DECLINED_VOLUME,
SQ1.CY_RETURNED_COUNT,
SQ2.PY_RETURNED_COUNT,
SQ1.CY_RETURNED_VOLUME,
SQ2.PY_RETURNED_VOLUME
FROM ( SELECT
CHANNEL,
SHORT,
NAME,
RDC,
SUM (CASE WHEN STATUS = 'Approved' THEN APP_COUNTS ELSE 0 END) AS CY_APPROVED_COUNT,
SUM (CASE WHEN STATUS = 'Approved' THEN PROJ_VOL ELSE 0 END) AS CY_APPROVED_VOLUME,
SUM (CASE WHEN STATUS = 'Declined' THEN APP_COUNTS ELSE 0 END) AS CY_DECLINED_COUNT,
SUM (CASE WHEN STATUS = 'Declined' THEN PROJ_VOL ELSE 0 END) AS CY_DECLINED_VOLUME,
SUM (CASE WHEN STATUS = 'Returned' THEN APP_COUNTS ELSE 0 END) AS CY_RETURNED_COUNT,
SUM (CASE WHEN STATUS = 'Returned' THEN PROJ_VOL ELSE 0 END) AS CY_RETURNED_VOLUME
FROM WFRT_MSP_SP_MTD
WHERE PERIOD >= TO_DATE('2016/02/01', 'yyyy/mm/dd')
AND PERIOD <= TO_DATE('2016/02/13','yyyy/mm/dd')
AND CHANNEL = 'MSP'
AND RDC = 'BASE'
GROUP BY
CHANNEL,
SHORT,
NAME,
RDC
) SQ1
-- NOT CORRECTLY SHOWING NULL VALUES
FULL OUTER JOIN
( SELECT
CHANNEL,
SHORT,
NAME,
RDC,
SUM (CASE WHEN STATUS = 'Approved' THEN APP_COUNTS ELSE 0 END) AS PY_APPROVED_COUNT,
SUM (CASE WHEN STATUS = 'Approved' THEN PROJ_VOL ELSE 0 END) AS PY_APPROVED_VOLUME,
SUM (CASE WHEN STATUS = 'Declined' THEN APP_COUNTS ELSE 0 END) AS PY_DECLINED_COUNT,
SUM (CASE WHEN STATUS = 'Declined' THEN PROJ_VOL ELSE 0 END) AS PY_DECLINED_VOLUME,
SUM (CASE WHEN STATUS = 'Returned' THEN APP_COUNTS ELSE 0 END) AS PY_RETURNED_COUNT,
SUM (CASE WHEN STATUS = 'Returned' THEN PROJ_VOL ELSE 0 END) AS PY_RETURNED_VOLUME
FROM WFRT_MSP_SP_MTD
WHERE PERIOD >= TO_DATE('2015/02/01', 'yyyy/mm/dd')
AND PERIOD <= TO_DATE('2015/02/13','yyyy/mm/dd')
AND CHANNEL = 'MSP'
AND RDC = 'BASE'
GROUP BY
CHANNEL,
SHORT,
NAME,
RDC
) SQ2
ON sq1.short = sq2.short
;
Please help if you can.
Just use conditional aggregation:
SELECT CHANNEL, SHORT, NAME, RDC,
SUM(CASE WHEN this_year = 1 AND STATUS = 'Approved' THEN APP_COUNTS ELSE 0 END) AS CY_APPROVED_COUNT,
SUM(CASE WHEN this_year = 1 AND STATUS = 'Approved' THEN PROJ_VOL ELSE 0 END) AS cY_APPROVED_VOLUME,
SUM(CASE WHEN this_year = 1 AND STATUS = 'Declined' THEN APP_COUNTS ELSE 0 END) AS CY_DECLINED_COUNT,
SUM(CASE WHEN this_year = 1 AND STATUS = 'Declined' THEN PROJ_VOL ELSE 0 END) AS CY_DECLINED_VOLUME,
SUM(CASE WHEN this_year = 1 AND STATUS = 'Returned' THEN APP_COUNTS ELSE 0 END) AS CY_RETURNED_COUNT,
SUM(CASE WHEN this_year = 1 AND STATUS = 'Returned' THEN PROJ_VOL ELSE 0 END) AS CY_RETURNED_VOLUME,
SUM(CASE WHEN prev_year = 1 AND STATUS = 'Approved' THEN APP_COUNTS ELSE 0 END) AS PY_APPROVED_COUNT,
SUM(CASE WHEN prev_year = 1 AND STATUS = 'Approved' THEN PROJ_VOL ELSE 0 END) AS PY_APPROVED_VOLUME,
SUM(CASE WHEN prev_year = 1 AND STATUS = 'Declined' THEN APP_COUNTS ELSE 0 END) AS PY_DECLINED_COUNT,
SUM(CASE WHEN prev_year = 1 AND STATUS = 'Declined' THEN PROJ_VOL ELSE 0 END) AS PY_DECLINED_VOLUME,
SUM(CASE WHEN prev_year = 1 AND STATUS = 'Returned' THEN APP_COUNTS ELSE 0 END) AS PY_RETURNED_COUNT,
SUM(CASE WHEN prev_year = 1 AND STATUS = 'Returned' THEN PROJ_VOL ELSE 0 END) AS PY_RETURNED_VOLUME
FROM (SELECT msm.*,
(CASE WHEN PERIOD >= DATE '2015-02-01' AND
PERIOD <= '2015-02-13'
THEN 1 ELSE 0
END) as prev_year,
(CASE WHEN PERIOD >= DATE '2016-02-01' AND
PERIOD <= '2016-02-13'
THEN 1 ELSE 0
END) as this_year
FROM WFRT_MSP_SP_MTD msm
) msm
WHERE CHANNEL = 'MSP' AND RDC = 'BASE'
GROUP BY CHANNEL, SHORT, NAME, RDC;

How to use having condition in SQL query

SELECT
userid,
CASE
WHEN (COUNT(CASE
WHEN onlinesportsgamewagers != 0
THEN 1
ELSE null
END)
+ COUNT(CASE
WHEN depositmade_amt != 0
THEN 1
ELSE null
END)) >= 10
THEN "VIP"
ELSE "NON-VIP"
END as VIPcheck
FROM
player_activity
WHERE
userid = 2023410
GROUP BY
year(txndate), month(txndate)
This query determines the user's VIP status for each month.
Ultimately, I want to have a query that determines if the user achieved VIP status for at least 3 months (including the current month). For the time being, it's only user 2023410, but eventually I want to run this for the whole database.
Therefore my ultimate output would be:
User - VIPcheck (3 different months w/ active status)
(one row per userID)
HAVING COUNT(CASE WHEN (COUNT(CASE WHEN onlinesportsgamewagers != 0
THEN 1
ELSE null
END)
+ COUNT(CASE WHEN depositmade_amt != 0
THEN 1
ELSE null
END)) >= 10
THEN 1
ELSE 0
END)
Tried the above having statement, but it didn't work. Any suggestions?
If I understand correctly, this gets the VIP status for one user by month:
SELECT userid, year(txndate), month(txndate),
(CASE WHEN SUM(CASE WHEN onlinesportsgamewagers <> 0 THEN 1 ELSE 0 END) +
SUM(CASE WHEN depositmade_amt <> 0 THEN 1 ELSE 0 END) >= 10
THEN 'VIP'
ELSE 'NON-VIP'
END) as VIPcheck
FROM player_activity
GROUP BY userid, year(txndate), month(txndate);
Another aggregation will get what you want:
SELECT userid,
(CASE WHEN SUM(VIPcheck = 'VIP') >= 3 THEN 'SUPER-VIP'
WHEN SUM(VIPcheck = 'VIP') >= 1 THEN 'VIP'
ELSE 'HOI POLLOI'
END) as status
FROM (SELECT userid, year(txndate), month(txndate),
(CASE WHEN SUM(CASE WHEN onlinesportsgamewagers <> 0 THEN 1 ELSE 0 END) +
SUM(CASE WHEN depositmade_amt <> 0 THEN 1 ELSE 0 END) >= 10
THEN 'VIP'
ELSE 'NON-VIP'
END) as VIPcheck
FROM player_activity
GROUP BY userid, year(txndate), month(txndate)
) uym
GROUP BY userid;

Sum data for many different results for same field

I am trying to find a better way to write this sql server code 2008. It works and data is accurate. Reason i ask is that i will be asked to do this for several other reports going forward and want to reduce the amount of code to upkeep going forward.
How can i take a field where i sum for the yes/no/- (dash) in each field without doing an individual sum as i have in code. Each table is a month of detail data which i sum using in a CTE. i changed the table name for each month and Union All to put data together. Is there a better way to do this. This is a small sample of code. Thanks for the help.
WITH H AS (
SELECT 'August' AS Month_Name
, SUM(CASE WHEN G.FFS = '-' THEN 1 ELSE 0 END) AS FFS_Dash
, SUM(CASE WHEN G.FFS = 'Yes' THEN 1 ELSE 0 END) AS FFS_Yes
, SUM(CASE WHEN G.FFS = 'No' THEN 1 ELSE 0 END) AS FFS_No
, SUM(CASE WHEN G.DNA = '-' THEN 1 ELSE 0 END) AS DNA_Dash
, SUM(CASE WHEN G.DNA = 'Yes' THEN 1 ELSE 0 END) AS DNA_Yes
, SUM(CASE WHEN G.DNA = 'No' THEN 1 ELSE 0 END) AS DNA_No
FROM table08 G )
, G AS (
SELECT 'July' AS Month_Name
, SUM(CASE WHEN G.FFS = '-' THEN 1 ELSE 0 END) AS FFS_Dash
, SUM(CASE WHEN G.FFS = 'Yes' THEN 1 ELSE 0 END) AS FFS_Yes
, SUM(CASE WHEN G.FFS = 'No' THEN 1 ELSE 0 END) AS FFS_No
, SUM(CASE WHEN G.DNA = '-' THEN 1 ELSE 0 END) AS DNA_Dash
, SUM(CASE WHEN G.DNA = 'Yes' THEN 1 ELSE 0 END) AS DNA_Yes
, SUM(CASE WHEN G.DNA = 'No' THEN 1 ELSE 0 END) AS DNA_No
FROM table07 G )
select * from H
UNION ALL
select * from G
How about:
SELECT Month_Name,
SUM(CASE WHEN G.FFS = '-' THEN 1 ELSE 0 END) AS FFS_Dash,
SUM(CASE WHEN G.FFS = 'Yes' THEN 1 ELSE 0 END) AS FFS_Yes,
SUM(CASE WHEN G.FFS = 'No' THEN 1 ELSE 0 END) AS FFS_No,
SUM(CASE WHEN G.DNA = '-' THEN 1 ELSE 0 END) AS DNA_Dash,
SUM(CASE WHEN G.DNA = 'Yes' THEN 1 ELSE 0 END) AS DNA_Yes,
SUM(CASE WHEN G.DNA = 'No' THEN 1 ELSE 0 END) AS DNA_No
FROM ((select 'July' as Month_Name, G.*
from table07 G
) union all
(select 'August', H.*
from table08 H
)
) gh
GROUP BY Month_Name;
However, having tables with the same structure is usually a sign of poor database design. You should have a single table with a column representing the month.

Order by the result of a division of two counts

I have a query like this one:
SELECT
type,
count(case when STATUS = 'N/A' then 1 end) as NOTAPPLICABLE,
count(case when STATUS = 'Failed' then 1 end) as FAILED,
count(case when STATUS = 'No Run' then 1 end) as NO_RUN,
count(case when STATUS = 'Not Completed' then 1 end) as NOT_COMPLETE,
count(case when STATUS = 'Blocked' then 1 end) as Blocked,
count(case when STATUS = 'Passed' then 1 end) as PASSED,
count(case when STATUS <> 'N/A' then 1 end) as TOTAL
FROM
table
GROUP BY
type
I want to order the results so the rows with the type with the highest percentage of passed is on top.
I though something like:
ORDER BY
"PASSED"/"TOTAL" DESC
But it's not working.
Do you have any idea to achieve this?
Thanks,
You can use expressions in ORDER BY
SELECT
type,
count(case when STATUS = 'N/A' then 1 end) as NOTAPPLICABLE,
count(case when STATUS = 'Failed' then 1 end) as FAILED,
count(case when STATUS = 'No Run' then 1 end) as NO_RUN,
count(case when STATUS = 'Not Completed' then 1 end) as NOT_COMPLETE,
count(case when STATUS = 'Blocked' then 1 end) as Blocked,
count(case when STATUS = 'Passed' then 1 end) as PASSED,
count(case when STATUS <> 'N/A' then 1 end) as TOTAL
FROM
table
GROUP BY
type
ORDER BY
count(case when STATUS = 'Passed' then 1 end)/count(case when STATUS <> 'N/A' then 1 end) desc
But this can produce division by zero exception you have to check if count(case when STATUS <> 'N/A' then 1 end) is not zero.
The other solution is using sub-queries - You enclose your initial query in sub-query and then you can order, limit or filter this sub-query as simple table in SQL
SELECT *
FROM (
SELECT
type,
count(case when STATUS = 'N/A' then 1 end) as NOTAPPLICABLE,
count(case when STATUS = 'Failed' then 1 end) as FAILED,
count(case when STATUS = 'No Run' then 1 end) as NO_RUN,
count(case when STATUS = 'Not Completed' then 1 end) as NOT_COMPLETE,
count(case when STATUS = 'Blocked' then 1 end) as Blocked,
count(case when STATUS = 'Passed' then 1 end) as PASSED,
count(case when STATUS <> 'N/A' then 1 end) as TOTAL
FROM
table
GROUP BY
type
) AS SUB_DATA
ORDER BY PASSED/TOTAL DESC
if you are using PostgreSQL you can use WITH construction (I very like it).
WITH _records as (
SELECT
type,
count(case when STATUS = 'N/A' then 1 end) as NOTAPPLICABLE,
count(case when STATUS = 'Failed' then 1 end) as FAILED,
count(case when STATUS = 'No Run' then 1 end) as NO_RUN,
count(case when STATUS = 'Not Completed' then 1 end) as NOT_COMPLETE,
count(case when STATUS = 'Blocked' then 1 end) as Blocked,
count(case when STATUS = 'Passed' then 1 end) as PASSED,
count(case when STATUS <> 'N/A' then 1 end) as TOTAL
FROM
table
GROUP BY
type
)
SELECT *
FROM _records
ORDER BY PASSED/TOTAL DESC
If Column aliases that are defined in the SELECT are then referenced in the ORDER BY they must be used on their own. Not in an expression.
You can use a derived table.
SELECT *
FROM
(
/* Your Query here*/
) T
ORDER BY PASSED/TOTAL DESC
You may also need to cast PASSED to numeric to avoid integer division depending on your DBMS.
SELECT *, (PASSED / TOTAL) [percent] FROM
( SELECT
type,
count(case when STATUS = 'N/A' then 1 end) as NOTAPPLICABLE,
count(case when STATUS = 'Failed' then 1 end) as FAILED,
count(case when STATUS = 'No Run' then 1 end) as NO_RUN,
count(case when STATUS = 'Not Completed' then 1 end) as NOT_COMPLETE,
count(case when STATUS = 'Blocked' then 1 end) as Blocked,
count(case when STATUS = 'Passed' then 1 end) as PASSED,
count(case when STATUS <> 'N/A' then 1 end) as TOTAL
FROM
table
GROUP BY
type ) T
ORDER BY [percent]
There are two problems in your approach:
As others already pointed out, you cant' use column aliases in
calculation. Instead of ORDER BY PASSED/TOTAL DESC, write ORDER
BY count(case when STATUS = 'Passed' then 1 end) / count(case when
STATUS <> 'N/A' then 1 end)
If you divide PASSED by TOTAL, and PASSED is less than TOTAL, you'll
always get 0 as a result. Just like select 5/10 will return 0
instead of 0.5 - because both values are integers, you'll get integer as a result. select 1.0*5/10 will return 0.5
your code works in sql server, but not in oracle i think. try:
SELECT
type,
count(case when STATUS = 'N/A' then 1 end) as NOTAPPLICABLE,
count(case when STATUS = 'Failed' then 1 end) as FAILED,
count(case when STATUS = 'No Run' then 1 end) as NO_RUN,
count(case when STATUS = 'Not Completed' then 1 end) as NOT_COMPLETE,
count(case when STATUS = 'Blocked' then 1 end) as Blocked,
count(case when STATUS = 'Passed' then 1 end) as PASSED,
count(case when STATUS <> 'N/A' then 1 end) as TOTAL,
count(case when STATUS = 'Passed' then 1 end) / count(case when STATUS <> 'N/A' then 1 end) as sort
FROM
table
GROUP BY
type
ORDER BY 9
sort DESC