oracle: counting number of null fields in a row - oracle

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.

Related

Is it possible to select from view using a column that isnt selected in the query?

I have this query for example:
CREATE OR REPLACE VIEW xx AS
SELECT TO_CHAR(tsc.id) AS status,
CASE WHEN tsc.description IS NULL THEN CAST('' as NVARCHAR2(50)) ELSE tsc.description END AS description,
SUM(CASE WHEN tr.USER_TYPE = 1 THEN 1 ELSE 0 END) AS "1",
SUM(CASE WHEN tr.USER_TYPE = 2 THEN 1 ELSE 0 END) AS "2",
SUM(CASE WHEN tr.USER_TYPE = 3 THEN 1 ELSE 0 END) AS "3",
SUM(CASE WHEN tr.USER_TYPE = 5 THEN 1 ELSE 0 END) AS "5",
SUM(CASE WHEN tr.USER_TYPE IS NOT NULL THEN 1 ELSE 0 END) AS total
FROM TRANSACTION_STATUS_CODES tsc
LEFT JOIN TRANSACTIONS tr ON tsc.id = tr.status AND tr.User_Type BETWEEN 1 AND 5 AND tr.status != 1 AND tr.update_date BETWEEN TO_DATE('2022-01-01', 'yyyy-mm-dd HH24:MI:SS') AND
TO_DATE('2023-01-04', 'yyyy-mm-dd HH24:MI:SS')
LEFT JOIN TRANSACTION_USER_TYPES ut ON ut.id = tr.user_type
WHERE tsc.id != 1
GROUP BY tsc.id, tsc.description;
I have this line inside:
AND tr.update_date BETWEEN TO_DATE('2022-01-01', 'yyyy-mm-dd HH24:MI:SS') AND TO_DATE('2023-01-04', 'yyyy-mm-dd HH24:MI:SS')
Im trying to create that view and then select all from view given an Update_date column as a where clause, something like either :
SELECT * FROM xx WHERE transactions.update_date IN (SELECT transactions.update_date FROM transactions WHERE transactions.update_date BETWEEN TO_DATE('2023-01-04', 'yyyy-mm-dd HH24:MI:SS') AND TO_DATE('2023-01-04', 'yyyy-mm-dd HH24:MI:SS'));
----
select * from xx where update_date between date1 and date2
nothing Ive tried seem to be working, and tbh thats kinda wierd that it has to be in the select statement of the view in order for my view to recognize it because the tables are joined so why the heck wouldnt my view recognize it? is that really how things are working in oracle? or am I missing something?
to be honest it will kinda freak me out if theres no workaround because this doesnt make any sense..
That's how it goes. Imagine view as a table - it contains its columns. You can't select something that doesn't exist. For example, table contains 3 columns:
SQL> select * from dept;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
Create a view that contains only one column from that table:
SQL> create or replace view xx as select dname from dept;
View created.
SQL> desc xx
Name Null? Type
----------------------------------------- -------- ----------------------------
DNAME VARCHAR2(14)
Can you select column that doesn't exist in that view? Of course you can not:
SQL> select loc from xx;
select loc from xx
*
ERROR at line 1:
ORA-00904: "LOC": invalid identifier
SQL>
The same goes for your view, although it is based on several tables.
If you want to be able to select other columns, you have to include them into SELECT column list. It is also probably a good idea NOT to filter data on date column while creating a view:
CREATE OR REPLACE VIEW xx
AS
SELECT TO_CHAR (tsc.id) AS status,
CASE
WHEN tsc.description IS NULL THEN CAST ('' AS NVARCHAR2 (50))
ELSE tsc.description
END AS description,
SUM (CASE WHEN tr.USER_TYPE = 1 THEN 1 ELSE 0 END) AS "1",
SUM (CASE WHEN tr.USER_TYPE = 2 THEN 1 ELSE 0 END) AS "2",
SUM (CASE WHEN tr.USER_TYPE = 3 THEN 1 ELSE 0 END) AS "3",
SUM (CASE WHEN tr.USER_TYPE = 5 THEN 1 ELSE 0 END) AS "5",
SUM (CASE WHEN tr.USER_TYPE IS NOT NULL THEN 1 ELSE 0 END) AS total,
tr.update_date --> newly added
FROM TRANSACTION_STATUS_CODES tsc
LEFT JOIN TRANSACTIONS tr
ON tsc.id = tr.status
AND tr.User_Type BETWEEN 1 AND 5
AND tr.status != 1
/* --> remove that filter
AND tr.update_date BETWEEN TO_DATE ('2022-01-01',
'yyyy-mm-dd HH24:MI:SS')
AND TO_DATE ('2023-01-04',
'yyyy-mm-dd HH24:MI:SS')
*/
LEFT JOIN TRANSACTION_USER_TYPES ut ON ut.id = tr.user_type
WHERE tsc.id != 1
GROUP BY tsc.id, tsc.description, tr.update_date --> added TR.UPDATE_DATE

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

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

Oracle - Selecting not null result of calculation

For every row of my data set, there exist data for the only one of the two options for calculation and the other columns are Null.
My goal is to find simplest way to select not null result of calculation for each row. Expected result:
ROW_NUM result
-------- -------
1 4.5
2 4.56
My code:
With DATASET AS (
-- column 1 is just for row number,
-- column 2 and 3 for caculation option1,
--- columns 4~6 for caculation option2
SELECT 1 ROW_NUM, NULL time1, NULL qty1, 2 time2_1, 2.5 time2_2, 1 qty2
FROM DUAL
UNION
SELECT 2 ROW_NUM, 4.56 time1, 1 qty1, NULL time2_1, NULL time2_2, NULL qty2
FROM DUAL
)
SELECT ROW_NUM, time1/qty1 OPTION1, (time2_1+time2_2)/qty2 OPTION2
FROM DATASET;
Result:
ROW_NUM OPTION1 OPTION2
-------- ------- ---------
1 4.5
2 4.56
You can decode and use different representation when null:
SELECT ROW_NUM, decode(time1/qty1,null,(time2_1+time2_2)/qty2,time1/qty1) result FROM DATASET;
Or nvl
SELECT ROW_NUM, nvl(time1/qty1,(time2_1+time2_2)/qty2,time1/qty1) result FROM DATASET;
NVL lets you replace null (returned as a blank) with a string in the results of a query.
use COALESCE function as following:
With DATASET AS (
--each row contain information for either option1 or 2
SELECT *
FROM
(
--column 1 is just for row number, column 2 and 3 for caculation option1, columns 4~6 for caculation option2
SELECT 1 ROW_NUM, NULL time1, NULL qty1 , 2 time2_1 , 2.5 time2_2, 1 qty2 FROM DUAL
UNION
SELECT 2 ROW_NUM, 4.56 time1 , 1 qty1 , NULL time2_1 , NULL time2_2 , NULL qty2 FROM DUAL
)
)SELECT ROW_NUM, coalesce(time1/qty1,(time2_1+time2_2)/qty2) as result FROM DATASET;
db<>fiddle demo
Cheers!!

plsql to fetch the max sequence line for every header and update the null values of the column with next sequence number for the particular header

I have a requirement like below .
I have a table with 2 columns, (contract_id,line_num)
create table tx (contract_id number,line_num number);
I have data like
contract_id || line_num
----------- ---------
1 || 1
1 || null
1 || null
2 || 1
2 || null
2 || null
3 || 1
3 || null
I have to write a plsql block , first I have to get the max(line_num) for each contract_id, and then update the next sequence number for each contract_id where ever there is null in line_num column for each contract_id using cursor for loop.
I should get like below
contract_id || line_num
----------- ---------
1 || 1
1 || 2
1 || 3
2 || 1
2 || 2
2 || 3
3 || 1
3 || 2
can u pls help me with this...
DECLARE
var1 NUMBER := 0;
BEGIN
SELECT MAX (gocpd.column46)
INTO var1
FROM gecm_okc_con_part_details gocpd, okc_rep_contracts_all orca
WHERE gocpd.contract_id = orca.contract_id
AND orca.attribute12 = 'GE-Power' --AND GOCPD.COLUMN46 = NULL AND GOCPD.CONTRACT_ID = 525215; END
;
BEGIN
UPDATE GECM_OKC_CON_PART_DETAILS GOCPD
SET GOCPD.COLUMN46 = var1 + 1
FROM okc_rep_contracts_all orca
WHERE gocpd.contract_id = orca.contract_id
AND orca.attribute12 = 'GE-Power'
AND gocpd.column46 = NULL
AND gocpd.contract_id = 525215;
COMMIT;
END;
END;
You have not provided enough details I requested and I cannot correlate your PL/SQL code with the data you provided.
I am assuming that you are simply interested to update the rows based on NULL values and then incrementing from the MAX(LINE_NUM). If yes, something like this should solve your purpose. If not, please add more details in your question.
MERGE INTO tx tgt USING (
SELECT
ROWID,
contract_id,
CASE
WHEN line_num IS NULL THEN ROW_NUMBER() OVER(
PARTITION BY contract_id
ORDER BY
contract_id
) + MAX(line_num) OVER(
PARTITION BY contract_id
) - 1
ELSE line_num
END
AS line_num
FROM
tx
)
src ON ( src.rowid = tgt.rowid )
WHEN MATCHED THEN UPDATE SET tgt.line_num = src.line_num;
SQL Fiddle Demo

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);

Resources