how to return a zero in sql instead of no row selected using case when - sql

If I query a output that doesn't exist then I will get nothing returned. i'm looking for default (0) is returned in that scenario
select sum(case when a2.status='SUCCESS' THEN A2.a else 0 end) as success,
sum(case when a2.status='FAILED' THEN A2.a else 0 end) as failed,
sum(case when a2.status='ERROR' THEN A2.a else 0 end) as error
from
(select a.stauts,count(1) a
from table1 a,table2 b
where a.id=b.id
a.date=sysdate
group by a.status)a2;
Note: There is no records for sysdate. I required default value "0" should be return for status.

This query should always return one row, even if nothing matches:
select sum(case when a.status = 'SUCCESS' then 1 else 0 end) as success,
sum(case when a.status = 'FAILED' then 1 else 0 end) as failed,
sum(case when a.status = 'ERROR' then 1 else 0 end) as error
from table1 a join
table2 b
on a.id = b.id
where a.date = trunc(sysdate);
Note that I changed the where logic. sysdate (despite its name) has a time component. If date has a time component, you may want:
where a.date >= trunc(sysdate) and a.date < trunc(sysdate + 1)
EDIT:
If the filter condition matches no rows, then you will get 0 using:
select count(case when a.status = 'SUCCESS' then 1 end) as success,
count(case when a.status = 'FAILED' then 1 end) as failed,
count(case when a.status = 'ERROR' then 1 end) as error
from table1 a join
table2 b
on a.id = b.id
where a.date = trunc(sysdate);

You could generate missing values:
WITH cte AS (
select a.status,count(1) a
from table1 a --JOIN syntax
join table2 b
on a.id=b.id
WHERE a.date=sysdate -- are you sure you want precision with time?
group by a.status
), placeholder AS (
SELECT *
FROM cte
UNION ALL
SELECT *
FROM (SELECT 'SUCCESS' AS status, 0 AS a FROM dual UNION ALL
SELECT 'ERROR', 0 FROM dual UNION ALL
SELECT 'FAILED', 0 FROM dual) p
WHERE NOT EXISTS (SELECT * FROM cte WHERE cte.status = p.status)
)
SELECT
sum(case when status='SUCCESS' THEN a else 0 end) as success,
sum(case when status='FAILED' THEN a else 0 end) as failed,
sum(case when status='ERROR' THEN a else 0 end) as error
FROM placeholder;

The only suggestion which comes to mind would be to use a left join in your subquery and move the entire WHERE logic to the ON clause:
SELECT
SUM(CASE WHEN a2.status = 'SUCCESS' THEN A2.a ELSE 0 END) AS success,
SUM(CASE WHEN a2.status = 'FAILED' THEN A2.a ELSE 0 END) AS failed,
SUM(CASE WHEN a2.status = 'ERROR' THEN A2.a ELSE 0 END) AS error
FROM
(
SELECT a.status, COUNT(1) a
FROM table1 a
LEFT JOIN table2 b
ON a.id = b.id AND
a.date = SYSDATE
GROUP BY a.status
) a2;
Your current query is using archaic join syntax which makes it hard to see what is actually happening. In particular, it makes it hard to see whether or not you might be discarding information during the join which you wish to retain.

If you use COUNT(), you don't need NVL() or COALESCE() to handle NULLs ,unlike the case for SUM(). COUNT() will always return a row with value=0 when the argument is NULL or when no rows are matched.GROUP BY too wouldn't be required.
SELECT COUNT(CASE WHEN a.status = 'SUCCESS' THEN 1 END) AS success,
COUNT(CASE WHEN a.status = 'FAILED' THEN 1 END) AS failed,
COUNT(CASE WHEN a.status = 'ERROR' THEN 1 END) AS error
FROM table1 a
JOIN table2 b ON a.id = b.id
WHERE a.date = TRUNC(SYSDATE);
If you just want to be clear, test these queries and pay attention to the result.
select SUM(1) FROM DUAL WHERE 1=0; --NULL
select SUM(NULL) FROM DUAL WHERE 1=0; --NULL
select SUM(NULL) FROM DUAL WHERE 1=1; --NULL
select COUNT(1) FROM DUAL WHERE 1=0; -- 0
select COUNT(NULL) FROM DUAL WHERE 1=0; -- 0
select COUNT(NULL) FROM DUAL WHERE 1=1; -- 0
Demo

Aggregation without GROUP BY always returns a row, so your existing query will return NULLs.
To change a NULL to zero simply apply COALESCE:
select
coalesce(sum(case when a2.status='SUCCESS' THEN A2.a end), 0) as success,
coalesce(sum(case when a2.status='FAILED' THEN A2.a end), 0) as failed,
coalesce(sum(case when a2.status='ERROR' THEN A2.a end), 0) as error
from
(
select a.status,count(1) a
from table1 a join table2 b
on a.id=b.id
where a.date=sysdate
group by a.status
) a2;

If I wanted to ensure there is always a result even for a query that wouldn't find any row to return, I would do a left join on dual table (for oracle):
select q.* FROM DUAL d LEFT JOIN ( your_query )q on 1=1
This way you will always get back a row, no matter what!

Related

SQL multiple data count from multiple tables with union all

My existing SQL looks like the below, it will generate only the total_pending_req count.
SELECT count(table1.employee_code) as total_requests, table1.employee_code as emp_code
FROM table1
WHERE employee_status = 'PENDING'
GROUP BY emp_code
UNION ALL
SELECT count(table2.employee_code) as total_requests, table2.employee_code as emp_code
FROM table2
WHERE employee_status = 'PENDING'
GROUP BY emp_code
UNION ALL
SELECT count(table3.employee_code) as total_requests, table3.employee_code as emp_code
FROM table3
WHERE employee_status = 'PENDING'
GROUP BY emp_code
This will return the result below,
I want to get the request count as total_pending_req, total_rejected_req and total_completed_req considering 3 different tables. All tables have the same status codes, PENDING, COMPLETED and REJECTED. The final result should be like this,
I would like to have an idea, of how to extract data more efficiently, since I have to use UNION ALL. May I know if there is any better approach to extract data more efficiently? I would appreciate your help on this.
The fact that your rows appear in the
several table{1,2,3} relations is just
an annoying distraction.
Let's make it a single relation, already.
We could create a table or a view.
CREATE VIEW combined AS
(SELECT * FROM table1
UNION ALL
SELECT * FROM table2
UNION ALL
SELECT * FROM table3
UNION ALL
)
Good!
With that out of the way, it becomes a trivial GROUP BY.
SELECT employee_code, employee_status, COUNT(*)
FROM combined
GROUP BY employee_code, employee_status
You can phrase it as a
CTE
if you're averse to DDL.
Or create a VIEW that does the UNION ALL heavy lifting.
Or a MATERIALIZED VIEW, whatever.
The output format is three emps × three statuses,
or nine rows.
If you really need three rows, feel free to SELECT
from that relation to re-format it.
you can use CASE and Union All as follows
select
table1.employee_code as emp_code,
case employee_status = 'PENDING' then count(table1.employee_code) else 0 end as PENDING,
case employee_status = 'COMPLETED' then count(table1.employee_code) else 0 end as COMPLETED,
case employee_status = 'REJECTED' then count(table1.employee_code) else 0 end as REJECTED
from FROM table1
GROUP BY emp_code
UNION ALL
select
table2.employee_code as emp_code,
case employee_status = 'PENDING' then count(table2.employee_code) else 0 end as PENDING,
case employee_status = 'COMPLETED' then count(table2.employee_code) else 0 end as COMPLETED,
case employee_status = 'REJECTED' then count(table2.employee_code) else 0 end as REJECTED
from FROM table2
GROUP BY emp_code
UNION ALL
select
table3.employee_code as emp_code,
case employee_status = 'PENDING' then count(table3.employee_code) else 0 end as PENDING,
case employee_status = 'COMPLETED' then count(table3.employee_code) else 0 end as COMPLETED,
case employee_status = 'REJECTED' then count(table3.employee_code) else 0 end as REJECTED
from FROM table3
GROUP BY emp_code
Or using SUM and Case
SELECT
table1.employee_code as emp_code,
SUM(CASE employee_status = 'PENDING' Then 1 Else 0 End ) as PENDING,
SUM(CASE employee_status = 'COMPLETED' Then 1 Else 0 End ) as COMPLETED,
SUM(CASE employee_status = 'REJECTED' Then 1 Else 0 End ) as REJECTED
from FROM table1
GROUP BY emp_code
UNION ALL
SELECT
table2.employee_code as emp_code,
SUM(CASE employee_status = 'PENDING' Then 1 Else 0 End ) as PENDING,
SUM(CASE employee_status = 'COMPLETED' Then 1 Else 0 End ) as COMPLETED,
SUM(CASE employee_status = 'REJECTED' Then 1 Else 0 End ) as REJECTED
from FROM table2
GROUP BY emp_code
UNION ALL
SELECT
table3.employee_code as emp_code,
SUM(CASE employee_status = 'PENDING' Then 1 Else 0 End ) as PENDING,
SUM(CASE employee_status = 'COMPLETED' Then 1 Else 0 End ) as COMPLETED,
SUM(CASE employee_status = 'REJECTED' Then 1 Else 0 End ) as REJECTED
from FROM table3
GROUP BY emp_code

Oracle stored procedure with function ORA-08103

I have a stored procedure that has a function call, and I get the below errors after about 10 mins of it running:
[Error] Execution (1: 1):
ORA-08103: object no longer exists
ORA-06512: at "CRYSTAL_REPORTS.FT_PAYCOM_ASOF", line 141
ORA-06512: at "CRYSTAL_REPORTS.PROC_DASHQS_PRODUCTION", line 26
ORA-06512: at line 2
However the function does exist and works as expected. Stripping down the query returns results, so I am concerned that complexity may be the cause. I appreciate any help, below is the procedure:
select
to_char(dt.dt,'YYYYMM') yearmonth,
ud.user_id,
CRYSTAL_REPORTS.FT_PAYCOM_ASOF(ud.user_eecode, dt.dt, 'DEPT') department,
CRYSTAL_REPORTS.FT_PAYCOM_ASOF(ud.user_eecode, dt.dt, 'PDEPT') par_department,
sum(case when clm.event_desc = 'NEW-OPEN' then 1 else 0 end) new_claim,
sum(case when clm.event_desc in ('INITIAL-CLOSE', 'RECLOSE', 'VOID') then 1 else 0 end) close_claim,
sum(case when clm.event_desc ='REOPEN' then 1 else 0 end) reopen_claim,
sum(case when clm.event_desc ='TRANSFER-IN' then 1 else 0 end) trans_in_claim,
sum(case when clm.event_desc ='TRANSFER-OUT' then 1 else 0 end) trans_out_claim,
sum(case when res.event_desc ='NEW-OPEN' then 1 else 0 end) new_res,
sum(case when res.event_desc in ('INITIAL-CLOSE','RECLOSE','VOID') then 1 else 0 end) close_res,
sum(case when res.event_desc ='REOPEN' then 1 else 0 end) reopen_res,
sum(case when res.event_desc ='TRANSFER-IN' then 1 else 0 end) trans_in_res,
sum(case when res.event_desc ='TRANSFER-OUT' then 1 else 0 end) trans_out_res,
sum(clm_wh.pending) pending_claims,
sum(res_wh.pending) pending_reserves
from
(select "DATE" dt from CRYSTAL_REPORTS.MV_CALENDAR_MONTHDATE) dt
cross join
crystal_reports.user_director ud
left join
CRYSTAL_REPORTS.MV_PROD_CLM_EVENT clm on clm.USER_ID = ud.USER_ID and to_char(clm.event_date,'YYYYMM') = to_char(dt.dt,'YYYYMM')
left join
CRYSTAL_REPORTS.MV_PROD_RES_EVENT res on res.USER_ID = ud.USER_ID and to_char(res.event_date,'YYYYMM')=to_char(dt.dt,'YYYYMM')
left join
crystal_reports.TBL_CLAIM_PROD_WH clm_wh on clm_wh.ADJUSTER=ud.user_id and clm_wh.type='MONTH' and to_char(dt.dt,'YYYYMM')= clm_wh.datadate
left join
crystal_reports.TBL_FEAT_PROD_WH res_wh on res_wh.ADJUSTER=ud.user_id and res_wh.type='MONTH' and to_char(dt.dt,'YYYYMM')= res_wh.datadate
where
to_char(dt.dt,'YYYYMMDD') = 20210901
and ud.user_id not like '%TEST%'
group by
to_char(dt.dt,'YYYYMM'), ud.user_id,
CRYSTAL_REPORTS.FT_PAYCOM_ASOF(ud.user_eecode, dt.dt, 'DEPT'),
CRYSTAL_REPORTS.FT_PAYCOM_ASOF(ud.user_eecode, dt.dt, 'PDEPT')
The function goes through several IF statements, and ends up using:
SELECT upper(case when uc_dept.detaildesc is null and orig_dept.detaildesc is null then upper(pext_dept.detaildesc) else upper(nvl(uc_dept.detaildesc,orig_dept.detaildesc)) end)
INTO xout_val
FROM crystal_reports.API_PAYCOM_USER_EXTENDED pext
left join crystal_reports.API_PAYCOM_USER_CHANGES uc on pext.EECODE = uc.EECODE and changedesc='PAF: Department Change' and (to_date(substr(changetime, 1,10), 'yyyy-mm-dd')) <= asof
left join crystal_reports.api_paycom_category pext_dept on pext_dept.detailcode=pext.DEPARTMENT_CODE
left join crystal_reports.api_paycom_category uc_dept on uc_dept.DETAILCODE=uc.new_value
left join (select eecode, orig_value,rn
from
(
select eecode,old_value orig_value, row_number() over (partition by eecode order by (to_date(substr(changetime, 1,10), 'yyyy-mm-dd')) asc) rn
from
crystal_reports.API_PAYCOM_USER_CHANGES orig_val
where changedesc='PAF: Department Change'
)
) orig_val on pext.eecode=orig_val.eecode and orig_val.rn=1
left join crystal_reports.api_paycom_category orig_dept on orig_dept.detailcode=orig_val.orig_value
where
acct=pext.eecode
order by (to_date(substr(changetime, 1,10), 'yyyy-mm-dd')) desc
FETCH NEXT 1 ROWS ONLY
It is very hard to remote diagnose such an error but in my experience the most likely cause of this error is another process/user that has removed the object since the operation has begun.
Alternatively there are also some Oracle bugs related to this error and you might want to check your alert log and eventually ask Oracle for help using MOS.

Nested CASE statements in SQL

I am running the below SQL and I need to add a case statement for the svcState column.
I have a value defined for each number in that column which I need to have in my query. For instance 7 is OK, 4 is down etc. I tried adding this in the CASE statement as below and it seems, the syntax is incorrect. Any help will be greatly appreciated.
SELECT * FROM
(
SELECT
A.NodeName AS NodeName,
MAX(CASE WHEN Poller_Name='svcServiceName' THEN CAST(Status AS varchar) ELSE ''END) svcServiceName,
MAX(CASE (CASE WHEN Poller_Name='svcState' AND Status ='7' THEN 'OK'
WHEN Poller_Name='svcstate' AND Status ='4' THEN 'OUT OF SERVICE' END)
THEN CAST(Status AS bigint) ELSE '' END) svcState
FROM
(
SELECT
Nodes.Caption AS NodeName, CustomNodePollers_CustomPollers.UniqueName AS Poller_Name, CustomNodePollerStatus_CustomPollerStatus.Status AS Status, CustomNodePollerStatus_CustomPollerStatus.rowid as row, CustomNodePollerStatus_CustomPollerStatus.RawStatus as RawStatus
FROM
((Nodes INNER JOIN CustomPollerAssignment CustomNodePollerAssignment_CustomPollerAssignment ON (Nodes.NodeID = CustomNodePollerAssignment_CustomPollerAssignment.NodeID)) INNER JOIN CustomPollers CustomNodePollers_CustomPollers ON (CustomNodePollerAssignment_CustomPollerAssignment.CustomPollerID = CustomNodePollers_CustomPollers.CustomPollerID)) INNER JOIN CustomPollerStatus CustomNodePollerStatus_CustomPollerStatus ON (CustomNodePollerAssignment_CustomPollerAssignment.CustomPollerAssignmentID = CustomNodePollerStatus_CustomPollerStatus.CustomPollerAssignmentID)
WHERE
(
(CustomNodePollers_CustomPollers.UniqueName = 'svcServiceName') OR
(CustomNodePollers_CustomPollers.UniqueName = 'svcState')
)
AND
(
(CustomNodePollerAssignment_CustomPollerAssignment.InterfaceID = 0)
)
and Nodes.Caption = '101'
)A
GROUP BY NodeName, row
--ORDER BY svcServiceName
) B
Desired Output
MAX(CASE WHEN Poller_Name = 'svcState' THEN (CASE WHEN status = '7' THEN 'OK' ELSE 'DOWN' END) END)
Or...
MAX(CASE WHEN Poller_Name = 'svcState' AND status = '7' THEN 'OK'
WHEN Poller_Name = 'svcState' AND status = '4' THEN 'DOWN' END)
Or...
MAX(CASE WHEN Poller_Name != 'svcState' THEN NULL -- Assumes the poller_name is never NULL
WHEN status = '7' THEN 'OK'
WHEN status = '4' THEN 'DOWN'
END)
Where there is no ELSE specified, it is implicitly ELSE NULL, and NULL values are skipped by the MAX().

HAVING gives me "column...does not exist" but I see the column

This is a practice question from stratascratch and I'm literally stuck at the final HAVING statement.
Problem statement:
Find the total number of downloads for paying and non-paying users by date. Include only records where non-paying customers have more downloads than paying customers. The output should be sorted by earliest date first and contain 3 columns date, non-paying downloads, paying downloads.
There are three tables:
ms_user_dimension (user_id, acc_id)
ms_acc_dimension (acc_id, paying_customer)
ms_download_facts (date, user_id, downloads)
This is my code so far
SELECT date,
SUM(CASE WHEN paying_customer = 'no' THEN cnt END) AS no,
SUM(CASE WHEN paying_customer = 'yes' THEN cnt END) AS yes
FROM (
SELECT date, paying_customer, SUM(downloads) AS cnt
FROM ms_download_facts d
LEFT JOIN ms_user_dimension u ON d.user_id = u.user_id
LEFT JOIN ms_acc_dimension a ON u.acc_id = a.acc_id
GROUP BY 1, 2
ORDER BY 1, 2
) prePivot
GROUP BY date
HAVING no > yes;
If I remove the HAVING no > yes at the end, the code will run and I can see I have three columns: date, yes, and no. However, if I add the HAVING statement, I get the error "column "no" does not exist...LINE 13: HAVING no > yes"
Can't figure out for the sake of my life what's going on here. Please let me know if anyone figures out something. TIA!
You don't need a subquery for this:
SELECT d.date,
SUM(CASE WHEN a.paying_customer = 'no' THEN d.downloads END) AS no,
SUM(CASE WHEN a.paying_customer = 'yes' THEN d.downloads END) AS yes
FROM ms_download_facts d LEFT JOIN
ms_user_dimension u
ON d.user_id = u.user_id LEFT JOIN
ms_acc_dimension a
ON u.acc_id = a.acc_id
GROUP BY d.date
HAVING SUM(CASE WHEN a.paying_customer = 'no' THEN d.downloads END) > SUM(CASE WHEN a.paying_customer = 'yes' THEN d.downloads END);
You can simplify the HAVING clause to:
HAVING SUM(CASE WHEN a.paying_customer = 'no' THEN 1 ELSE -1 END) > 0
This version assumes that paying_customer only takes on the values 'yes' and 'no'.
You may be able to simplify the query further, depending on the database you are using.
It doesn't like aliases in the having statement. Replace no with:
SUM(CASE WHEN paying_customer = 'no' THEN cnt END)
and do the similar thing for yes.
SELECT date,
SUM(CASE WHEN paying_customer = 'no' THEN cnt END) AS no,
SUM(CASE WHEN paying_customer = 'yes' THEN cnt END) AS yes
FROM (
SELECT date, paying_customer, SUM(downloads) AS cnt
FROM ms_download_facts d
LEFT JOIN ms_user_dimension u ON d.user_id = u.user_id
LEFT JOIN ms_acc_dimension a ON u.acc_id = a.acc_id
GROUP BY 1, 2
ORDER BY 1, 2
) prePivot
GROUP BY date
HAVING SUM(CASE WHEN paying_customer = 'no' THEN cnt END) > SUM(CASE WHEN paying_customer = 'yes' THEN cnt END);

How can I make two column from same table by two query

I've two query from same table but by two condition but how can I make two column for this two conditional count.
SELECT Count(*) FROM TBL_FT WHERE STATUS = 'X';
SELECT Count(*) FROM TBL_FT WHERE STATUS = 'Y' and
LOGDATE>trunc(sysdate);
You can use conditional aggregation:
SELECT
COUNT(CASE WHEN STATUS = 'X' THEN 1 END),
COUNT(CASE WHEN STATUS = 'Y' AND LOGDATE > trunc(sysdate) THEN 1 END)
FROM TBL_FT
You can also add a WHERE clause:
WHERE STATUS IN ('X', 'Y');
you can use something like this -
SELECT SUM(CASE
WHEN STATUS = 'X' THEN
1
ELSE
0
END) FIRST_VAL,
SUM(CASE
WHEN STATUS = 'Y'
AND LOGDATE > TRUNC(SYSDATE) THEN
1
ELSE
0
END) second_val
FROM TBL_FT;