Count specific match from complete table data in Oracle DB - sql

I want to count "Yes" and "No" from the below table data irrespective of the columns.
Oracle DB Table:
Intended Output:
It doesn't matter from which column it is coming. I just want to calculate the total count of Yes and No.
I tried with pivot but couldn't find a way how to use that for multiple columns.
Please help.

If it has to be dynamic, then ... it has to be dynamic, which means that PL/SQL is an option to do what you want. Here's an example:
Function that loops through all columns in a table whose name is passed as a parameter and calculates number of a value passed as the second parameter:
SQL> create or replace function f_yes_no
2 (par_table_name in varchar2, par_yes_no in varchar2)
3 return number
4 is
5 l_cnt number;
6 retval number := 0;
7 begin
8 for cur_r in (select column_name from user_tab_columns
9 where table_name = dbms_assert.sql_object_name(par_table_name)
10 and data_type like '%CHAR%'
11 )
12 loop
13 execute immediate
14 'select count(*) from ' || dbms_assert.sql_object_name(par_table_name) ||
15 ' where ' || cur_r.column_name || ' = ' ||
16 dbms_assert.enquote_literal(par_yes_no)
17 into l_cnt;
18 retval := retval + l_cnt;
19 end loop;
20 return retval;
21 end;
22 /
Function created.
SQL>
Testing: sample table:
For a sample table:
SQL> select * from test;
ZING PLING COL3 BOING
----- ----- ----- -----
Yes No Yes No
Yes No No No
No No Yes No
SQL> select f_yes_no('TEST', 'Yes') cnt_yes,
2 f_yes_no('TEST', 'No' ) cnt_no
3 from dual;
CNT_YES CNT_NO
---------- ----------
4 8
SQL>
What's nice with it? You can reuse the function for other strings in another tables, e.g. how many MANAGERs are there in Scott's EMP table?
SQL> select ename, job from emp order by job;
ENAME JOB
---------- ---------
SCOTT ANALYST
FORD ANALYST
MILLER CLERK
JAMES CLERK
SMITH CLERK
ADAMS CLERK
BLAKE MANAGER
JONES MANAGER
CLARK MANAGER
KING PRESIDENT
TURNER SALESMAN
MARTIN SALESMAN
WARD SALESMAN
ALLEN SALESMAN
14 rows selected.
SQL> select f_yes_no('EMP', 'MANAGER') cnt_mgr from dual;
CNT_MGR
----------
3
SQL>

The UNPIVOT clause?
SELECT
SUM(DECODE(v, 'Yes', 1)) "Yes",
SUM(DECODE(v, 'No', 1)) "No"
FROM t
UNPIVOT (
v FOR value_type IN (c1, c2, c3, c4, c5)
);
db<>fiddle
More generic way:
SELECT
SUM(DECODE(yes_no, 'Yes', 1)) "Yes",
SUM(DECODE(yes_no, 'No', 1)) "No"
FROM (
SELECT
x.yes_no.EXTRACT(c.column_name || '/text()').getStringVal() yes_no
FROM
XMLTABLE ('/ROWSET/ROW'
PASSING XMLTYPE(dbms_xmlgen.getxml('SELECT * FROM t'))
COLUMNS
yes_no XMLTYPE PATH '*[text() = ("Yes", "No")]') x
CROSS JOIN user_tab_columns c
WHERE
c.table_name = 'T' AND
column_name LIKE 'C%'
) t;
db<>fiddle
All credit must go to https://stackoverflow.com/a/45866854/3350428.

One method would use a lateral join:
select sum(case when col = 'Yes' then 1 else 0 end) as num_yes,
sum(case when col = 'No' then 1 else 0 end) as num_no
from t cross join lateral
(select t.column1 as col from dual union all
select t.column2 from dual union all
select t.column3 from dual union all
select t.column4 from dual union all
select t.column5 from dual
) t

Related

Filter rows in Oracle select query (and not script or procedure)

I have code in my Oracle stored procedure like this:
SELECT COUNT(EMP_ID) INTO A_VARIABLE
FROM STUDENT;
IF (A_VARIABLE > 0) THEN
SELECT * FROM DEPARTMENT1;
ELSE
SELECT * FROM DEPARTMENT2;
END IF;
Basically I have to do this same filtration of rows in my SQL query (and not in the procedure or script).
Can anyone help with this Oracle query?
I have tried multiple solutions, but I'm not getting the desired output.
For 19c (19.7 and above) you may use SQL_MACRO(table) feature and inline function declaration.
But different output structure of the same query is somewhat misleading, so I would recommend to use union all approach and align columns of two tables.
Below is an example for SQL Macro.
Setup:
create table table1
as
select
level as id,
level as val
from dual
connect by level < 5
create table table2
as
select
level as id,
lpad(level, 4, '0') as val,
dbms_random.string('x', 5) as str2
from dual
connect by level < 4
create table decision
as
select 'Y' as res
from dual
Run 1:
with function f_decide_table
return varchar2
sql_macro(table)
as
l_res varchar2(100);
begin
select
coalesce (
max(q'[select * from table1]'),
q'[select * from table2]'
)
into l_res
from decision;
return l_res;
end;
select *
from f_decide_table()
order by 1
ID
VAL
1
1
2
2
3
3
4
4
Run 2:
truncate table decision
with function f_decide_table
return varchar2
sql_macro(table)
as
l_res varchar2(100);
begin
select
coalesce (
max(q'[select * from table1]'),
q'[select * from table2]'
)
into l_res
from decision;
return l_res;
end;
select *
from f_decide_table()
order by 1
ID
VAL
STR2
1
0001
DGNY9
2
0002
UHFYH
3
0003
EU12B
fiddle
Sample student table; depending on course students take, main query will return data from one table (or another).
SQL> select * from student;
ID NAME COURSE
1 Little Web
2 Foot Web
SQL> select * from department1;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
SQL> select * from department2;
DEPTNO DNAME LOC
---------- -------------- -------------
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>
Query uses a CTE that counts rows in student table; it is then used in exists subquery for two similar select statements which fetch rows from one of departments tables.
Currently, nobody is in IT so departments2 table it is:
SQL> with temp as
2 (select count(*) cnt
3 from student
4 where course = 'IT')
5 --
6 select * from department1
7 where exists (select null from temp
8 where cnt > 0
9 )
10 union all
11 select * from department2
12 where exists (select null from temp
13 where cnt = 0
14 );
DEPTNO DNAME LOC
---------- -------------- -------------
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>
However, if someone studies IT, department1 will provide data:
SQL> update student set course = 'IT' where id = 1;
1 row updated.
SQL> select * from student;
ID NAME COURSE
---------- ------ ----------
1 Little IT
2 Foot Web
SQL> with temp as
2 (select count(*) cnt
3 from student
4 where course = 'IT')
5 --
6 select * from department1
7 where exists (select null from temp
8 where cnt > 0
9 )
10 union all
11 select * from department2
12 where exists (select null from temp
13 where cnt = 0
14 );
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
SQL>
Closest thing to what you want to do (using just sql) would mean that you need to know all the column names and datatypes in both tables.
Next, you would have to do the datatype conversion for one union query as you have to select same number of columns with same datatypes.
The result would have all that you need but with some empty columns (those that do exist in other table but not in one you are selecting from).
Depending on total number of columns and on differences in names and types this coulld becomee too complicated and you realy shoud think about some function and reference queries.
Anyway, lets create some sample data to work with:
WITH -- sample data
students (ID, A_NAME) AS
(
Select 1, 'Mark' From Dual
),
dept_1 (ID, DEPT_NAME, DEPT_CITY) AS
(
Select '10', 'HR', 'NEW_YORK' From Dual Union All
Select '20', 'IT', 'BOSTON' From Dual
),
dept_2 (ID, DEPARTMENT) AS
(
Select 30, 'SALES' From Dual Union All
Select 40, 'PRODUCTION' From Dual
),
-- dept_1 has 3 columns (ID VARCHAR) - same column name different datatype
-- dept_2 has 2 columns (ID NUMBER)
-- The rest of the columns have different names and same datatype
create cte (namings) - forcing union of rows for all columns from both tables - dummy filling non existant and converting To_Char(ID) to get same datatype (this is needed for union to work)
-- here are total of 5 columns coded - it can be widen more if needed, this is just a sample to show handling of extra columns
namings AS
(
SELECT ROWNUM "RN", 'dept_1' "TABLE_NAME", Count(*) "CNT", To_Char(ID) "V1", DEPT_NAME "V2", DEPT_CITY "V3", 'X' "V4", 'X' "V5", 'ID' "C1", 'DEPT_NAME' "C2", 'DEPT_CITY' "C3", 'COL_X4' "C4", 'COL_X5' "C5"
From dept_1
Where CASE WHEN (Select Count(*) "CNT" From students Where ID = 999) > 0 THEN 1 ELSE 0 END = 1
Group By ROWNUM, ID, DEPT_NAME, DEPT_CITY
Union All
SELECT ROWNUM "RN", 'dept_2' "TABLE_NAME", Count(*) "CNT", To_Char(ID) "V1", DEPARTMENT "V2", 'X' "V3", 'X' "V4", 'X' "V5", 'ID' "C1", 'DEPARTMENT' "C2", 'COL_X3' "C3", 'COL_X4' "C4", 'COL_X5' "C5"
From dept_2
Where CASE WHEN (Select Count(*) "CNT" From students Where ID = 999) > 0 THEN 1 ELSE 0 END = 0
Group By ROWNUM,ID, DEPARTMENT
),
R e s u l t when count from students = 0 (filtered out with where clause)
RN TABLE_NAME CNT V1 V2 V3 V4 V5 C1 C2 C3 C4 C5
---------- ---------- ---------- ---------------------------------------- ---------- -------- -- -- -- ---------- --------- ------ ------
2 dept_2 1 40 PRODUCTION X X X ID DEPARTMENT COL_X3 COL_X4 COL_X5
1 dept_2 1 30 SALES X X X ID DEPARTMENT COL_X3 COL_X4 COL_X5
R e s u l t when count from students > 0
RN TABLE_NAME CNT V1 V2 V3 V4 V5 C1 C2 C3 C4 C5
---------- ---------- ---------- ---------------------------------------- ---------- -------- -- -- -- ---------- --------- ------ ------
1 dept_1 1 10 HR NEW_YORK X X ID DEPT_NAME DEPT_CITY COL_X4 COL_X5
2 dept_1 1 20 IT BOSTON X X ID DEPT_NAME DEPT_CITY COL_X4 COL_X5
create another cte (transposed) to unpivot the above data
transposed AS
( SELECT * FROM namings
UNPIVOT ( (NAMES, VALS) For COL_NAME IN( (C1, V1), (C2, V2), (C3, V3), (C4, V4), (C5, V5) ) )
WHERE VALS <> 'X'
)
R e s u l t when count from students = 0
RN TABLE_NAME CNT COL_NAME NAMES VALS
---------- ---------- ---------- -------- ---------- ----------------------------------------
2 dept_2 1 C1_V1 ID 40
2 dept_2 1 C2_V2 DEPARTMENT PRODUCTION
1 dept_2 1 C1_V1 ID 30
1 dept_2 1 C2_V2 DEPARTMENT SALES
R e s u l t when count from students > 0
RN TABLE_NAME CNT COL_NAME NAMES VALS
---------- ---------- ---------- -------- ---------- ----------------------------------------
1 dept_1 1 C1_V1 ID 10
1 dept_1 1 C2_V2 DEPT_NAME HR
1 dept_1 1 C3_V3 DEPT_CITY NEW_YORK
2 dept_1 1 C1_V1 ID 20
2 dept_1 1 C2_V2 DEPT_NAME IT
2 dept_1 1 C3_V3 DEPT_CITY BOSTON
Main sql - pivoting data to columns from both tables
SELECT DISTINCT
RN,
TABLE_NAME,
MAX(ID_V) OVER(Partition By RN) "ID",
MAX(DEPT_NAME_V) OVER(Partition By RN) "DEPT_NAME",
MAX(DEPT_CITY_V) OVER(Partition By RN) "DEPT_CITY",
MAX(DEPARTMENT_V) OVER(Partition By RN) "DEPARTMENT"
FROM (
SELECT * FROM transposed
PIVOT (
COUNT(NAMES), MAX(VALS) "V" FOR NAMES IN('ID' "ID", 'DEPT_NAME' "DEPT_NAME", 'DEPT_CITY' "DEPT_CITY", 'DEPARTMENT' "DEPARTMENT")
)
)
ORDER BY RN
R e s u l t when count from students = 0
RN TABLE_NAME ID DEPT_NAME DEPT_CITY DEPARTMENT
---------- ---------- ---------------------------------------- ---------------------------------------- ---------------------------------------- ----------------------------------------
1 dept_2 30 SALES
2 dept_2 40 PRODUCTION
R e s u l t when count from students > 0
RN TABLE_NAME ID DEPT_NAME DEPT_CITY DEPARTMENT
---------- ---------- ---------------------------------------- ---------------------------------------- ---------------------------------------- ----------------------------------------
1 dept_1 10 HR NEW_YORK
2 dept_1 20 IT BOSTON

I want to get NULL when the string in my column is not available How can I achieve this using SQL?

Column
aaa-xyz-bbb
xyz-mmm-ooo
aaa-ttt-eee
How to achieve this in Oracle sql
Out put
xyz
xyz
Null
One option is to use instr function:
Sample data:
SQL> with test (col) as
2 (select 'aaa-xyz-bbb' from dual union all
3 select 'xyz-mmm-ooo' from dual union all
4 select 'aaa-ttt-eee' from dual
5 )
Query:
6 select col,
7 case when instr('-' || col || '-', '-xyz-') > 0 then 'xyz'
8 else 'Null'
9 end result
10 from test;
COL RESULT
----------- ----------
aaa-xyz-bbb xyz
xyz-mmm-ooo xyz
aaa-ttt-eee Null
SQL>

Error while using listagg in oracle "STRING concatenation too long"

I have a table like temp_a which has:
acc ai_tab where
A B QQQQ
A B RRRR
C D SSSS
C D TTTT
The column where has too large string stored. so,My expected output is
acc ai_tab where
A B QQQQ RRRR
C D SSSS TTTT
I tried to achieve this using:
select acc,ai_tab,LISTAGG(WHERE,'') WITHIN GROUP ORDER BY (acc) "where_cond2" from temp_a
group by acc,ai_tab;
I got the error as:
ORA-01489 :result of string concatenation is too long.
I searched this similar question and its says use the XMLCLOB also but its not working?Can we use function to get this or is there any other methods?
Here's an example which shows both options you might use; as listagg fails, try xmlagg instead.
SQL> SELECT RTRIM (
2 XMLAGG (XMLELEMENT (e, ename || ' ') ORDER BY empno).EXTRACT (
3 '//text()'),
4 ', ')
5 employees_1,
6 --
7 LISTAGG (ename, ' ') WITHIN GROUP (ORDER BY empno) employees_2
8 FROM emp
9 WHERE deptno = 10;
EMPLOYEES_1 EMPLOYEES_2
------------------------------ ------------------------------
CLARK KING MILLER CLARK KING MILLER
SQL>
Agree with Littlefoot's solution.
Alternatively, you could perhaps write your own function. I've included a sample below.
Note: I haven't tested this with your data, so not sure how well this would perform
SQL> create table foo as
select 'A' acc, 'B' ai_tab, 'QQQQ' where_column from dual union all
select 'A','B','RRRR' from dual union all
select 'C','D','SSSS' from dual union all
select 'C','D','TTTT' from dual;
Table created.
SQL> select * from foo;
ACC AI_TAB WHERE_COLUMN
-- -- ----
A B QQQQ
A B RRRR
C D SSSS
C D TTTT
SQL> create or replace function str_concat (
p_acc in foo.acc%type,
p_ai_tab in foo.ai_tab%type
)
return clob
is
l_concat clob;
begin
for o in (
select where_column from foo where acc = p_acc and ai_tab = p_ai_tab
) loop
l_concat := l_concat || ' ' || o.where_column;
end loop;
return ltrim(l_concat, ' ');
end;
/
Function created.
SQL> select acc, ai_tab, where_column from (
select acc, ai_tab, str_concat(acc, ai_tab) where_column, row_number() over (partition by acc, ai_tab order by null)
) where rn = 1;
ACC AI_TAB WHERE_COLUMN
-- -- ----
A B QQQQ RRRR
C D SSSS TTTT

How do we pass a complete sql query from a table to an insert statement in PL/SQL using execute immediate

I'm trying to use the SQL queries stored in a table, in a insert statement inside the procedure.
Below is the statement_table,
STMT_ID STATMENT1 STATEMENT2
S001 INSERT INTO TABLE1 (S_ID, REQUEST_NUM,CASE_ID,C1,C2) select min(s_id) s_id, REQUEST_NUM, CASE_ID, trim(c1),
ABS(max(case when source = 'S1' then c2 end) +
max(case when source = 'S2' then -c2 end))
from TABLE2 where REQUEST_NUM =REQUEST_IN
group by REQUEST_NUM , CASE_ID, trim(c1)
order by S_ID
Below is the procedure block,
execute immediate 'select '||STATEMENT1||'+'||STATEMENT2||' FROM statement_table where stmt_id='S001';
The result that I am trying to get is,
INSERT INTO TABLE1 (S_ID, REQUEST_NUM,CASE_ID,C1,C2)
select min(s_id) s_id, REQUEST_NUM, CASE_ID, trim(c1),
ABS(max(case when source = 'S1' then c2 end) +
max(case when source = 'S2' then -c2 end))
from TABLE2 where REQUEST_NUM =REQUEST_IN
group by REQUEST_NUM , CASE_ID, trim(c1)
order by S_ID
Please help.
Here's how.
First, your (actually, my) statements. I'm going to insert a row into Scott's DEPT table.
SQL> select * from test;
ID
----------
ST1
--------------------------------------------------------------------------------
ST2
--------------------------------------------------------------------------------
1
insert into dept (deptno, dname, loc)
select max(deptno) + 1, 'stack', 'zoom' from dept order by 1
SQL> select * From dept;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
1 x y
Code; DBMS_OUTPUT is used to verify whether the statement is correct or not.
SQL> set serveroutput on
SQL> declare
2 l_st1 test.st1%type;
3 l_st2 test.st2%type;
4 l_str varchar2(1000);
5 begin
6 select st1, st2
7 into l_st1, l_st2
8 from test
9 where id = 1;
10
11 l_str := l_st1 ||' '|| l_st2;
12 dbms_output.put_line(l_str);
13 execute immediate l_str;
14 end;
15 /
insert into dept (deptno, dname, loc) select max(deptno) + 1, 'stack', 'zoom'
from dept order by 1
PL/SQL procedure successfully completed.
Result:
SQL> select * from dept;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
41 stack zoom --> this was inserted
1 x y
6 rows selected.
SQL>

Select all columns except null or NVL all null

Is any way to select all columns in a record where fields are not null or NVL the null fields to ''?
Im working on ORACLE
Something like this
SELECT * IS NOT NULL
FROM table
WHERE id_table ='001'
or like this
SELECT NVL(*,'')
FROM table
WHERE id_table ='001'
Just reverse the NULL logic in the demonstration about Find all columns having at least a NULL value from all tables in the schema.
For example,
FIND_NULL_COL is a simple user defined function(UDF) which will return 1 for the column which has at least one NULL value :
SQL> CREATE OR REPLACE
2 FUNCTION FIND_NULL_COL(
3 TABLE_NAME VARCHAR2,
4 COLUMN_NAME VARCHAR2)
5 RETURN NUMBER
6 IS
7 cnt NUMBER;
8 BEGIN
9 CNT :=1;
10 EXECUTE IMMEDIATE 'select count(1) from '
11 ||TABLE_NAME||' where ' ||COLUMN_NAME||' is not null
12 and deptno = 20' INTO cnt;
13 RETURN
14 CASE
15 WHEN CNT=0 THEN
16 1
17 ELSE
18 0
19 END;
20 END;
21 /
Function created.
Call the function in SQL to get the NULL status of all the column of any table :
SQL> SET pagesize 1000
SQL> column owner format A10;
SQL> column column_name format A20;
SQL> COLUMN TABLE_NAME FORMAT A20;
SQL> column n format A1;
SQL> SELECT c.OWNER,
2 c.TABLE_NAME,
3 c.COLUMN_NAME,
4 C.NULLABLE,
5 FIND_NULL_COL(c.TABLE_NAME,c.COLUMN_NAME) null_status
6 FROM all_tab_columns c
7 WHERE C.OWNER =USER
8 AND c.TABLE_NAME = 'EMP'
9 ORDER BY C.OWNER,
10 C.TABLE_NAME,
11 C.COLUMN_ID
12 /
OWNER TABLE_NAME COLUMN_NAME N NULL_STATUS
---------- -------------------- -------------------- - -----------
SCOTT EMP EMPNO N 0
SCOTT EMP ENAME Y 0
SCOTT EMP JOB Y 0
SCOTT EMP MGR Y 0
SCOTT EMP HIREDATE Y 0
SCOTT EMP SAL Y 0
SCOTT EMP COMM Y 1
SCOTT EMP DEPTNO Y 0
8 rows selected.
SQL>
So, NULL_STATUS "1" is the column which has NULL value(s).