How to force a set of possible values in conditional aggregation? - oracle

In Oracle I have a query that uses conditional aggregation to display totals.
See http://sqlfiddle.com/#!4/ab1915/2
select ts.description,
sum(case when ta.group_number = 1 then 1 else 0 end) group_one
,sum(case when ta.group_number = 2 then 1 else 0 end) group_two
,sum(case when ta.group_number = 3 then 1 else 0 end) group_three
from ta
join ts on ta.status_id=ts.status_id
group by ts.description;
However, I need the results to also show another row for the status "cancelled", which is not currently showed because there are not any record with that status, but I need to display "cancelled" with 0 counts anyway,
Any idea how I would do that?
The DDL for this example is
create table ta (group_number number, status_id number);
create table ts (status_id number, description varchar(111));
insert into ta (group_number,status_id) values (1,1);
insert into ta (group_number,status_id) values (2,1);
insert into ta (group_number,status_id) values (3,2);
insert into ta (group_number,status_id) values (3,3);
insert into ta (group_number,status_id) values (3,3);

Use an outer join so you pick up all of the ts valueswhich means you need to reverse the order of the tables; and I'd use count() instead of sum():
select ts.description,
count(case when ta.group_number = 1 then 1 end) group_one
,count(case when ta.group_number = 2 then 1 end) group_two
,count(case when ta.group_number = 3 then 1 end) group_three
,count(case when ta.group_number = 4 then 1 end) group_four
from ts
left join ta on ta.status_id=ts.status_id
group by ts.description;
DESCRIPTION GROUP_ONE GROUP_TWO GROUP_THREE
-------------------- ---------- ---------- -----------
started 1 1 0
finished 0 0 2
cancelled 0 0 0
progressing 0 0 1
Updated SQL Fiddle
The case expression can evaluate to anything when matched, it just has to be non-null. From the docs:
If you specify expr, then COUNT returns the number of rows where expr is not null.
so where the case is not matched it evaluates to null, which is not counted. (You can have else null if you prefer to be explicit, but I tend to prefer brevity...)

Related

Count and Sum inside a select with multiple sums and counts

I'm using sub-query factoring and I have a query that returns invoice lines, and in the end I have this final sub-query:
I've already tried Partition but without success
SELECT
COUNT(CASE WHEN PC <> 0 THEN 1 END) AS A_LINECOUNT,
SUM(CASE WHEN PC > 0 THEN NR ELSE 0 END) AS B_PRODUCTCOUNT,
COUNT(CASE WHEN ALLOW_PAY = 1 THEN 1 END) AS C_INVOICECOUNT, --- ERROR
SUM(CASE WHEN ALLOW_PAY = 1 THEN MISSING_VALUE ELSE 0 END) AS D_INVOICETOTAL, --- ERROR
COUNT(CASE WHEN IS_NON_LIQUIDABLE_PRODUCT = 1 THEN 1 END) AS E_CONDITIONCOUNT,
COUNT(CASE WHEN IS_LIQUIDABLE_PRODUCT = 1 THEN 1 END) AS F_CONDITIONCOUNT
FROM MAIN_Q
The calculation of C_INVOICECOUNT and D_INVOICETOTAL is not correct because their values are repeated within each line of the invoice. Please consider that um MAIN_Q i also have a document_id where i can group by.
thanks
Maybe I understood correctly, maybe not, but this is too long for comment. If yes, C_INVOICECOUNT can be count as:
count(distinct case when allow_pay = 1 then document_id end)
But the problem is with D_INVOICETOTAL. You have repeated values for each invoice here and details which do not repeat. If so, add row numbering to your query:
select main_q.*, row_number() over (partition by document_id) rn from main_q
and then in problematic places use rn = 1:
select ...
count(case when rn = 1 and allow_pay = 1 then 1 end),
sum(case when rn = 1 and allow_pay = 1 then missing_value else 0 end)
...
from (select main_q.*, row_number() over (partition by document_id) rn from main_q)
Only first rows for each invoice will be analysed. Of course you can add row_number in earlier step.

PL/SQL: Need help creating calculated columns based on conditions. to be done in select query

I am trying to count all the distinct ids based on conditions. But I am unable to figure out where I am going wrong with the syntax. The logic is
COUNTD(IF ([column_name1] = 1) THEN [DATAPAGEID] END)
This is the formula I used in Tableau. However when writing it in a PL/SQL query as
Select FT.NAME, COUNT(DISTINCT FT.pageID IF FT."column_name" = 1 )
as total_expected
FROM
( Sub Query) FT
Group by FT.Name
Order by FT.Name
Needless to say its throwing errors. Now I can write separate queries which can give me each number using a where condition. For example, if I wanted a count of distinct pageid where column_name1 = 1, I would write something like this
Select FT.SITENAME, COUNT(DISTINCT DATAPAGEID) as Datapage
from
(sub query)
WHERE FT."column_name" = 1
but the problem with that is that I have other calculated columns in the query which will all need to be part of the same row. To illustrate here's what the table would look like
name Calculated_Column1 Calculated_Column2 Calculated_column3
abc 781 811 96.54%
pqr 600 800 75.00%
where calculated_column3 is the result of 781/811. Therefore I can't have a new query for each column. I thought using an if condition when calculating columns will solve this, but I can't get the syntax right somehow.
Therefore, I need to know how can I create conditional calculated columns within the select query. If I have not explained this well, please let me know and I will try to clarify further.
You can use a CASE block inside the count (DISTINCT ) as shown.
SELECT FT.NAME,
COUNT(DISTINCT
CASE
WHEN DATAPAGEID = 1
THEN 1
ELSE 0
END ) Calculated_Column1,
COUNT(DISTINCT
CASE
WHEN DATAPAGEID = 2
THEN 1
ELSE 0
END ) Calculated_Column2,
( COUNT(DISTINCT
CASE
WHEN DATAPAGEID = 1
THEN 1
ELSE 0
END ) / COUNT(DISTINCT
CASE
WHEN DATAPAGEID = 2
THEN 1
ELSE 0
END ) ) * 100||'%' Calculated_Column3
FROM
( SELECT 'abc' name, 1 DATAPAGEID FROM dual
UNION ALL
SELECT 'abc' name, 1 DATAPAGEID FROM dual
UNION ALL
SELECT 'pqr' name, 2 DATAPAGEID FROM dual
UNION ALL
SELECT 'pqr' name, 2 DATAPAGEID FROM dual
UNION ALL
SELECT 'pqr' name, 3 DATAPAGEID FROM dual
) FT
GROUP BY FT.Name
ORDER BY FT.Name;
Output is
abc 1 1 100%
pqr 1 2 50%

add value to local variable oracle PL/SQL

DECLARE
var1 INTEGER :=0;
var2 INTEGER :=0;
BEGIN
SELECT DISTINCT
<whatever here>
CASE
WHEN trunc(thisDate) - TRUNC(thatDate) BETWEEN 0 AND 10 THEN var1+1
WHEN trunc(thisDate2) - TRUNC(thatDate2) BETWEEN 11 AND 20 THEN var2+1
ELSE 0
END
FROM
<Rest of query here>
basically what I want to be able to do is to add 1 to the local variable then print out the value of that variable as part of my select statement using a count (or sum of the count) or something everytime the difference in the ages falls within those categories
I'm not sure how to add to the local variable basically.
SELECT
sum(CASE WHEN trunc(thisDate) - TRUNC(thatDate) BETWEEN 0 AND 10 THEN 1 ELSE 0 END) + var1,
sum(CASE WHEN trunc(thisDate2) - TRUNC(thatDate2) BETWEEN 11 AND 20 THEN 1 ELSE 0 END) + var2
into var1, var2
FROM
<Rest of query here>
Try analytic functions
BEGIN
SELECT DISTINCT
<whatever here>
sum(CASE
WHEN trunc(thisDate) - TRUNC(thatDate) BETWEEN 0 AND 10 THEN 1
end) over() var1,
sum(CASE
WHEN trunc(thisDate2) - TRUNC(thatDate2) BETWEEN 11 AND 20 THEN 1
END) over() var2
FROM
<Rest of query here>
Given a comment by Allan, maybe you are looking for something like than instead:
SELECT V.*,
case when t1 is not null then
count(t1) over (ORDER BY n ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
when t2 is not null then
count(t2) over (ORDER BY n ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
else
0
end varX
FROM (
SELECT DISTINCT
<whatever here>
ROWNUM n,
CASE WHEN trunc(thisDate) - TRUNC(thatDate) BETWEEN 0 AND 10 THEN 1 END t1,
CASE WHEN trunc(thisDate) - TRUNC(thatDate) BETWEEN 11 AND 20 THEN 1 END t2
FROM
<Rest of query here>
) V
Some example for you to check if this is what you are looking for: http://sqlfiddle.com/#!4/2de0d/2
The inner query is basically yours, plus
a ROWNUM column (maybe not necessary if you somehow ORDER BY your rows)
a marker set to 1 if the row is in the first range, NULL otherwise
a marker set to 1 if the row is in the second range, NULL otherwise
The outer query use the analytic function COUNT() OVER(...) to respectively count the numbers of markers between the current row and the first of the result set. The row number n is used here. Replace that by something more relevant if your data are already ordered.
I don't think you can update some variable while executing a query. All you can do is fetch the result value into some variable.
Slight variation over Multisync's answer, using COUNT instead of SUM:
SELECT
count(CASE WHEN trunc(thisDate) - TRUNC(thatDate) BETWEEN 0 AND 10 THEN 1 END) ,
count(CASE WHEN trunc(thisDate2) - TRUNC(thatDate2) BETWEEN 11 AND 20 THEN 1 END)
into var1, var2
FROM
<Rest of query here>
This will actually return a one-row result with the count of values is the first range in the first column, and the count of values in the second range in the second column. Using the INTO var1,var2 clause, PL/SQL will implicitly fetch those values into your local variables.

Oracle: How to count null and non-null rows

I have a table with two columns that might be null (as well as some other columns). I would like to count how many rows that have column a, b, both and neither columns set to null.
Is this possible with Oracle in one query? Or would I have to create one query for each? Can't use group by or some other stuff I might not know about for example?
COUNT(expr) will count the number of rows where expr is not null, thus you can count the number of nulls with expressions like these:
SELECT count(a) nb_a_not_null,
count(b) nb_b_not_null,
count(*) - count(a) nb_a_null,
count(*) - count(b) nb_b_null,
count(case when a is not null and b is not null then 1 end)nb_a_b_not_null
count(case when a is null and b is null then 1 end) nb_a_and_b_null
FROM my_table
Something like this:
SELECT sum(case
when a is null and b is null then 1
else 0
end) as both_null_count,
sum(case
when a is null and b is not null then 1
else 0
end) as only_a_is_null_count
FROM your_table
You can extend that for other combinations of null/not null
select sum(decode(a,null,0,1)) as "NotNullCount", sum(decode(a,null,1,0)) as "NullCount"
from myTable;
Repeat for as many fields as you like.
It can be accomplished in Oracle just in 1 row:
SELECT COUNT(NVL(potential_null_column, 0)) FROM table;
Function NVL checks if first argument is null and treats it as value from second argument.
This worked well for me for counting getting the total count for blank cells on a group of columns in a table in oracle: I added the trim to count empty spaces as null
SELECT (sum(case
when trim(a) is null Then 1
else 0
end)) +
(sum(case
when trim(b) is null
else 0
end)) +
(sum(case
when trim(c) is null
else 0
end)) as NullCount
FROM your_table
Hope this helps
Cheers.
SQL>CREATE TABLE SAMPLE_TAB (COL1 NUMBER NOT NULL, COL2 DATE DEFAULT SYSDATE, COL3 VARCHAR2(20));
SQL>INSERT INTO SAMPLE_TAB(COL1,COL2,COL3) VALUES(121,SYSDATE-2,'SAMPLE DATA');
SQL>INSERT INTO SAMPLE_TAB(COL1,COL2,COL3) VALUES(122,NULL,NULL); --ASSIGN NULL TO COL2
SQL>INSERT INTO SAMPLE_TAB(COL1,COL3) VALUES(123,'SAMPLE DATA RECORD 3');--COL2 DEFAULT VALUE ASSIGN AS SYSDDATE AS PER STRUCTURE.
SQL>COMMIT;
SQL> SELECT * FROM SAMPLE_TAB;
SQL> SELECT *
FROM USER_TAB_COLUMNS U
WHERE 1=1
AND TABLE_NAME='SAMPLE_TAB'
AND NUM_NULLS!=0;
SQL> ANALYZE TABLE SAMPLE_TAB COMPUTE STATISTICS;
SQL> SELECT *
FROM USER_TAB_COLUMNS U
WHERE 1=1
AND TABLE_NAME='SAMPLE_TAB'
AND NUM_NULLS!=0;
One way to do it would be:
select count(*) from table group by nvl2(a, 0, 1), nvl2(b, 0, 1) having nvl2(a,0,1) = nvl2(b,0,1);

oracle: counting number of null fields in a row

I have a table that has 5 "optional" fields. I'd like to find out how many rows have all 5 null, how many have 1 field non-null, etc.
I've tried a couple of things, like:
select
count(*),
( (if field1 is null then 1 else 0) +
(if field2 is null then 1 else 0) +
etc.
but of course that doesn't work.
Ideally, I'm looking for output that's something like
Nulls Cnt
0 200
1 345
...
5 40
Is there an elegant solution?
The keyword is not if, it is case, and you must use end to end the case statement.
Here is a query that can suit you:
WITH subQuery AS
(
SELECT My_Table.*, (CASE WHEN My_Table.field1 IS NULL THEN 1 ELSE 0 END +
CASE WHEN My_Table.field2 IS NULL THEN 1 ELSE 0 END +
CASE WHEN My_Table.field3 IS NULL THEN 1 ELSE 0 END +
CASE WHEN My_Table.field4 IS NULL THEN 1 ELSE 0 END +
CASE WHEN My_Table.field5 IS NULL THEN 1 ELSE 0 END ) NumberOfNullFields
FROM My_Table
)
SELECT NumberOfNullFields, COUNT(*)
FROM subQuery
GROUP BY NumberOfNullFields;
While there is nothing wrong with the case WHEN counting, I just wanted to see if there was another way.
WITH SAMPLEDATA AS(--just generate some data with nulls for 5 cols
select level ,
(case when mod(level,2) = 0 then 1 else null end) colA,
(case when mod(level,3) = 0 then 1 else null end) colB,
(case when mod(level,5) = 0 then 1 else null end) colC,
(case when mod(level,7) = 0 then 1 else null end) colD,
(case when mod(level,11) = 0 then 1 else null end) colE
from dual
connect by level < 1000
), --utilize the count(Aggregate)'s avoidance of nulls to our summation advantage
nullCols as(
SELECT COUNT(COLA) aNotNull
,cOUNT(*)-COUNT(COLA) aNull
,count(colB) bNotNull
,cOUNT(*)-count(colB) bNull
,count(colc) cNotNull
,cOUNT(*)-count(colc) cNull
,count(cold) dNotNull
,cOUNT(*)-count(cold) dNull
,count(cole) eNotNull
,cOUNT(*)-count(cole) eNull
, cOUNT(*) TotalCountOfRows
from SAMPLEDATA )
SELECT (select count(*) from sampledata where cola is null and colb is null and colc is null and cold is null and cole is null) allIsNull
,nullCols.*
FROM nullCols;
ALLISNULL ANOTNULL ANULL BNOTNULL BNULL CNOTNULL CNULL DNOTNULL DNULL ENOTNULL ENULL TOTALCOUNTOFROWS
---------------------- ---------------------- ---------------------- ---------------------- ---------------------- ---------------------- ---------------------- ---------------------- ---------------------- ---------------------- ---------------------- ----------------------
207 499 500 333 666 199 800 142 857 90 909 999
this utilizes the
If expression in count(expression)
evaluates to null, it is not counted:
as noted from here
This method, as is obvious above, does not 'eloquently' sum all null columns well. Just wanted to see if this was possible without the CASE logic.

Resources