Need SQL query/script to give me a distinct count per column for every column in one table - sql

I need a query/script to just display a distinct count of values in each column of a table. I'm using it to tie out to a legacy report where each column is a two way freq of the column by disctinct count. Something like below:
select distinct field1,count(*)
from EBL_CLIENT.EAP_FACT
where run_id = '205572'
select distinct field2,count(*)
from EBL_CLIENT.EAP_FACT
where run_id = '205572'
select distinct fieldetc...,count(*)
from EBL_CLIENT.EAP_FACT
where run_id = '205572'

Please find below script for generating SQL query:
declare
v_col varchar2(64) := 'run_id';
v_val varchar2(64) := '205572';
v_table varchar2(64) := 'EAP_FACT';
v_schema varchar2(64) := 'EBL_CLIENT';
begin
dbms_output.put_line('select *'||chr(10)||'from (select ');
for i in (select t.COLUMN_NAME, rownum rn
from all_tab_columns t
where t.TABLE_NAME = upper(v_table)
and t.OWNER = upper(v_schema)
and t.COLUMN_NAME <> upper(v_col)
order by t.COLUMN_ID)
loop
dbms_output.put_line(' '||case when i.rn=1 then ' ' else ',' end||
'count(distinct '||i.column_name||') '||i.column_name);
end loop;
dbms_output.put_line(' from '||v_schema||'.'||v_table||' t where t.'||v_col||' = '''||v_val||''')'
||chr(10)||'unpivot'||chr(10)||'(cnt');
for i in (select listagg (t.COLUMN_NAME,',') within group (order by t.COLUMN_ID) lst
from all_tab_columns t
where t.TABLE_NAME = upper(v_table)
and t.OWNER = upper(v_schema)
and t.COLUMN_NAME <> upper(v_col))
loop
dbms_output.put_line(' '||'for col in ('||i.lst||')');
end loop;
dbms_output.put_line(')'||chr(10)||'order by cnt desc');
end;
You will get some query like this:
select *
from (select
count(distinct t.field1) field1
,count(distinct t.field2) field2
,count(distinct t.field3) field3
from EBL_CLIENT.EAP_FACT t where t.run_id = '205572')
unpivot
(cnt
for col in (field1,field2,field3)
)
order by cnt desc
And after run this query result will be like this:
col cnt
field2 5
field1 3
field3 1

This would give the results for each column in a single row
select Field1Count = count(distinct field1)
,Field2Count = count(distinct field2)
,fieldetcCount = count(fieldetc)
from EBL_CLIENT.EAP_FACT
where run_id = '205572'

I don't know if this helps as it will still run 117 queries but you won't have to manually create them. Run the query which will return 117 select statements. Copy them and run them to get the counts.
SELECT 'SELECT ''' || COLUMN_NAME || ''' AS ColumnName , COUNT(DISTINCT '
|| COLUMN_NAME || ') AS Count FROM ' || Table_Schema || '.' || Table_Name
FROM INFORMATION_SCHEMA.columns
WHERE TABLE_SCHEMA = 'EBL_CLIENT'
AND TABLE_NAME = 'EAP_FACT'

Related

sqlplus SELECT from the selected result

I am using Oracle sqlplus and am trying to use the result being selected from the codes below and select the column again from Table_1
SELECT *
FROM
(
SELECT Column_Name
FROM All_Tab_columns
WHERE Table_Name=UPPER('Table_1')
)
INTERSECT
(
SELECT Column_Name
FROM All_Tab_columns
WHERE Table_Name=UPPER('Table_2')
);
How can I perform something like that:
SELECT <Columns_That_Intersected>
FROM Table_1;
Is it possible to store the Columns_That_Intersected in a variable/function/procedure and use it again for other select statement?
Yes, it's possible. Just save the aggregated result of the query in a substitution variable. Something like this:
create table items1 as
select rownum id, 'me '||rownum name, 1 dummy
from xmlTable ('1 to 3');
create table items2 as select id, name from items1;
set verify off
col Columns_That_Intersected new_value Columns_That_Intersected noprint
select listagg (column_name, ',') within group (order by null) Columns_That_Intersected
from (
select column_name
from All_Tab_Columns
where Table_Name=UPPER('items1')
intersect
select column_name
from All_Tab_columns
where Table_Name=UPPER('items2')
);
prompt Columns_That_Intersected=&Columns_That_Intersected
select &Columns_That_Intersected
from items1;
Output:
Columns_That_Intersected=ID,NAME
ID NAME
---------- -------------------------------------------
1 me 1
2 me 2
3 me 3
About col[umn] command
Just to fetch the column names which exists in both the table table1 and table2, you can use below query:
SELECT Column_Name
FROM all_tab_columns t1
WHERE table_name = 'Table1'
AND EXISTS (SELECT 1
FROM all_tab_columns t2
WHERE table_name = 'Table2'
AND t2.Column_Name = t1.Column_Name);
Then to fetch these column values from Table1, you can use below PL/SQL construct:
DECLARE
v_sql_statement VARCHAR2(2000);
v_cols VARCHAR2(2000);
BEGIN
FOR cn IN (SELECT Column_Name
FROM all_tab_columns t1
WHERE table_name = 'Table1'
AND EXISTS (SELECT 1
FROM all_tab_columns t2
WHERE table_name = 'Table2'
AND t2.Column_Name = t1.Column_Name))
LOOP
v_cols := v_cols || ', ' || cn.column_name;
END LOOP;
v_cols := ltrim(v_cols, ',');
v_sql_statement := 'SELECT ' || v_cols || ' FROM Table1';
EXECUTE IMMEDIATE v_sql_statement;
END;
you can, for example, work with a dynamic sql.
The result of your selects can be aggregated to a list using the listagg function
SELECT listagg( Column_Name,',') WITHIN GROUP (ORDER BY 1) cols
FROM
(
SELECT Column_Name
FROM All_Tab_columns
WHERE Table_Name=UPPER('tab1')
INTERSECT
SELECT Column_Name
FROM All_Tab_columns
WHERE Table_Name=UPPER('tab2')
);
As result you get then eg. col1, col2, col3
with the help of this list you can then create a cursor in a plsql block and then loop through it to print out wat you want.
declare
v_cols VARCHAR2(2000);
v_select_string varchar(2000);
csr SYS_REFCURSOR;
v1 VARCHAR2(2000);
BEGIN
SELECT listagg( Column_Name,',') WITHIN GROUP (ORDER BY 1) cols
into v_cols
FROM
(
SELECT Column_Name
FROM All_Tab_columns
WHERE Table_Name=UPPER('tab1')
INTERSECT
SELECT Column_Name
FROM All_Tab_columns
WHERE Table_Name=UPPER('tab2')
);
v_select_string := 'SELECT ' || v_cols ||
' FROM tab1';
OPEN csr FOR v_select_string;
LOOP
FETCH csr INTO v1;
EXIT WHEN csr%NOTFOUND;
DBMS_OUTPUT.PUT_LINE( v1);
END LOOP;
CLOSE csr;
END P1;

CREATE AS SELECT * but with one column obtained from another table

I need to 'recreate' over 50 tables (in Oracle) with CREATE AS SELECT statements. However, all this tables will have one column modified using data from another table. Is there a way to achieve this without declaring each column in the SELECT statement?
Something like:
CREATE TABLE table_name_copy AS SELECT *, (SELECT col_name FROM other_table WHERE other_table.col_id = table_name.col_id) AS col_name FROM table_name`
Basically on all tables I have a column which needs to be replaced with the data in the other_table column.
Generate the SQL string as such:
SELECT 'CREATE TABLE table_name_copy AS SELECT '
|| LISTAGG (column_name, ', ') WITHIN GROUP (ORDER BY column_name)
|| ', (SELECT col_name FROM other_table
WHERE other_table.col_id = table_name.col_id) AS col_name'
|| ' FROM table_name'
FROM all_tab_cols
WHERE owner = 'OWNER'
AND table_name = 'TABLE_NAME'
AND column_name != 'COL_NAME'
If you want to run the above statement, you could use EXECUTE IMMEDIATE:
DECLARE
v_sql VARCHAR2(10000);
BEGIN
SELECT 'CREATE TABLE table_name_copy AS SELECT '
|| LISTAGG (column_name, ', ') WITHIN GROUP (ORDER BY column_name)
|| ', (SELECT col_name FROM other_table
WHERE other_table.col_id = table_name.col_id) AS col_name'
|| ' FROM table_name'
INTO v_sql
FROM all_tab_cols
WHERE owner = 'OWNER'
AND table_name = 'TABLE_NAME'
AND column_name != 'COL_NAME';
EXECUTE IMMEDIATE v_sql;
END;
/
If col_id column is fixed for both of the joined tables,
you may use user_tab_columns and user_tables dictionary views through the schema to produce new tables named as "table_name_copy" by using the following mechanism :
declare
v_ddl varchar2(4000);
v_cln varchar2(400);
begin
for c in ( select *
from user_tables t
where t.table_name in
( select c.table_name
from user_tab_columns c
where c.column_name = 'COL_ID' )
order by t.table_name )
loop
v_ddl := 'create table '||c.table_name||'_copy as
select ';
for d in ( select listagg('t1.'||column_name, ',') within group ( order by column_name ) cln
from user_tab_columns
where table_name = c.table_name
and column_name != 'COL_ID' )
loop
v_cln := v_cln||d.cln;
end loop;
v_ddl := v_ddl||v_cln;
v_ddl := v_ddl||', t2.col_id t2_id
from '||c.table_name||' t1
left outer join other_table t2 on ( t1.col_id = t2.col_id )';
execute immediate v_ddl;
v_ddl := null;
v_cln := null;
end loop;
end;
Maybe you can use a simple join and an asterisk to return all columns from the first table, like that:
CREATE TABLE table_name_copy AS
SELECT * FROM (
SELECT tab1.*, tab2.column_name
FROM table_name tab1 LEFT JOIN other_table tab2 ON tab1.col_id = tab2.col_id
);
I would try this (but I don't have Oracle SQL to test on so please leave me the benefit of the doubt)
CREATE TABLE table_name_copy AS
SELECT * FROM (
SELECT *, (SELECT col_name FROM other_table WHERE other_table.col_id = table_name.col_id) as col_name
FROM table_name`
)
edit:
then run
ALTER TABLE table_name_copy DROP COLUMN <old column>
to remove the column you don't need any more

plsql List all table.column containing null values

I'd like to find all column of a set of table with null values in them.
I can find the table and column names
SELECT TABLE_NAME, COLUMN_NAME
FROM user_tab_cols
where nullable='Y'
and table_name in ('myTalbe', 'myTable2');
And also check if the are nulls
select count(*) from myTable where myColumn is null;
but how can I put this toghether to have as result
table_name column_name
myTable myColumn
myTable myCol2
myTable2 col4
An approach could be with some dynamic SQL, but this would require some PL/SQL code; the following only uses SQL to get the result you need:
select *
from (
select table_name,
column_name,
to_number(extractvalue(xmltype(dbms_xmlgen.getxml('select count(*) c from '||table_name || ' where ' || column_name || ' is null')),'/ROWSET/ROW/C')) as rowcount
from user_tab_columns
where nullable='Y'
and table_name in ('myTalbe', 'myTable2')
)
where rowcount > 0
This could be an approach with dynamic SQL:
declare
type tableOfNumber is table of number;
type tableOfChar is table of varchar2(30);
--
vSQl varchar2(4000);
vListNumbers tableOfNumber;
vListTables tableOfChar;
vListColumns tableOfChar;
begin
select listagg( 'select ''' ||
table_name || ''' as table_name, ''' ||
column_name || ''' as column_name, count(*) as rowCount from ' ||
table_name ||
' where ' ||
column_name ||
' is null having count(*) > 0' ,
' UNION ALL '
) within group ( order by table_name, column_name)
into vSQL
from user_tab_columns
where nullable='Y'
and table_name in ('myTalbe', 'myTable2');
--
dbms_output.put_line(vSQL);
/* whatever you may want to do with the query */
/* for example, fetch into some variables and print the result */
execute immediate vSQL
bulk collect into vListTables, vListColumns, vListNumbers;
--
if vListTables.count() > 0 then
for i in vListTables.first .. vListTables.last loop
dbms_output.put_line('Table ' || vListTables(i) ||
', column ' || vListColumns(i) ||
', number of nulls: ' || vListNumbers(i)
);
end loop;
end if;
end;
Here's a routine I wrote a while back to do exactly that. It will output the required DDL to make those columns that are nullable (but do not contain any nulls) not nullable.
https://connormcdonald.wordpress.com/2016/03/11/tightening-up-your-data-model/
It can do the task for a schema or a single table.

A more efficient way to do a select insert that involves over 300 columns?

I am trying to find a more efficient way to write PL/SQL Query to
to select insert from a table with 300+ columns, to the back up version of that table (same column names + 2 extra columns).
I could simply type out all the column names in the script (example below), but with that many names, it will bother me... :(
INSERT INTO
TABLE_TEMP
(column1, column2, column3, etc)
(SELECT column1, column2, column3, etc FROM TABLE WHERE id = USER_ID);
Thanks in advance
Specify literals/null for those two extra columns.
INSERT INTO
TABLE_TEMP
SELECT t1.*, null, null FROM TABLE t1 WHERE id = USER_ID
You can pretty easily build a column list for any given table:
select table_catalog
,table_schema
,table_name
,string_agg(column_name, ', ' order by ordinal_position)
from information_schema.columns
where table_catalog = 'catalog_name'
and table_schema = 'schema_name'
and table_name = 'table_name'
group by table_catalog
,table_schema
,table_name
That should get you nearly where you need to be.
The question tag says plsql, which is Oracle or one of its variants. Here is an example of doing it in Oracle:
drop table brianl.deleteme1;
drop table brianl.deleteme2;
CREATE TABLE brianl.deleteme1
(
a INTEGER
, b INTEGER
, c INTEGER
, efg INTEGER
);
CREATE TABLE brianl.deleteme2
(
b INTEGER
, c INTEGER
, d INTEGER
, efg INTEGER
);
DECLARE
l_ownerfrom VARCHAR2 (30) := 'BRIANL';
l_tablefrom VARCHAR2 (30) := 'DELETEME1';
l_ownerto VARCHAR2 (30) := 'BRIANL';
l_tableto VARCHAR2 (30) := 'DELETEME2';
l_comma VARCHAR2 (1) := NULL;
BEGIN
DBMS_OUTPUT.put_line ('insert into ' || l_ownerto || '.' || l_tableto || '(');
FOR eachrec IN ( SELECT f.column_name
FROM all_tab_cols f INNER JOIN all_tab_cols t ON (f.column_name = t.column_name)
WHERE f.owner = l_ownerfrom
AND f.table_name = l_tablefrom
AND t.owner = l_ownerto
AND t.table_name = l_tableto
ORDER BY f.column_name)
LOOP
DBMS_OUTPUT.put_line (l_comma || eachrec.column_name);
l_comma := ',';
END LOOP;
DBMS_OUTPUT.put_line (') select ');
l_comma := NULL;
FOR eachrec IN ( SELECT f.column_name
FROM all_tab_cols f INNER JOIN all_tab_cols t ON (f.column_name = t.column_name)
WHERE f.owner = l_ownerfrom
AND f.table_name = l_tablefrom
AND t.owner = l_ownerto
AND t.table_name = l_tableto
ORDER BY f.column_name)
LOOP
DBMS_OUTPUT.put_line (l_comma || eachrec.column_name);
l_comma := ',';
END LOOP;
DBMS_OUTPUT.put_line (' from ' || l_ownerfrom || '.' || l_tablefrom || ';');
END;
This results in this output:
insert into BRIANL.DELETEME2(
B
,C
,EFG
) select
B
,C
,EFG
from BRIANL.DELETEME1;
Nicely formatted:
INSERT INTO brianl.deleteme2 (b, c, efg)
SELECT b, c, efg
FROM brianl.deleteme1;

sql query to find table_name and count(table_name) in a schema

sql query to find table_name and count(table_name) in a schema:
Example:
I get the table_name from this query:
SELECT * FROM USER_TABLES
and count(table_name) from
select count(*) from employee
select count(*) from dept
select count(*) from subjects
now i want to get the result like this:
table_name count(table_name)
Employee 100
dept 21
subjects 56
Try like this
select
table_name,
to_number(
extractvalue(
xmltype(
dbms_xmlgen.getxml('select count(*) c ' ||
' from '||owner||'.'||table_name))
,'/ROWSET/ROW/C')) count
from all_tables
where table_name In (
SELECT table_name from user_tables )
OR Using Join
select
a.table_name,
to_number(
extractvalue(
xmltype(
dbms_xmlgen.getxml('select count(*) c ' ||
' from '||owner||'.'||a.table_name))
,'/ROWSET/ROW/C')) count
from all_tables a JOIN user_tables u ON
a.table_name=u.table_name AND a.owner = user
Looks like a typical use case for a simple UNION ALL:
select
'Employee' table_name,
count(*) from employee
union all
select
'dept' table_name,
count(*) from dept
union all
select
'subjects' table_name,
count(*) from subjects
If you want to automate this, you can iterate over USER_TABLES:
declare
l_cnt pls_integer;
begin
for cur in (select table_name from user_tables order by table_name)
loop
execute immediate 'select count(*) from ' || cur.table_name into l_cnt;
dbms_output.put_line(cur.table_name || ' ' || l_cnt);
end loop;
end;
Instead of simply printing the result, you can also build a SQL statement dynamically and use that afterwards:
declare
l_sql varchar2(4000);
begin
for cur in (select table_name from user_tables order by table_name)
loop
l_sql := l_sql || ' select ' || cur.table_name || ' as table_name, count(*) as cnt from ' || cur.table_name || ' union all';
end loop;
-- remove trailing UNION ALL
l_sql := regexp_replace(l_sql, 'union all$', '');
dbms_output.put_line(l_sql);
end;