Select all columns except null or NVL all null - sql

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

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

Count specific match from complete table data in Oracle DB

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

SQL - Selecting only columns containing sum(data)

This are few records that I want to have sum of but excluding columns with NULL values:
ID val1 val2 val3
1 1 2
2 0 3
3 2 4
select
sum(val1),
sum(val2),
sum(val3)
from table
will result in:
val1 val2 val3
3 9
but I would prefer to see:
val2 val3
3 9
Is there anyway to achive that in SQL or PL/SQL?
kind regards
Sample data:
SQL> select * from test;
ID VAL1 VAL2 VAL3
---------- ---------- ---------- ----------
1 1 2
2 0 3
3 2 4
In my opinion, this is just fine:
SQL> select sum(val1), sum(val2), sum(val3) from test;
SUM(VAL1) SUM(VAL2) SUM(VAL3)
---------- ---------- ----------
3 9
SQL>
Why? Because what you prefer requires some programming and that doesn't have to be simple. Your example is simple, so something like this is easy as there are 6 possible combinations:
SQL> create or replace function f_test return sys_refcursor
2 is
3 l_sum1 number;
4 l_sum2 number;
5 l_sum3 number;
6 rc sys_refcursor;
7 begin
8 select sum(val1), sum(val2), sum(val3)
9 into l_sum1 , l_sum2 , l_sum3
10 from test;
11
12 if l_sum1 is null and l_sum2 is null and l_sum3 is null then
13 open rc for select null from dual;
14 elsif l_sum1 is null and l_sum2 is null then
15 open rc for select l_sum3 from dual;
16 elsif l_sum1 is null and l_sum3 is null then
17 open rc for select l_sum2 from dual;
18 elsif l_sum2 is null and l_sum3 is null then
19 open rc for select l_sum1 from dual;
20 elsif l_sum1 is null then
21 open rc for select l_sum2, l_sum3 from dual;
22 elsif l_sum2 is null then
23 open rc for select l_sum1, l_sum3 from dual;
24 elsif l_sum3 is null then
25 open rc for select l_sum1, l_sum2 from dual;
26 end if;
27 return rc;
28 end;
29 /
Function created.
SQL>
Testing:
SQL> select f_test from dual;
F_TEST
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
:B2 :B1
---------- ----------
3 9
SQL> update test set val3 = null;
3 rows updated.
SQL> select f_test from dual;
F_TEST
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
:B1
----------
3
In a more complex situation, with more columns, such an approach would be close to suicide so you'd have to do that differently, use dynamic SQL which makes things not that easy, so the question is: is it worth it? I don't think so.

How to restrict output of following query to only be of datatype number/integer by joining with ALL_TAB_COLS? - Oracle SQL

Essentially need the following query to only output a column name of datatype Number/Integer? How would I restrict the output to only a specific data type?
select column_name, num_distinct
from all_tab_col_statistics
where table_name = 'MY_TABLE_NAME'
order by num_distinct desc
fetch first row with ties;
I'm aware of the fact that doing this involves joining with the ALL_TAB_COLS table but I'm not sure how I would implement this given my current query.
Any help would be appreciated!
Join ALL_TAB_COL_STATISTICS to ALL_TAB_COLUMNS as it contains columns' datatypes.
If you want to include several tables, a simple way is to use then IN clause and name all tables you're interested in.
For example:
SQL> select c.table_name, c.column_name, c.data_type, s.num_distinct
2 from all_tab_columns c join all_tab_col_statistics s on
3 s.table_name = c.table_name and s.column_name = c.column_name
4 where c.table_name in ('EMP', 'DEPT')
5 and c.data_type in ('NUMBER', 'VARCHAR2')
6 order by c.table_name, c.column_name;
TABLE_NAME COLUMN_NAME DATA_TYPE NUM_DISTINCT
---------- --------------- --------------- ------------
DEPT DEPTNO NUMBER 4
DEPT DNAME VARCHAR2 4
DEPT LOC VARCHAR2 4
EMP COMM NUMBER 4
EMP DEPTNO NUMBER 3
EMP EMPNO NUMBER 14
EMP ENAME VARCHAR2 14
EMP JOB VARCHAR2 5
EMP MGR NUMBER 6
EMP SAL NUMBER 12
10 rows selected.
SQL>
If you want to select only columns that have the highest NUM_DISTINCT values per each table, use current query as a CTE (Common Table Expression a.k.a. the WITH factoring clause), analytical function (such as RANK) to find the one(s) you're interested in. Something like this:
SQL> with data as
2 (select c.table_name, c.column_name, c.data_type, s.num_distinct,
3 --
4 rank() over (partition by c.table_name order by s.num_distinct desc) rnk
5 from all_tab_columns c join all_tab_col_statistics s on
6 s.table_name = c.table_name and s.column_name = c.column_name
7 where c.table_name in ('EMP', 'DEPT')
8 and c.data_type in ('NUMBER', 'VARCHAR2')
9 )
10 select table_name, column_name, data_type, num_distinct
11 from data
12 where rnk = 1;
TABLE_NAME COLUMN_NAME DATA_TYPE NUM_DISTINCT
---------- --------------- --------------- ------------
DEPT LOC VARCHAR2 4
DEPT DNAME VARCHAR2 4
DEPT DEPTNO NUMBER 4
EMP ENAME VARCHAR2 14
EMP EMPNO NUMBER 14
SQL>
It says that
in table DEPT, there are 3 columns that share the same MAX value (4)
in table EMP, there are 2 such columns (MAX value = 14)

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>