I am writing below query which divides the two select query and calculate the percentage. But i am getting an error as not a single-group group function
select CASE WHEN COUNT(*) = 0 THEN 0 ELSE round((r.cnt / o.cnt)*100,3) END from
(Select count(*) as cnt from O2_CDR_HEADER WHERE STATUS NOT IN(0,1) and DATE_CREATED > (SYSDATE - 1)) r cross join
(Select count(*) as cnt from O2_CDR_HEADER WHERE DATE_CREATED > (SYSDATE - 1)) o;
You don't need to use joins. If I were you, I'd do:
select case when count(*) = 0 then 0
else round(100 * count(case when status not in (0, 1) then 1 end) / count(*), 3)
end non_0_or_1_status_percentage
from o2_cdr_header
where date_created > sysdate - 1;
Here's a simple demo:
with t as (select 1 status from dual union all
select 2 status from dual union all
select 3 status from dual union all
select 2 status from dual union all
select 4 status from dual union all
select 5 status from dual union all
select 6 status from dual union all
select 7 status from dual union all
select 1 status from dual union all
select 0 status from dual union all
select 1 status from dual)
select case when count(*) = 0 then 0
else round(100 * count(case when status not in (0, 1) then 1 end) / count(*), 3)
end col1
from t
where 1=0;
COL1
----------
0
And just in case you aren't sure that doing the filtering of the count in the case statement returns the same as when you filter in the where clause, here's a demo that proves it:
with t as (select 1 status from dual union all
select 2 status from dual union all
select 3 status from dual union all
select 2 status from dual union all
select 4 status from dual union all
select 5 status from dual union all
select 6 status from dual union all
select 7 status from dual union all
select 1 status from dual union all
select 0 status from dual union all
select 1 status from dual)
select 'using case statement' how_count_filtered,
count(case when status not in (0, 1) then 1 end) cnt
from t
union all
select 'using where clause' how_count_filtered,
count(*) cnt
from t
where status not in (0, 1);
HOW_COUNT_FILTERED CNT
-------------------- ----------
using case statement 7
using where clause 7
You are referencing an aggregate function (COUNT(*)) and an individual column expression (r.cnt and o.cnt) in the same SELECT query. This is not valid SQL unless a GROUP BY clause is added for the relevant individual columns.
It would be easier to provide a valid alternative it you could clarify what you'd like this query to return (given a sample schema and set of data). As a guess, I'd say you can simply substitute COUNT(*) with o.cnt to avoid the division by 0 issue. If there's some other logic expected to be present here, you'd need to clarify what that is.
It looks like you want to get a percentage of status not in 0,1, or 0 if there is no results.
Maybe this is what you want for the first line?
SELECT CASE WHEN (R.CNT = 0 AND O.CNT = 0) THEN 0 ELSE ROUND((R.CNT *100.0 / O.CNT),3) END
You don't need a cross join. Select the counts and do a division later on.
select case when ocnt > 0 then round((rcnt / ocnt)*100,3)
else 0 end
from
(
select
CASE WHEN STATUS NOT IN(0,1) and DATE_CREATED > (SYSDATE - 1)
THEN COUNT(*) END as rcnt,
CASE WHEN DATE_CREATED > (SYSDATE - 1)
THEN COUNT(*) END as ocnt
from O2_CDR_HEADER
group by status, date_created
) t
Boneist's answer is fine, but I would write it as:
select coalesce(round(100 * avg(case when status not in (0, 1) then 1.0 else 0
end), 3), 0) as non_0_or_1_status_percentage
from o2_cdr_header
where date_created > sysdate - 1;
Here is the answer which works perfectly for me
select CASE WHEN (o.cnt = 0) THEN 0 ELSE round((r.cnt / o.cnt)*100,3) END from
(Select count(*) as cnt from O2_CDR_HEADER WHERE STATUS NOT IN(0,1) and DATE_CREATED > (SYSDATE - 1)) r cross join
(Select count(*) as cnt from O2_CDR_HEADER WHERE DATE_CREATED > (SYSDATE - 1)) o
Related
Using below code;
SELECT CASE GROUPING_ID(period) WHEN 1 THEN 'TOTAL' ELSE TO_CHAR(period) END AS period,
COUNT(CASE status WHEN 'Completed' THEN 1 END) AS completed,
COUNT(CASE status WHEN 'WIP' THEN 1 END) AS wip,
COUNT(CASE status WHEN 'Not Started' THEN 1 END) AS not_started,
COUNT(*) AS Total
FROM transition_tasks_new
GROUP BY ROLLUP(period)
ORDER BY period;
I'm getting data in the attached format:
which is fine. However, I need to get the %complete for each of them.
For ex - % Pre-Update "Complete" would be 2/28 i.e 7%;
I wrote this query;
SELECT SUM(CASE WHEN period = '1-Pre Update' THEN 1 ELSE 0 END) AS value,
COUNT(taskid) AS max_value
FROM transition_tasks_new
WHERE status='Completed'
but it's not working as intended.
Divide the value by the total and multiply by 100 to calculate the percentage:
SELECT CASE GROUPING_ID(period) WHEN 1 THEN 'TOTAL' ELSE TO_CHAR(period) END
AS period,
COUNT(CASE status WHEN 'Completed' THEN 1 END) AS completed,
ROUND(COUNT(CASE status WHEN 'Completed' THEN 1 END)/COUNT(*) * 100, 3)
AS percent_completed,
COUNT(CASE status WHEN 'WIP' THEN 1 END) AS wip,
COUNT(CASE status WHEN 'Not Started' THEN 1 END) AS not_started,
COUNT(*) AS total
FROM tasks
GROUP BY ROLLUP(period)
ORDER BY period;
Which, for the sample data:
CREATE TABLE tasks (period, status) AS
SELECT 1, 'Completed' FROM DUAL CONNECT BY LEVEL <= 3 UNION ALL
SELECT 1, 'WIP' FROM DUAL CONNECT BY LEVEL <= 2 UNION ALL
SELECT 1, 'Not Started' FROM DUAL CONNECT BY LEVEL <= 1 UNION ALL
SELECT 1, 'Other' FROM DUAL CONNECT BY LEVEL <= 1 UNION ALL
SELECT 2, 'Completed' FROM DUAL CONNECT BY LEVEL <= 2 UNION ALL
SELECT 2, 'WIP' FROM DUAL CONNECT BY LEVEL <= 1 UNION ALL
SELECT 3, 'Completed' FROM DUAL CONNECT BY LEVEL <= 4 UNION ALL
SELECT 3, 'Not Started' FROM DUAL CONNECT BY LEVEL <= 2;
Outputs:
PERIOD
COMPLETED
PERCENT_COMPLETED
WIP
NOT_STARTED
TOTAL
1
3
42.857
2
1
7
2
2
66.667
1
0
3
3
4
66.667
0
2
6
TOTAL
9
56.25
3
3
16
db<>fiddle here
SELECT X.*,
ROUND(X.COMPLETED / X.TOTAL * 100,
2) AS PERCENT_COMPLETE
FROM
(WITH SAMPLE_DATA AS
(SELECT '1-Pre-Update' AS PERIOD,
3 AS WIP,
2 AS COMPLETED,
23 AS NOT_STARTED) SELECT PERIOD,
SUM(WIP) AS WIP,
SUM(COMPLETED) AS COMPLETED,
SUM(NOT_STARTED) AS NOT_STARTED,
SUM(WIP + COMPLETED + NOT_STARTED) AS TOTAL
FROM SAMPLE_DATA
GROUP BY PERIOD) X
Im using ORACLE TO check condition in 'DATE_PERIOS' with current time and last time
my ORD0011 table:
-------------------------------------------------------------
ORDER_ID | BS_NO | DATE_PERIOS | STATUS
-------------------------------------------------------------
3000003 HS00001 4-2021 COMPLETE
3000003 HS00183 5-2021 COMPLETE
3000003 HS00776 10-2021 FALSE
3000003 HS00559 11-2021 COMPLETE
3000003 HS00221 12-2021 ACTIVE
3000003 HS00222 1-2022 COMPLETE
--------------------------------------------------------------
when i select 'ORDER_ID' = 3000003, it will output with data following as:
------------------------------
ORDER_ID | HS_TIME
------------------------------
3000003 4
------------------------------
this is my recipe:
If i select DATE_PERIOS is: 1/2022, it will display HS_TIME is 4 (3 times COMPLETE before: 4-2021, 5-2021, 11-2021 + 1)
If i select DATE_PERIOS is: 11/2021, it will display HS_TIME is 3 (2 times COMPLETE before: 4-2021, 5-2021 + 1)
Note: only + 1 when status is COMPLETE
How to count number of Month with condition to output result as above ? Thanks a lot
You can use the below query having case statement to handle the status = 'COMPLETE' -
SELECT CASE WHEN COUNT(CASE WHEN status = 'COMPLETE' THEN 1 ELSE NULL END) > 0 THEN
COUNT(CASE WHEN status = 'COMPLETE' THEN 1 ELSE NULL END) + 1
ELSE NULL
END HS_TIME
FROM tb
WHERE TO_DATE(DATE_PERIOS, 'MM-YYYY') < TO_DATE('01-2020', 'MM-YYYY');
Demo.
One option is to conditionally (that's the CASE expression) add 1 (that's the SUM function) if status is COMPLETE.
WHERE clause requires TO_DATE with appropriate date format. Otherwise, you'd be comparing strings which would lead to wrong result; might be OK if date_perios was stored in YYYYMM format; on the other hand, perhaps you'd want to consider storing date values into the DATE datatype column.
Sample data
SQL> with ord0011 (order_id, date_perios, status) as
2 (select 303, '4-2021' , 'COMPLETE' from dual union all
3 select 303, '5-2021' , 'COMPLETE' from dual union all
4 select 303, '10-2021', 'FALSE' from dual union all
5 select 303, '11-2021', 'COMPLETE' from dual union all
6 select 303, '12-2021', 'ACTIVE' from dual union all
7 select 303, '1-2022' , 'COMPLETE' from dual
8 )
Query itself
9 select order_id,
10 sum(case when status = 'COMPLETE' then 1 else 0 end) hs_time
11 from ord0011
12 where to_date(date_perios, 'mm-yyyy') <= to_date('&par_date', 'mm-yyyy')
13 group by order_id;
Enter value for par_date: 1-2022
ORDER_ID HS_TIME
---------- ----------
303 4
SQL> /
Enter value for par_date: 11-2021
ORDER_ID HS_TIME
---------- ----------
303 3
SQL>
with cte as(
select *,row_number()over(partition by ORDER_ID order by ORDER_ID) as seq
from tb
),
cte2 as(
select *,
case
when DATE_PERIOS = '1-2022'
then (select count(*) + 1 from cte t2 where t1.seq > t2.seq and t2.STATUS = 'COMPLETE')
else 0 end as HS_TIME_1_2022,
case
when DATE_PERIOS = '11-2021'
then (select count(*) + 1 from cte t2 where t1.seq > t2.seq and t2.STATUS = 'COMPLETE')
else 0 end as HS_TIME_11_2022
from cte t1)
select ORDER_ID,max(HS_TIME_1_2022)HS_TIME_1_2022,max(HS_TIME_11_2022)HS_TIME_11_2022
from cte2
group by ORDER_ID
The following output is taken in sql-server but can also be run in Oracle.
Result
This may not be the right forum to ask this but I want to understand the logical error happening in my query.
I have wrote below query to understand how many users have delivered messages greater than sent messages(possibly an error in data capture, just wanted to assess it).
SELECT COUNT(DISTINCT user_id)
FROM wk_24_trigger
UNION
SELECT COUNT(DISTINCT user_id)
FROM (
SELECT *, (CASE WHEN delivered > 0 THEN 1 ELSE 0 END) as D,
(CASE WHEN sent > 0 THEN 1 ELSE 0 END) as S
FROM wk_24_trigger) t
WHERE t.D > t.s
The result which I got are as belows
_c0
1 1056840
2 1819729
I am not getting why row 2 > row 1.
Ideally even if for every entry Delivered > Sent then row 2 and row 1 should have been same
Are you sure that the first row is the result from the first query and the second one from the second query..??
It always need not be..
Try adding alias name after the count in each query and verify the result..
you can check below example as well..
WITH TEMP
AS(
SELECT 'A' USER_ID , 1 DELIVERED , NULL SENT FROM DUAL
UNION
SELECT 'B' ID , 10 A , 1 B FROM DUAL
UNION
SELECT 'C' ID , NULL A , 1 B FROM DUAL
UNION
SELECT 'D' ID , -1 A , 1 B FROM DUAL
)
SELECT COUNT(DISTINCT USER_ID), 'QUERY_1' QUERY
FROM TEMP
UNION
(SELECT COUNT(DISTINCT USER_ID), 'QUERY_2'
FROM (
SELECT USER_ID,DELIVERED,SENT,
(CASE
WHEN DELIVERED > 0 THEN
1
ELSE
0
END) D,
(CASE
WHEN SENT > 0 THEN
1
ELSE
0
END) S
FROM TEMP) T
WHERE T.D > T.S);
and system output is as below..
COUNT(DISTINCTUSER_ID) QUERY
1 1 QUERY_2
2 4 QUERY_1
the same could be your case as well..
Not sure if this is possible or not in straight SQL.
Is it possible to choose the operator for a sum using the likes of a CASE Statement or similar logic.
e.g.
select
(select 1 from dual)
(case when (select 1 from dual) = 1 then + else - end)
(select 2 from dual)
from dual
Thanks in return.
- and + cannot be results of CASE WHEN, because they are not values. -1 and +1 however are. Multiply this -1 or +1 with the desired value in order to get the positive or negative value. E.g:
select case when type = 'withdrawal' then -1 else +1 end * value as balance_change
For your example:
select
(select 1 from dual) +
(case when (select 1 from dual) = 1 then +1 else -1 end) *
(select 2 from dual)
from dual
select 1 + (case when (select 1 from dual) = 1 then +1 else -1 end) from dual
I mean:
with
sub_q as (
select 2000 val1, -1500 val2 from dual
union all
select 1000 val1, +2000 val2 from dual
)
select val1, val2, SIGN(val2) s, val1 + (case when SIGN(val2) = 1 then +val2 else -val2 end) res
from sub_q
I get crazy because of one query. I have a table like following and I want to get a data - Summa of Values by Status For every Date in interval.
Table
Id Name Value Date Status
1 pro1 2 01.04.14 0
2 pro1 8 02.04.14 1
3 pro2 6 02.04.14 1
4 pro3 0 03.04.14 0
5 pro4 7 03.04.14 0
6 pro4 2 03.04.14 0
7 pro4 4 03.04.14 1
8 pro4 6 04.04.14 1
9 pro4 1 04.04.14 1
For example,
Input: Name = pro4, minDate = 01.02.14, maxDate = 04.09.14
Output:
Date Values sum for 0 Status Values sum for 1 Status
01.04.14 0 0
02.04.14 0 0
03.04.14 9 (=7+2) 4 (only 4 exist)
04.04.14 0 7 (6+1)
In 01.02.14 and 02.04.14 dates, pro4 has not values by status, but I want to show that rows, because I need all dates in that interval. Can anyone help me to create this query?
Edit:
I can not change structure, I have already that table with data. Every day exist in table many times (minimum 1 time)
Thanks in advance.
Assuming you have a row for each date in the table, use conditional aggregation:
select date,
sum(Case when name = 'pro4' and status = 0 then Value else 0 end) as values_0,
sum(case when name = 'pro4' and status = 1 then Value else 0 end) as values_1
from Table t
where date >= '2014-04-01' and date <= '2014-04-09'
group by date
order by date;
If you don't have this list of dates, you can take this approach instead:
with dates as (
select cast('2014-04-01' as date) as thedate
union all
select dateadd(day, 1, thedate)
from dates
where thedate < '2014-04-09'
)
select dates.thedate,
sum(Case when status = 0 then Value else 0 end) as values_0,
sum(case when status = 1 then Value else 0 end) as values_1
from dates left outer join
table t
on t.date = dates.thedate and t.name = 'pro4'
group by dates.thedate;
just an assumption query :
select Distinct date ,case when status = 0 and MAX(date) then SUM(value) ELSE 0 END Status0 ,
case when status = 1 and MAX(date) then SUM(value) ELSE 0 END Status1 from table
To expand my comment the complete query is
WITH [counter](N) AS
(SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1)
, days(N) AS (
SELECT row_number() over (ORDER BY (SELECT NULL)) FROM [counter])
, months (N) AS (
SELECT N - 1 FROM days WHERE N < 13)
, calendar ([date]) AS (
SELECT DISTINCT cast(dateadd(DAY, days.n
, dateadd(MONTH, months.n, '20131231')) AS date)
FROM months
CROSS JOIN days
)
SELECT a.Name
, c.Date
, [Sum of 0] = SUM(CASE Status WHEN 0 THEN Value ELSE 0 END)
, [Sum of 1] = SUM(CASE Status WHEN 1 THEN Value ELSE 0 END)
FROM Calendar c
LEFT JOIN myTable a ON c.Date = a.Date AND a.name = 'pro4'
WHERE c.date BETWEEN '20140201' AND '20140904'
GROUP BY c.Date, a.Name
ORDER BY c.Date
Note that the condition on the name need to be in the JOIN, otherwise you'll get only the date of your table.
If you need multiple years just add another CTE for the count and a dateadd(YEAR,...) in the CTE calendar
This is not really the exact query, but I think you can get that by having a query that looks like:
select date, status, sum(value) from table
where (date between mindate and maxdate) and name = product_name
group by date, status;
this page gives more info.
EDIT
So the above query only gives a part of the answer required by the OP. A LEFT OUTER JOIN of the original table and the result of the above query on thedate and status fields will give the missing info.
e.g.
select x.date, x.status, x.sum_of_values from table as y
left outer join
(select date, status, sum(value) as sum_of_values
from table
where (date between mindate and maxdate) and name = product_name
group by date, status) as x
on y.date= x.date and y.status = x.status
order by x.date;