I want to run an Oracle validation function against every table in the database that has a suitable column.
The validation can be run against one table simply:
SELECT
count (*)
FROM
Table_name t
Where
SDO_GEOM.VALIDATE_GEOMETRY_WITH_CONTEXT(t.Column_name, 0.005) <> 'TRUE';
This works fine for an individual table, but as there are over 100 to test I wanted to merge it with the Oracle Metadata table using a subquery and thus automate the whole thing. I've come up with two variants but neither works, I guess because it's having trouble passing variables from inside the subquery.
My two attempts are:
SELECT TABLE_NAME tab, COLUMN_NAME col, (select count(*) from tab where sdo_geom.validate_geometry(tab.col, 0.005) <> 'TRUE')
From
All_Tab_Columns
where
owner = 'WCCDATA' and DATA_TYPE = 'SDO_GEOMETRY'
Which returns:
SQL Error: ORA-00904: "TAB"."COL": invalid identifier
00904. 00000 - "%s: invalid identifier"
and
SELECT count(*)
From
(SELECT
TABLE_NAME, COLUMN_NAME as col
FROM
All_Tab_Columns
where
owner = 'WCCDATA' and DATA_TYPE = 'SDO_GEOMETRY') subquery
WHERE sdo_geom.validate_geometry(subquery.col, 0.005) <> 'TRUE';
which returns:
ORA-06512: at "MDSYS.SDO_GEOM", line 2204
00942. 00000 - "table or view does not exist"
Anyone have any thoughts? Thanks.
You can't ever reference column values as identifiers (tables, columns, etc.). In order to do this, you'll need to write some PL/SQL to create and execute the SQL dynamically, perhaps like this:
DECLARE
CURSOR cur_tables IS
SELECT table_name,
'SELECT count(*) From '
|| table_name
|| ' WHERE sdo_geom.validate_geometry('
|| column_name
|| ', 0.005) <> ''TRUE'''
AS dsql
FROM all_tab_columns
WHERE owner = 'WCCDATA' AND data_type = 'SDO_GEOMETRY';
v_count NUMBER;
BEGIN
FOR r_tables IN cur_tables LOOP
EXECUTE IMMEDIATE r_tables.dsql INTO v_count;
DBMS_OUTPUT.put_line(r_tables.table_name || ': ' || v_count);
END LOOP;
END;
Related
I want to do select column_name from table_name where column_name.
table_name should be coming from cursor.
Query:
DECLARE
COLUMN_NAME VARCHAR(50);
TABLE_NAME VARCHAR(100);
schema_name VARCHAR(100);
A VARCHAR(100);
B VARCHAR(100);
CURSOR col_cursor IS
select col.owner as schema_name,
col.table_name,
col.column_name
from sys.all_tab_columns col
inner join sys.all_tables t
on col.owner = t.owner and
col.table_name = t.table_name
where col.owner = 'PIYUSH1910_BEFORE'
AND
DATA_TYPE = 'NUMBER'
AND
DATA_PRECISION IS NULL
AND
col.TABLE_NAME NOT LIKE '%ER%';
BEGIN
OPEN col_cursor;
LOOP
FETCH col_cursor INTO schema_name,TABLE_NAME,COLUMN_NAME;
EXIT WHEN col_cursor%NOTFOUND;
EXECUTE IMMEDIATE ' SELECT '||COLUMN_NAME ||' INTO A from ' || Table_Name || 'WHERE'||COLUMN_NAME||'- TRUNC('||COLUMN_NAME||',2) > 0';
dbms_output.Put_line(A);
END LOOP;
CLOSE col_cursor;
END
But its throwing this error:
Error report - ORA-00933: SQL command not properly ended. ORA-06512: at line 29 00933. 00000 - "SQL command not properly ended *Cause: *Action:
INTO clause must be at the end while using the EXECUTE IMMEDIATE as follows:
EXECUTE IMMEDIATE ' SELECT '||COLUMN_NAME ||' from '
|| Table_Name || ' WHERE '||COLUMN_NAME||'- TRUNC('||COLUMN_NAME||',2) > 0'
INTO A;
Also, space before and after WHERE keyword is added in the above solution.
Change your EXECUTE IMMEDIATE to:
EXECUTE IMMEDIATE ' SELECT '|| COLUMN_NAME || ' from ' || Table_Name ||
' WHERE ' || COLUMN_NAME || '- TRUNC('||COLUMN_NAME||',2) > 0'
INTO A;
This moves the INTO clause to the correct position (in this case it's part of the EXECUTE IMMEDIATE rather than being part of the SELECT) and reformats the code slightly so that column names are separated from e.g. the WHERE keyword.
I have a procedure which receives as parameter a where clause (i.e. where col1 = 1). I am using this clause to search in some tables using an EXECUTE IMMEDIATE statement and the result to be inserted into a nested table, and than be displayed.
The procedure works fine if any data is found but in case no data is found, then the above error is thrown.
Can someone explain what cause this error, please?
Here is the procedure:
create or replace procedure prc_checks(pi_where varchar2) as
cursor c_tables is
select object_name,
case object_name
when 'XP_IMPORT_MW' THEN 99999999
when 'XP_IMPORT_MW_ARCH' THEN 99999998
else TO_NUMBER(SUBSTR(object_name, -8, 8))
end to_order
from dba_objects
where object_type = 'TABLE'
and object_name IN ('XP_IMPORT_MW', 'XP_IMPORT_MW_ARCH')
or REGEXP_LIKE (object_name, 'XP_IMPORT_MW_ARCH_201(5|6|7)[0-9]{4}') order by 2 desc;
type t_result is table of xp_import_mw%rowtype;
v_result t_result;
v_sql varchar2(300);
BEGIN
for i in c_tables
loop
v_sql := 'select * from ' || i.object_name || ' ' || pi_where;
execute immediate v_sql bulk collect into v_result;
if v_result.count > 0
then
for j in v_result.first .. v_result.last
loop
dbms_output.put_line(v_result(j).art_nr);
end loop;
dbms_output.put_line('... the required information was found on table name ' || upper(i.object_name));
exit;
end if;
end loop;
END prc_checks;
You'll get this is one of the tables being found by the cursor has fewer columns than xp_import_mw. For example:
create table xp_import_mw (col1 number, art_nr number, dummy number);
create table xp_import_mw_arch_20160102 (col1 number, art_nr number, dummy number);
create table xp_import_mw_arch_20160101 (col1 number, art_nr number);
insert into xp_import_mw_arch_20160101 values (1, 42);
So the main xp_import_mw table has three columns but no matching data. One of the old archive tables has one fewer columns.
I added a dbms_output.put_line(v_sql) to the procedure to see which table it fails against, then ran it:
set serveroutput on
exec prc_checks('where col1 = 1');
which got output:
select * from XP_IMPORT_MW where col1 = 1
select * from XP_IMPORT_MW_ARCH_20160102 where col1 = 1
select * from XP_IMPORT_MW_ARCH_20160101 where col1 = 1
Error starting at line : 49 in command -
BEGIN prc_checks('where col1 = 1'); END;
Error report -
ORA-01007: variable not in select list
ORA-06512: at "MY_SCHEMA.PRC_CHECKS", line 25
ORA-06512: at line 1
01007. 00000 - "variable not in select list"
*Cause:
*Action:
So the problem isn't that there is no data found; the problem is that there is matching data in a table which has the wrong structure.
You could construct the select list based on the xp_import_mw table's structure, instead of using *; that won't stop it failing, but would at least give you a slightly more helpful error message - in this case ORA-00904: "DUMMY": invalid identifier instead of ORA-01007.
You could do a quick and crude check for discrepancies with something like:
select table_name, count(column_id) as column_count,
listagg(column_name, ',') within group (order by column_id) as columns
from dba_tab_columns
where table_name IN ('XP_IMPORT_MW', 'XP_IMPORT_MW_ARCH')
or REGEXP_LIKE (table_name, 'XP_IMPORT_MW_ARCH_201(5|6|7)[0-9]{4}')
group by table_name
having count(column_id) != (
select count(column_id) from dba_tab_columns where table_name = 'XP_IMPORT_MW'
);
... although if you're using dba_* or all_* view you should really be including the owner, here and in your procedure.
I would like to check all columns of all tables for entries which are not contained in the table of valid dates.
In other words the elements of all columns with type date have to one of the entries of the table column CALENDAR.BD.
My problem is that executing the function by select checkAllDateColumns() from DUAL results only in
invalid table name, line 16
and I don't see why?
CREATE OR REPLACE FUNCTION checkAllDateColumns RETURN NUMBER IS
l_count NUMBER;
BEGIN
FOR t IN (SELECT table_name
FROM all_tables
WHERE owner = 'ASK_QUESTION')
LOOP
dbms_output.put_line ('Current table : ' || t.table_name);
FOR c IN (SELECT column_name
FROM all_tab_columns
WHERE TABLE_NAME = t.table_name AND data_type = 'DATE')
LOOP
execute immediate 'select count(*) from :1 where :2 not in (select BD from CALENDAR where is_business_day = 1)'
into l_count
using t.table_name, c.column_name;
IF (l_count > 0) THEN
RETURN 1;
END IF;
END LOOP;
END LOOP;
RETURN 0;
END checkAllDateColumns;
/
Btw: I am fine with the statement stopping on the first mismatch, currently I would like to figure out the dynamic sql...
You can't use a bind variable for the table or column names, only for column values. It's trying to interpret :1 as an identifier, it isn't using the value from the using clause. Your returning into clause should also just be into. This works:
execute immediate 'select count(*) from "' || t.table_name
|| '" where "' || c.column_name
|| '" not in (select BD from CALENDAR where is_business_day = 1)'
into l_count;
You might get a performance difference using a left-join approach, but it depends on your data:
execute immediate 'select count(*) from "' || t.table_name
|| '" t left join calendar c on c.bd = trunc(t."'
|| c.column_name || '") and c.is_business_day = 1 '
|| ' where c.bd is null'
into l_count;
I added a trunc() in case any of the fields might have times in them; you can do the same in the other version too of course.
And in both I've included double-quotes around the table and column names, just in case there are any quoted identifiers - without those there's a risk of getting an ORA-00904 'invalid identifier' error. But hopefully you don't have any to worry about anyway.
You also don't really need nested loops; you can either get the table name from all_tab_columns, or if you prefer, join all_tables and all_tab_columns in a single cursor. You should also be checking that the owner is the same in both tables, in case there are two versions of a table in different schemas.
Considering that I have a Schema named SBST
I want to find all empty tables list in this SBST Schema. Is there any PL/SQL procedure to find that. I found few. But those were using user tables where I was not able specify the Schema name SBST.
I was using this
select table_name from dba_tables where owner ='SBST'
having count(*)=0 group by table_name
What's wrong in the above query?
You can fire below query to find the list of tables haveing no data
select * from ALL_TABLES
where owner ='SBST'
AND NUM_ROWS = 0;
Similar to #shareef's answer, but using dynamic SQL to avoid having to create the temporary .sql file. You'll need dbms_output to be visible, e.g. with set serveroutput on in SQL*Plus - don't know about Toad.
declare
cursor c(p_schema varchar2) is
select 'select ''' || table_name || ''' from ' ||
p_schema ||'.' || table_name || ' where rownum < 2 ' ||
' having count(*) = 0' as query
from all_tables
where owner = p_schema
order by table_name;
l_table all_tables.table_name%TYPE;
begin
for r in c('SBST') loop
begin
execute immediate r.query into l_table;
exception
when no_data_found then continue;
end;
dbms_output.put_line(l_table);
end loop;
end;
/
Using all_tables seems more useful than dba_tables here so you know you can select from the tables it lists. I've also included the schema in the from clause in case there other users have tables with the same name, and so you can still see it if you're connected as a different user - possibly avoiding synonym issues too.
Specifically what's wrong with your query... you've got the having and group by clauses the wrong way around; but it will always return no data anyway because if SBST has any tables then count (*) from dba_tables must be non-zero, so the having always matches; and if it doesn't then, well, there's no data anyway so there's nothing for the having to match against. You're counting how many tables there are, not how many rows are in each table.
the straight forward answer is
select 'select ''' || table_name || ''' from ' || table_name || '
having count(*) = 0;' from dba_tables where owner='SBST';
EXPLANATION
You can run this... it will just output the table names of those having no rows:
assuming you use sqlplus but i used toad to test it
and it worked very well
set echo off heading off feedback off lines 100 pages 0;
spool tmp.sql
select 'select ''' || table_name || ''' from ' || table_name || '
having count(*) = 0;' from user_tables where owner='SBST';
spool off;
#tmp.sql
If you open the "tmp.sql" file, you'll see for all tables....
select 'PERSONS' from PERSONS having count(*) = 0;
select 'DEPARTMENT' from DEPARTMENT having count(*)=0;
in your case you want a schema and schema is a user right the above code if you connect with the user SBST but if you connect with other then you have to use DBA_TABLES and assign owner attribute to SBST
USER_TABLES is tables which you own ALL_TABLES is tables which own,
and tables owner by other users, which you have been granted excplicit
access to DBA_TABLES is all tables in the database
like this
set echo off heading off feedback off lines 100 pages 0;
spool tmp.sql
select 'select ''' || table_name || ''' from ' || table_name || '
having count(*) = 0;' from dba_tables where owner='SBST';
spool off;
#tmp.sql
All three are views of the underlying SYS tables, but the USER_ and
ALL_ views joing in your username/security info to limit the results
**SUMMARY **
PLEASE JUST RUN THIS QUERY
select 'select ''' || table_name || ''' from ' || table_name || '
having count(*) = 0;' from dba_tables where owner='SBST';
If table stats are up to date then you could use:
SELECT TABLE_NAME
FROM ALL_TAB_STATISTICS
WHERE (OWNER = 'ME')
AND (NUM_ROWS = 0);
I have problem about how to get all of list structure table using loop pl/sql,
i have syntax loop like bellow :
BEGIN
FOR x IN (SELECT table_name FROM user_tables)
LOOP
EXECUTE IMMEDIATE 'DESC' || x.table_name;
END LOOP;
COMMIT;
END;
and this is log output from sql developer
Error starting at line 14 in command:
BEGIN
FOR x IN (SELECT table_name FROM user_tables)
LOOP
EXECUTE IMMEDIATE 'DESC' || x.table_name;
END LOOP;
COMMIT;
END;
Error report:
ORA-00900: invalid SQL statement
ORA-06512: at line 4
00900. 00000 - "invalid SQL statement"
*Cause:
*Action:
Help me to solve this
Thanks
The DESC command is neither SQL nor plsql but a sqlplus command, so you can't use it in a plsql code.
More than that, why ? you can get all the values you need from USER_TAB_COLUMNS ...
Use DBMS_OUTPUT.put_line() instead:
BEGIN
FOR x IN (SELECT COLUMN_NAME, DATA_TYPE, NULLABLE FROM USER_TAB_COLUMNS)
LOOP
DBMS_OUTPUT.put_line(COLUMN_NAME || ' ' || DATA_TYPE || ' ' || NULLABLE );
END LOOP;
COMMIT;
END;
Of course this can be formatted nicer and there are more columns you can select ...
Consider this instead:
Select * from All_tab_Cols
or to be closer to what is returned from describe:
Select Owner, Table_name, ColumN_Name, Data_Type, Data_Length, Nullable
from all_tab_Cols;
To address your specific problem shouldn't it be:
EXECUTE IMMEDIATE 'DESC ' || x.table_name|| ';';
Missing space between desc & table name and ; is part of the SQL to run isn't it?
If you're trying to reverse engineer the schema then use the DBMS_Metadata package to do so, as it can also extract constraints, privileges, users etc.