I need to display all tables that have zero records.
I tried,
select * from user_all_tables where (select count(*) from user_all_tables)=0;
But it doesn't seem to work.
How should I go about redesigning this query?
Thanks.
If all of your tables are analyzed, you can check the column num_rows of table user_tables.
Otherwise, you will need PL/SQL to make this work. This will output all tables of your current user without records (use all_tables if you need tables of other users):
Set Serveroutput On;
Declare
cnt PLS_INTEGER;
Begin
For c In ( Select table_name From user_tables ) Loop
Execute Immediate 'Select Count(*) From "' || c.table_name || '" where rownum=1'
Into cnt;
If( cnt = 0 ) Then
dbms_output.put_line( c.table_name );
End If;
End Loop;
End;
You'd have to resort to PL/SQL and issue a select count(*) for every table. Or you can use dbms_xmlgen to do this for you in a tricky way:
select table_name
from ( select table_name
, extractvalue
( dbms_xmlgen.getxmltype('select count(*) c from '|| table_name)
, '/ROWSET/ROW/C'
) cnt
, rownum to_prevent_predicate_push
from user_tables
)
where cnt = '0'
Regards,
Rob.
Variation of the accepted answer but using a more efficient method.
Declare
cnt PLS_INTEGER;
Begin
For c In ( Select table_name From user_tables ) Loop
begin
Execute Immediate 'Select 1 From dual where exists (select 1 from ' || c.table_name ||')' Into cnt;
exception when no_data_found then
dbms_output.put_line( c.table_name );
end;
End Loop;
End;
select TABLE_NAME
from USER_ALL_TABLES
where NUM_ROWS = 0
This answer is one Fetch-per-table more efficient than Rene's. SELECT INTO requires an Extra Fetch to see if the "TOO_MANY_ROWS" exception should be raised. We can take control of that process with an explicit cursor and NOT doing an unnecessary extra fetch.
Declare
cnt PLS_INTEGER;
s_Cur Varchar2(255);
c_Cur Sys_refcursor;
Begin
For c In ( Select table_name From user_tables ) Loop
s_Cur := 'Select 1 From dual where exists (select 1 from ' || c.table_name ||')';
Open c_Cur For s_cur ;
Fetch c_cur into cnt;
If c_cur%NOTFOUND then
dbms_output.put_line( c.table_name );
end if;
End Loop;
End;
Related
I need to search in a large DB a table that matches with a column name, but this table must have more than 0 rows.
Here is the query by the way:
SELECT * FROM all_tab_columns WHERE column_name LIKE '%ID_SUPPORT%';
You could use single query to filter names and get actual number of rows:
SELECT owner, table_name, cnt
FROM all_tab_columns, XMLTABLE('/ROWSET/ROW' passing
(dbms_xmlgen.getxmltype(REPLACE(REPLACE(
'select COUNT(*) AS cnt from <owner>.<table_name>', '<owner>', owner)
, '<table_name>', table_name))) COLUMNS cnt INT)
WHERE column_name LIKE '%ID_SUPPORT%' AND cnt > 0;
DBFiddle Demo
Any chance this can be expanded/tweaked to yield the values of the first few rows for all tables?
Yes, by flattening row using JSON_ARRAYAGG(JSON_OBJECT(*)) Oracle 19c:
-- generic approach Oracle 19c
SELECT owner, table_name, cnt, example
FROM all_tab_columns, XMLTABLE('/ROWSET/ROW' passing
(dbms_xmlgen.getxmltype(REPLACE(REPLACE(
'select COUNT(*) AS cnt,
MAX((SELECT JSON_ARRAYAGG(JSON_OBJECT(*))
FROM <owner>.<table_name>
WHERE rownum < 10) -- taking up to 10 rows as example
) as example
from <owner>.<table_name>', '<owner>', owner)
, '<table_name>', table_name)))
COLUMNS cnt INT
, example VARCHAR2(1000))
WHERE column_name LIKE '%ID_SUPPORT%'
AND cnt > 0;
Demo contains hardcoded column list inside JSON_OBJECT. Oracle 19c and JSON_OBJECT(*) would allow any column list per table.
db<>fiddle demo
How it works:
find all tables that have column named '%ID_SUPPORT'
run query per table using dbms_xml_gen.getxmltype
in sub query count the rows, flatten few rows an example to JSON
return rows that have at least one record table
One way:
SELECT * FROM all_tables WHERE num_rows > 0
AND table_name in (SELECT table_name FROM all_tab_columns WHERE column_name LIKE '%ID_SUPPORT%')
If your DB is periodically analyzed the direct way is to use the following SQL :
SELECT *
FROM all_tables t
WHERE t.table_name LIKE '%ID_SUPPORT%'
and t.num_rows > 0;
More precise way to determine is using the following :
declare
v_val pls_integer := 0;
begin
for c in (
SELECT *
FROM all_tables t
WHERE t.table_name LIKE '%ID_SUPPORT%'
)
loop
execute immediate 'select count(1) from '||c.owner||'.'||c.table_name into v_val;
if v_val > 0 then
dbms_output.put_line('Table Name : '||c.table_name||' with '||v_val||' rows ');
end if;
end loop;
end;
I'm confused with the word matches. If you mean column, but not table, you may use the following routine to get the desired tables with columns whose names are like ID_SUPPORT :
declare
v_val pls_integer := 0;
begin
for c in (
SELECT t.*
FROM all_tab_columns c
JOIN all_tables t on ( c.table_name = t.table_name )
WHERE c.column_name LIKE '%ID_SUPPORT%'
)
loop
execute immediate 'select count(1) from '||c.owner||'.'||c.table_name into v_val;
if v_val > 0 then
dbms_output.put_line('Table Name : '||c.table_name||' with '||v_val||' rows ');
end if;
end loop;
end;
I have table with 100 columns with not correlated names (ABC1, DA23, EE123 - there is no common pattern there).
I want to iterate through every row and every column in this table.
My current script:
BEGIN
FOR single_row IN (
SELECT *
FROM MY_TABLE)
LOOP
--iterate through columns of 'single_row'
--for each nullable column do insert with real current column name and column value)
--I assume each column is nullable except of ID
INSERT INTO ANOTHER_TABLE VALUES (single_row.id, column_name, column_value);
END LOOP;
END;
So for example, if MY_TABLE contains 2 rows:
ID|ABC1|DA23|EE123|...
1|123|456|789|...
2|321|654|987|...
After running my script, my ANOTHER_TABLE will contain:
MY_TABLE_ID|COLUMN_NAME|COLUMN_VALUE
1|ABC1|123
1|DA23|456
1|EE123|789
... other columns from row 1
2|ABC1|321
2|DA23|654
2|EE123|987
... other columns from row 2
How I can do this?
I'm using Oracle 11g
EDIT
#vkp provided great solution, but there is one more thing to solve. I don't want to specify all columns in in clause. I would love to use some kind of query there or * or anything else, just to not be forced to list all of them.
I have tried something like this:
select *
from MY_TABLE t
unpivot (
column_value for column_name in (select column_name
from user_tab_columns
where table_name = 'MY_TABLE'
and nullable = 'Y')
) u
but it returns error:
ORA-00904: : invalid identifier
00904. 00000 - "%s: invalid identifier"
This is an application of unpivot.
select *
from my_table m
unpivot (column_value for column_name in (ABC1,DA23,EE123)) u
null values for any of the columns for an id won't be shown in the result.
If you have to include null values in the output, use the option INCLUDE NULLS.
select *
from my_table m
unpivot include nulls (column_value for column_name in (ABC1,DA23,EE123)) u
Edit: To include column names dynamically, use
DECLARE
sql_stmt VARCHAR2(4000);
var_columns VARCHAR2(4000); --use clob datatype if the column names can't fit in with this datatype
BEGIN
SELECT LISTAGG(column_name,',') WITHIN GROUP(ORDER BY column_name)
INTO var_columns
FROM user_tab_columns
WHERE table_name='MY_TABLE' AND column_name<>'ID';
sql_stmt:='select * from my_table m
unpivot
(column_value for column_name in (' || var_columns || ')) u';
EXECUTE IMMEDIATE sql_stmt;
END;
/
First option. With dynamic sql.
declare
v_ctx number;
v_query varchar2(500);
v_total NUMBER;
v_desctab DBMS_SQL.DESC_TAB;
v_column_cnt NUMBER;
v_value varchar2(32767);
v_result clob := '';
v_rownum number := 0;
begin
v_ctx := dbms_sql.open_cursor;
v_query := 'select * from user_objects where rownum < 100';
dbms_sql.parse(v_ctx,v_query,dbms_sql.v7);
v_total := dbms_sql.execute(v_ctx);
DBMS_SQL.DESCRIBE_COLUMNS(v_ctx, v_column_cnt, v_desctab);
for i in 1 .. v_column_cnt loop
dbms_sql.define_column(v_ctx, i, v_value /* data_type varchar2*/, 32767 /* max_length*/);
end loop;
loop
exit when dbms_sql.fetch_rows(v_ctx) = 0;
v_rownum := v_rownum +1;
for i in 1 .. v_column_cnt loop
dbms_sql.column_value(v_ctx, i, v_value);
dbms_output.put_line(v_rownum||' - '||v_desctab(i).col_name||' - '||v_value);
end loop;
end loop;
dbms_sql.close_cursor(v_ctx);
exception
when others then
dbms_sql.close_cursor(v_ctx);
raise;
end;
/
2nd option with xquery.
select t1.id,t2.* from xmltable('for $i in ora:view("<you_table_here>")/ROW
return $i'
columns id FOR ORDINALITY
, row_value xmltype path'.'
) t1
,xmltable('for $i in $row_value/ROW/* return $i'
passing t1.row_value as "row_value"
columns col_index for ORDINALITY ,
column_name varchar2(100) path 'name()',
column_value varchar2(100) path 'text()'
) t2
Here is a simple solution using REF CURSOR.
I've tried this code and it's working at my end.
DECLARE
query_2 VARCHAR2(1000);
TYPE icur IS REF CURSOR;
ic icur;
col_val VARCHAR2(100);
BEGIN
FOR j IN
(SELECT * FROM user_tab_cols WHERE table_name = UPPER('MY_TABLE'))
LOOP
dbms_output.put_line(j.column_name);
query_2 := 'SELECT ' || j.column_name|| ' FROM MY_TABLE';
OPEN ic FOR query_2;
LOOP
FETCH ic INTO col_val;
EXIT WHEN ic%NOTFOUND;
INSERT INTO ANOTHER_TABLE VALUES( j.column_name, col_val);
END LOOP;
END LOOP;
END;
/
Below is the sample code in which I have stored all the table names in one table (table_config) and trying to insert one record of every table into its temporary table and trying to get the particular rowid for further need.
So I need every table rowtype to make this work, something dynamic. Could you please help me with this?
DECLARE
l_row table_name%ROWTYPE;
l_rowid ROWID;
l_table_name all_tab_partitions.table_name%TYPE;
l_temp_table_name all_tab_partitions.table_name%TYPE;
BEGIN
FOR tab IN
(select table_name from
Table_config)
LOOP
l_table_name:= tab.table_name;
l_temp_table_name:= 'TEMP_'||l_table_name;
SELECT * INTO l_row
FROM l_table_name
WHERE ROWNUM=1;
INSERT INTO l_temp_table_name VALUES l_row
RETURNING ROWID INTO l_rowid;
COMMIT;
END LOOP;
END;
Thank you,
Pradeep
Without coding the complete answer for you.
Why don't you do something like this?
FOR tab IN
(select table_name from
Table_config)
EXECUTE_IMMEDIATE(
'declare
l_row '||table_name||'%ROWTYPE;
begin
INSERT INTO '||l_temp_table_name
SELECT * FROM '||l_table_name||' WHERE ROWNUM=1;
end;');
EXECUTE_IMMEDIATE ('SELECT ROWID FROM '||l_table_name)
INTO l_rowid;
END LOOP;
it assumes target table is empty to begin with with only one record inserted during the process.
You can't do that as already mentioned in the comment by OldProgrammer above.
You'll have to use Dynamic SQL to achieve what you're trying to achieve.
DECLARE
temp_table VARCHAR2(255);
source_table VARCHAR2(255);
sql_stmt VARCHAR2(255);
CURSOR c1 IS
SELECT table_name FROM user_Tables;
BEGIN
FOR c1_Rec IN c1 LOOP
temp_table := 'TEMP_'||c1_rec.table_name;
source_table := c1_rec.table_name;
sql_stmt := 'INSERT INTO '||temp_table||' SELECT * FROM '||source_table||' WHERE rownum = 1';
EXECUTE IMMEDIATE sql_stmt;
END LOOP;
END;
/
Below is solution. What do you need this rowids for? I would be much simpler without it, as you cannot use returning with insert as select
DECLARE
l_rowid ROWID;
l_table_name all_tab_partitions.table_name%TYPE;
l_temp_table_name all_tab_partitions.table_name%TYPE;
v_sql1 varchar2(4000);
v_sql2 varchar2(4000);
BEGIN
FOR tab IN (select table_name from Table_config) LOOP
l_table_name:= tab.table_name;
l_temp_table_name:= 'TEMP_'||l_table_name;
v_sql1 := 'select rowid from ' || l_table_name || ' where rownum =1 for update';
v_sql2 := 'insert into ' || l_temp_table_name || ' select * from ' || l_table_name || ' where rownum = 1';
execute immediate v_sql1 into l_rowid;
execute immediate v_sql2;
commit;
END LOOP;
END;
/
You should investigate EXECUTE IMMEDIATE INTO. I think this would be an excellent way to get the ROWID when combined with some dynamic SQL examples from above. Here's an example:
DECLARE
DYN_SQL VARCHAR(4000) := 'SELECT 1 FROM DUAL';
INTO_VAR NUMBER(1);
BEGIN
EXECUTE IMMEDIATE DYN_SQL INTO INTO_VAR;
DBMS_OUTPUT.PUT_LINE(INTO_VAR);
END;
Thank you guys for your response. Actually I was trying to implement partition exchange on interval partitioned tables. I achieved it by using Dynamic Sql now. Initially I was trying to implement it by using rowid which is ok when I hard coded for one table, but when I thought of configuring it and using it for multiple tables I got stuck at that %ROWTYPE.
In the below code I have hard coded table name in few places which can be modified as dynamic but the problem is how to get the %ROWTYPE for the every table we pass.
DECLARE
l_table_name table_config.table_name%TYPE;
l_query_temp VARCHAR2(1000);
l_part_table_name all_tab_partitions.table_name%TYPE;
l_part_name all_tab_partitions.partition_name%TYPE;
l_temp_table_name all_tab_partitions.table_name%TYPE;
l_row test_archival%ROWTYPE;
l_rowid ROWID;
l_arch_table_name all_tab_partitions.table_name%TYPE;
l_arch_part_name VARCHAR2(30);
l_query_arch VARCHAR2(1000);
l_query_source VARCHAR2(1000);
BEGIN
<<outer_loop>>
FOR tab IN
(SELECT table_name FROM
table_config)
LOOP
l_table_name:= tab.table_name;
<<inner_loop>>
FOR part IN
(SELECT table_name, partition_position, partition_name FROM
(SELECT table_name, partition_position, partition_name,
DENSE_RANK() OVER (PARTITION BY table_name ORDER BY partition_position DESC) AS RANK
FROM all_tab_partitions
WHERE table_name=l_table_name
) WHERE RANK NOT IN(1, 2) ORDER BY partition_position)
LOOP
l_part_table_name:= part.table_name;
l_part_name:= part.partition_name;
l_temp_table_name := 'TEMP_'||l_part_table_name;
l_arch_table_name := 'ARCH_'||l_part_table_name;
l_query_temp := 'ALTER TABLE '
|| l_part_table_name
|| ' EXCHANGE PARTITION '
|| l_part_name
|| ' WITH TABLE '
|| l_temp_table_name
||' INCLUDING INDEXES WITHOUT VALIDATION';
EXECUTE IMMEDIATE l_query_temp;
COMMIT;
SELECT * INTO l_row FROM temp_test_archival WHERE ROWNUM = 1;
INSERT INTO arch_test_archival VALUES l_row RETURNING ROWID INTO l_rowid;
COMMIT;
SELECT subobject_name
INTO l_arch_part_name FROM user_objects
WHERE data_object_id = dbms_rowid.rowid_object(l_rowid);
DELETE from arch_test_archival where rowid=l_rowid;
COMMIT;
l_query_arch := 'ALTER TABLE '
||'ARCH_TEST_ARCHIVAL'
||' EXCHANGE PARTITION '
||l_arch_part_name
||' WITH TABLE '
||'TEMP_TEST_ARCHIVAL'
||' INCLUDING INDEXES WITHOUT VALIDATION';
EXECUTE IMMEDIATE l_query_arch;
END LOOP;
END LOOP;
END;
/
I have this plsql code that will give me all tables in the database with name CUSTOMERS. Now I am struck how to insert another loop in to this. I want to get the output from this code and pass it on to next loop where I want to query something like, select count(*) from Schema.customers; for each schema.
DECLARE
--c_id customers.id%type;
c_name all_tables.table_name%type;
c_tabs all_tables.owner%type;
CURSOR c_tables is
SELECT table_name, owner FROM all_tables where table_name='CUSTOMERS';
BEGIN
OPEN c_tables;
LOOP
FETCH c_tables into c_name, c_tabs;
dbms_output.put_line(c_tabs || '.' || c_name );
EXIT WHEN c_tables%notfound;
END LOOP;
CLOSE c_tables;
END;
/
------- Sample output of my code: ------------
UMICH2.CUSTOMERS
TRINITYDC.CUSTOMERS
BUFFALO.CUSTOMERS
SNOW.CUSTOMERS
PULASKITECH.CUSTOMERS
RARITANVAL.CUSTOMERS
STMARYSCA.CUSTOMERS
You can get the same result in a single SQL statement
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 = 'CUSTOMERS'
This way is one possibility that you can do that.
DECLARE
--c_id customers.id%type;
c_name all_tables.table_name%TYPE;
c_tabs all_tables.owner%TYPE;
v_value PLS_INTEGER;
CURSOR c_tables IS
SELECT table_name
FROM all_tables
WHERE table_name = 'CUSTOMERS';
CURSOR c_owner IS
SELECT DISTINCT owner
FROM all_tables
WHERE table_name = 'CUSTOMERS';
BEGIN
OPEN c_tables;
LOOP
FETCH c_tables INTO c_name;
OPEN c_owner;
LOOP
FETCH c_owner INTO c_tabs;
BEGIN
EXECUTE IMMEDIATE 'SELECT COUNT(1) FROM ' || c_tabs || '.' || c_name INTO v_value;
EXCEPTION
WHEN other THEN
NULL;
END;
DBMS_OUTPUT.put_line ( v_value );
EXIT WHEN c_owner%NOTFOUND;
END LOOP;
CLOSE c_owner;
DBMS_OUTPUT.put_line ( c_tabs || '.' || c_name );
EXIT WHEN c_tables%NOTFOUND;
END LOOP;
CLOSE c_tables;
END;
Any question just let me know.
Thanks.
I have written 2 separate queries
1)
SELECT COLUMN_NAME
FROM ALL_TAB_COLUMNS
WHERE TABLE_NAME =
(SELECT DISTINCT UT.TABLE_NAME
FROM USER_TABLES UT
WHERE UT.TABLE_NAME = 'MY_TABLE')
AND COLUMN_NAME NOT IN ('AVOID_COLUMN')
2)
SELECT *
FROM MY_TABLE MT
WHERE MT.COL1 = '1'
The 1st query returns the names of all the columns except the one I want to avoid. The 2nd one returns data of all the columns from the table.
Is there some way to merge these queries so that only those column's data is selected from the 2nd query, which are returned from the 1st query?
Thanks in advance
You'll have to use dynamic SQL for this (BTW, I got rid of the subselect for the USER_TABLES query - it's unnecessary):
var cur refcursor
/
declare
v_stmt varchar2(4000);
begin
v_stmt := 'SELECT ';
for cur in (
SELECT COLUMN_NAME
FROM ALL_TAB_COLUMNS
WHERE TABLE_NAME =
'MY_TABLE'
AND COLUMN_NAME NOT IN ('AVOID_COLUMN')
)
loop
v_stmt := v_stmt || cur.column_name || ',';
end loop;
-- get rid of trailing ','
v_stmt := regexp_replace(v_stmt, ',$', '');
v_stmt := v_stmt || ' from my_table MT WHERE MT.COL1 = ''1''';
dbms_output.put_line(v_stmt);
open :cur for v_stmt;
end;