SQL multiple data count from multiple tables with union all - sql

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

Related

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().

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

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!

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;

Count calculation on enum type in PostgreSQL

I have a PostgreSQLtable structured like so:
file(owner_id int, filename text, status status_type)
with status_type defined:
create type status_type as enum
(
, 'pending'
'complete'
);
From here, what I would like to achieve is to get the percentage of how many files have the status 'complete' from 'complete'+'pending' collection for the same owner id.
e.g. if i have 10 entries for owner_id=1, 3 with status complete and 7 with status pending then the percentage would be 30%.
Any idea how could I do this in just one SELECT statement, provinding only the owner_id?
something like this:
select pending_count,
complete_count,
case
when (pending_count + complete_count) = 0 then null
else pending_count::decimal / (pending_count + complete_count)
end as percentage
from (
select sum(case when status = 'pending' then 1 end) as pending_count,
sum(case when status = 'complete' then 1 end) as complete_count
from file
where owner_id = 1
) t
You can use that to get the percentage for all users as well:
select owner_id,
pending_count,
complete_count,
case
when (pending_count + complete_count) = 0 then null
else pending_count::decimal / (pending_count + complete_count)
end as percentage
from (
select owner_id,
sum(case when status = 'pending' then 1 end) as pending_count,
sum(case when status = 'complete' then 1 end) as complete_count
from file
group by owner_id
) t
SQLFiddle example: http://sqlfiddle.com/#!15/0b341/1

Merging data SQL Query

I have a query request where I have to show one customer activity for each web-site but it has to be only one row each, instead of one customer showing multiple times for each activity.
Following is the query I tried but brings lot more rows. please help me as how I can avoid duplicates and show only one customer by each row for each activity.
SELECT i.customer_id, i.SEGMENT AS Pistachio_segment,
(CASE when S.SUBSCRIPTION_TYPE = '5' then 'Y' else 'N' end ) PB_SUBS
(CASE WHEN S.SUBSCRIPTION_TYPE ='12' THEN 'Y' ELSE 'N' END) Daily_test,
(CASE when S.SUBSCRIPTION_TYPE ='8' then 'Y' else 'N' end) COOK_4_2
FROM IDEN_WITH_MAIL_ID i JOIN CUSTOMER_SUBSCRIPTION_FCT S
ON I.IDENTITY_ID = S.IDENTITY_ID and I.CUSTOMER_ID = S.CUSTOMER_ID
WHERE s.site_code ='PB' and s.subscription_end_date is null
Sounds like you need to group by customer_id and perform aggregations for the other columns you are selecting. For example:
sum(case when s.subscription_type = '5' then 1 else 0 end) as pb_subs_count
You could try one of two things:
Use a GROUP BY statement to combine all records with the same id, e.g.,
...
WHERE s.site_code ='PB' and s.subscription_end_date is null
GROUP BY i.customer_id
Use the DISTINCT command in your SELECT, e.g.,
SELECT DISTINCT i.customer_id, i.SEGMENT, ...
you could use a aggregation (SUM) on customer_id, but what do you expect to happen on the other fields? for example, if you have SUBSCRIPTION_TYPE 5 and 13 for the same customer (2 rows), which value do you want?
Perhaps you are looking for something like this:
SELECT i.customer_id, i.SEGMENT AS Pistachio_segment,
MAX(CASE when S.SUBSCRIPTION_TYPE = '5' then 'Y' else 'N' end ) PB_SUBS
MAX(CASE WHEN S.SUBSCRIPTION_TYPE ='12' THEN 'Y' ELSE 'N' END) Daily_test,
MAX(CASE when S.SUBSCRIPTION_TYPE ='8' then 'Y' else 'N' end) COOK_4_2
FROM IDEN_WITH_MAIL_ID i JOIN CUSTOMER_SUBSCRIPTION_FCT S
ON I.IDENTITY_ID = S.IDENTITY_ID and I.CUSTOMER_ID = S.CUSTOMER_ID
WHERE s.site_code ='PB' and s.subscription_end_date is null
GROUP BY i.customer_id, i.SEGMENT
I can't be sure, though, without knowing more about the tables involved.