Finding Empty Tablespaces in Oracle

I’m given a task to find the empty tablespaces.
I’ve checked dev server and majority of the tables’ stats were locked.
This solution takes time but works.

SELECT owner,count(1) FROM dba_tab_statistics where stattype_locked='ALL'  group by owner;

They are fixed for OCB execution plans. so statistics can not be updated.
So I’ve followed this method to find the empty tablespaces.

CREATE TABLE stats AS SELECT tablespace_name,owner,table_name,num_rows
FROM dba_tables WHERE 1=2;

DECLARE
val integer;
BEGIN
for i in (SELECT distinct table_name, owner,tablespace_name  FROM DBA_tables WHERE  temporary='N' and partitioned='NO' and tablespace_name not in ('SYSTEM','USERS','SYSAUX'))
LOOP
EXECUTE IMMEDIATE 'SELECT count(1) from ' || i.owner ||'.'|| i.table_name INTO val;
IF VAL=0 THEN 
    INSERT INTO stats VALUES (i.tablespace_name,i.owner,i.table_name,val);
	commit;
END IF;
END LOOP;

for i in (SELECT distinct table_name, owner,tablespace_name  FROM DBA_indexes WHERE  temporary='N' and partitioned='NO'  and tablespace_name not in ('SYSTEM','USERS','SYSAUX'))
LOOP
EXECUTE IMMEDIATE 'SELECT count(1) from ' || i.owner ||'.'|| i.table_name INTO val;
IF VAL=0 THEN 
    INSERT INTO stats VALUES (i.tablespace_name,i.owner,i.table_name,val);
	commit;
END IF;
END LOOP;

for i in (SELECT distinct table_name, owner,tablespace_name  FROM DBA_lobs WHERE   partitioned='NO' and tablespace_name not in ('SYSTEM','USERS','SYSAUX'))
LOOP
EXECUTE IMMEDIATE 'SELECT count(1) from ' || i.owner ||'.'|| i.table_name INTO val;
IF VAL=0 THEN 
    INSERT INTO stats VALUES (i.tablespace_name,i.owner,i.table_name,val);
	commit;
END IF;
END LOOP;

for i in (SELECT distinct table_name, table_owner owner,tablespace_name  FROM DBA_tab_partitions WHERE  tablespace_name not in ('SYSTEM','USERS','SYSAUX') )
LOOP
EXECUTE IMMEDIATE 'SELECT count(1) from ' || i.owner ||'.'|| i.table_name INTO val;
IF VAL=0 THEN 
    INSERT INTO stats VALUES (i.tablespace_name,i.owner,i.table_name,val);
	commit;
END IF;
END LOOP;

for i in (SELECT distinct ind.table_name, ind.table_owner owner,ind.tablespace_name  from dba_IND_PARTITIONS IP JOIN DBA_INDEXES Ind ON IP.INDEX_OWNER=Ind.OWNER AND IP.INDEX_NAME=Ind.INDEX_NAME WHERE   IND.tablespace_name not in ('SYSTEM','USERS','SYSAUX') )
LOOP
EXECUTE IMMEDIATE 'SELECT count(1) from ' || i.owner ||'.'|| i.table_name INTO val;

IF VAL=0 THEN 
    INSERT INTO stats VALUES (i.tablespace_name,i.owner,i.table_name,val);
	commit;
END IF;
END LOOP;

for i in (SELECT distinct table_name, table_owner owner,tablespace_name  FROM DBA_lob_partitions WHERE  tablespace_name not in ('SYSTEM','USERS','SYSAUX') )
LOOP
EXECUTE IMMEDIATE 'SELECT count(1) from ' || i.owner ||'.'|| i.table_name INTO val;
IF VAL=0 THEN 
    INSERT INTO stats VALUES (i.tablespace_name,i.owner,i.table_name,val);
	commit;
END IF;
END LOOP;


END;

after I’ve collected empty objects in stats table I’ve leveraged these tablespaces to start searching in dba_extents for the empty tablespaces.


CREATE TABLE TBSOBJ AS SELECT * FROM TBS_OBJ WHERE 1=2;

CREATE TABLE TBS_OBJ  (	"TABLESPACE_NAME" VARCHAR2(128 BYTE), "OWNER" VARCHAR2(60 BYTE), "OBJ_NAME" VARCHAR2(128 BYTE), "OBJ_TYPE" VARCHAR2(60 BYTE), "ROWNUMS" NUMBER(*,0) );

declare
sql_stmt varchar2(1024);
row_count number;
v_table_name varchar2(50);

cursor get_tab is select tablespace_name, OWNER, SEGMENT_TYPE,COUNT(1) cnt from dba_extents where  tablespace_name in (select distinct tablespace_name from stats)GROUP BY tablespace_name, OWNER, SEGMENT_TYPE order by 1;
begin
    FOR get_tab_rec in get_tab LOOP
        v_table_name := get_tab_rec.segment_type;
       
        CASE WHEN get_tab_rec.SEGMENT_TYPE= 'INDEX' AND get_tab_rec.CNT>0  THEN 
                insert into tbs_obj select tablespace_name,owner,index_name,'INDEX',COUNT(1) FROM DBA_INDEXES WHERE TABLESPACE_NAME=get_tab_rec.tablespace_name and temporary='N' and partitioned='NO' group by tablespace_name,owner,index_name,'INDEX' ;
				commit;				
                DECLARE
                  cursor get_tbl is select tablespace_name , owner,table_name  from  DBA_INDEXES  where  TABLESPACE_NAME=get_tab_rec.tablespace_name ;
                BEGIN                          
                    FOR tbl_rec in GET_tbl LOOP
                     sql_stmt := 'SELECT COUNT(1) FROM "'|| tbl_rec.OWNER ||'"."'|| tbl_rec.TABLE_NAME||'"';                    
                     EXECUTE IMMEDIATE sql_stmt INTO row_count;
                     if row_count>0 then 
                        insert into tbsobj VALUES(tbl_rec.tablespace_name,tbl_rec.owner,tbl_rec.table_NAME,'INDEX',row_count);
						commit;
                     end if ;
                    end LOOP;
                END;
             when get_tab_rec.SEGMENT_TYPE= 'TABLE' AND get_tab_rec.CNT>0  THEN
               insert into tbs_obj select tablespace_name,owner,table_name,'TABLE',COUNT(1) FROM DBA_TABLES WHERE TABLESPACE_NAME=get_tab_rec.tablespace_name and temporary='N' and partitioned='NO' group by tablespace_name,owner,table_name,'TABLE' ;
			   commit;
               DECLARE
               cursor get_tbl is select tablespace_name , owner,table_name  FROM DBA_tables WHERE TABLESPACE_NAME=get_tab_rec.tablespace_name ;
                  BEGIN                          
                    FOR tbl_rec in GET_tbl LOOP
                     sql_stmt := 'SELECT COUNT(1) FROM "'|| tbl_rec.OWNER ||'"."'|| tbl_rec.TABLE_NAME||'"';                    
                     EXECUTE IMMEDIATE sql_stmt INTO row_count;
                      if row_count>0 then 
                        insert into tbsobj VALUES(tbl_rec.tablespace_name,tbl_rec.owner,tbl_rec.table_NAME,'TABLE',row_count);
						commit;
                     end if ;
                    
                    end LOOP;
                 END;
             when get_tab_rec.SEGMENT_TYPE= 'TABLE PARTITION' AND get_tab_rec.CNT>0  THEN
               insert into tbs_obj select tablespace_name,TABLE_owner,table_name,'TABLE_PARTITION',COUNT(1) FROM dba_TAB_PARTITIONS WHERE TABLESPACE_NAME=get_tab_rec.tablespace_name group by tablespace_name,table_owner,table_name,'TABLE_PARTITION' ;
			   commit;
               DECLARE
               cursor get_tbl_prt is select tablespace_name , table_owner,table_name  FROM dba_TAB_PARTITIONS WHERE TABLESPACE_NAME=get_tab_rec.tablespace_name ;
                  BEGIN                          
                    FOR tbl_rec_prt in get_tbl_prt LOOP
                     sql_stmt := 'SELECT COUNT(1) FROM "'|| tbl_rec_prt.table_OWNER ||'"."'|| tbl_rec_prt.TABLE_NAME||'"';                   
                     EXECUTE IMMEDIATE sql_stmt INTO row_count;
                     if row_count>0 then 
                        insert into tbsobj VALUES(tbl_rec_prt.tablespace_name,tbl_rec_prt.table_owner,tbl_rec_prt.table_NAME,'TABLE PARTITION',row_count)   ;
						commit;
                     end if;
                    end LOOP;
                 END;
             when get_tab_rec.SEGMENT_TYPE= 'INDEX PARTITION' AND get_tab_rec.CNT>0  THEN
               insert into tbs_obj select I.tablespace_name,I.TABLE_owner,I.table_name,'INDEX_PARTITION',COUNT(1) from dba_IND_PARTITIONS IP JOIN DBA_INDEXES I ON IP.INDEX_OWNER=I.OWNER AND IP.INDEX_NAME=I.INDEX_NAME where  IP.TABLESPACE_NAME=get_tab_rec.tablespace_name 
               group by I.tablespace_name,I.table_owner,I.table_name,'INDEX_PARTITION' ;
			   commit;
               DECLARE
                  cursor get_tbl is select IP.tablespace_name , I.owner, I.table_name  from dba_IND_PARTITIONS IP JOIN DBA_INDEXES I ON IP.INDEX_OWNER=I.OWNER AND IP.INDEX_NAME=I.INDEX_NAME where  IP.TABLESPACE_NAME=get_tab_rec.tablespace_name ;
                BEGIN                          
                    FOR tbl_rec in GET_tbl LOOP
                     sql_stmt := 'SELECT COUNT(1) FROM "'|| tbl_rec.OWNER ||'"."'|| tbl_rec.TABLE_NAME||'"';                   
                     EXECUTE IMMEDIATE sql_stmt INTO row_count;
                     if row_count>0 then 
                        insert into tbsobj VALUES(tbl_rec.tablespace_name,tbl_rec.owner,tbl_rec.table_NAME,'INDEX PARTITION',row_count)   ;
						commit;
                    end if;
                    end LOOP;
                END;
            
			when get_tab_rec.SEGMENT_TYPE= 'LOBSEGMENT' AND get_tab_rec.CNT>0  THEN
               insert into tbs_obj select tablespace_name,owner,table_name,'LOB',COUNT(1) FROM DBA_LOBS WHERE TABLESPACE_NAME=get_tab_rec.tablespace_name and partitioned='NO' group by tablespace_name,owner,table_name,'LOB' ;
			   commit;
               DECLARE
               cursor get_tbl is select tablespace_name , owner,table_name  FROM DBA_LOBS WHERE TABLESPACE_NAME=get_tab_rec.tablespace_name ;
                  BEGIN                          
                    FOR tbl_rec in GET_tbl LOOP
                     sql_stmt := 'SELECT COUNT(1) FROM "'|| tbl_rec.OWNER ||'"."'|| tbl_rec.TABLE_NAME||'"';                    
                     EXECUTE IMMEDIATE sql_stmt INTO row_count;
                      if row_count>0 then 
                        insert into tbsobj VALUES(tbl_rec.tablespace_name,tbl_rec.owner,tbl_rec.table_NAME,'LOB',row_count);
						commit;
                     end if ;
                    
                    end LOOP;
                 END;
     
            when get_tab_rec.SEGMENT_TYPE= 'LOB PARTITION' AND get_tab_rec.CNT>0  THEN
               insert into tbs_obj select tablespace_name,TABLE_owner,table_name,'LOB_PARTITION',COUNT(1) FROM dba_LOB_PARTITIONS WHERE TABLESPACE_NAME=get_tab_rec.tablespace_name group by tablespace_name,table_owner,table_name,'LOB_PARTITION' ;
			   commit;
               DECLARE
               cursor get_tbl_prt is select tablespace_name , table_owner,table_name  FROM dba_LOB_PARTITIONS WHERE TABLESPACE_NAME=get_tab_rec.tablespace_name ;
                  BEGIN                          
                    FOR tbl_rec_prt in get_tbl_prt LOOP
                     sql_stmt := 'SELECT COUNT(1) FROM "'|| tbl_rec_prt.table_OWNER ||'"."'|| tbl_rec_prt.TABLE_NAME||'"';                   
                     EXECUTE IMMEDIATE sql_stmt INTO row_count;
                     if row_count>0 then 
                        insert into tbsobj VALUES(tbl_rec_prt.tablespace_name,tbl_rec_prt.table_owner,tbl_rec_prt.table_NAME,'LOB PARTITION',row_count)   ;
						commit;
                     end if;
                    end LOOP;
                 END;
            else null;
         end case;
    END LOOP;
   
END;

and get the Full List of Empty Tablespaces.

select distinct tablespace_name from stats
minus
select distinct TABLESPACE_NAME  FROM TBSOBJ 
order by tablespace_name
;