I am trying to write a SQL script that guesses foreign keys. My approach is to eliminate every column that can't be a foreign key. The rest would be manual work.
SELECT
atc1.table_name atc1_tn,
atc1.column_name atc1_cn,
atc2.table_name atc2_tn,
atc2.column_name atc2_cn
FROM
all_tab_cols atc1,
all_tab_cols atc2
WHERE
atc1.data_type = 'NUMBER'
AND atc1.data_type = atc2.data_type
AND atc1.table_name != atc2.table_name
AND atc1.high_value <= atc2.high_value
AND atc1.num_distinct <= atc2.num_distinct
At this point I get all possible matching columns but that is still not accurate enough.
The next step would be to check if every entry in atc1.column_name exists in atc2.column_name, because if not it can't be a foreign key.
How can I add that condition to my where clause?
The approach is:
Select
(execute immediate 'select '||ATC1_CN||' from '||ATC1_TN||'') as a,
(execute immediate 'select '||ATC2_CN||' from '||ATC2_TN||'') as b
from my_temp_table
where a not in b;
But that doesn't work as expected, because I can't use the table names in a string for a query.
Try the below for existing foreign keys
SELECT a.table_name, a.column_name, a.constraint_name, c.owner,
-- referenced pk
c.r_owner, c_pk.table_name r_table_name, c_pk.constraint_name r_pk
FROM all_cons_columns a
JOIN all_constraints c ON a.owner = c.owner
AND a.constraint_name = c.constraint_name
JOIN all_constraints c_pk ON c.r_owner = c_pk.owner
AND c.r_constraint_name = c_pk.constraint_name
WHERE c.constraint_type = 'R'
For potential foreign keys here are some pointers
The data type of foreign and referenced key should be same
The values in foreign and referenced key columns should be same
The child and parent tables must be on the same database
For query you can use the below
declare
prec number;
begin
for rec in (SELECT atc1.table_name atc1_tn,
atc1.column_name atc1_cn,
atc2.table_name atc2_tn,
atc2.column_name atc2_cn
FROM user_tab_cols atc1, user_tab_cols atc2
WHERE atc1.data_type = 'NUMBER'
AND atc1.data_type = atc2.data_type
AND atc1.table_name != atc2.table_name
AND atc1.high_value <= atc2.high_value
AND atc1.num_distinct <= atc2.num_distinct
) loop
execute immediate 'select count(1) from ' || rec.atc1_tn ||
' a where EXISTS (SELECT 1 FROM ' || rec.atc2_tn ||
' b where a.' || rec.atc1_cn || '!=' || ' b.' ||
rec.atc2_cn || ' )'
into prec;
if prec = 0 Then
dbms_output.put_line('potential foreign key rec:table1 ' ||
rec.atc1_tn || ' table2: ' || rec.atc2_tn ||
' column1: ' || rec.atc1_cn || ' column2: ' ||
rec.atc2_cn);
end if;
end loop;
end;
Related
I am trying to loop through multiple tables in a data mart and see how my TABLE_A joins to a TABLE_B based on a specific field (that changes with every loop). The variables for fields and tables resolve correctly in the DBMS_Output, but the query itself does not resovle to the full length. Instead, parts of the query are being cut off with each loop. How can I fix this?
My loop looks like this:
DECLARE
CURSOR c_tables is
SELECT
COLUMN_NAME
,JOIN_TABLE_NAME
FROM meself.test_loop ;
v_string1 varchar2(32767) := '';
BEGIN
FOR i IN c_tables LOOP
v_string1 := '
INSERT INTO myself.FIN_HASHKEY_MATCH
SELECT
x.TABLE_NAME
,x.JOIN_TABLE_NAME
,x.HASH_KEY_NAME
,x.MATCH_TYPE
,count(*) AS TOTALS
FROM
(
SELECT
DISTINCT a.'||i.COLUMN_NAME||'
,''' || i.JOIN_TABLE_NAME || ''' AS JOIN_TABLE_NAME
,''' || i.COLUMN_NAME || ''' AS HASH_KEY_NAME
, ''TABLE_A'' AS Table_Name
,(CASE
WHEN
a.'|| i.COLUMN_NAME || ' = z.' || i.COLUMN_NAME || ' THEN "MATCH"
ELSE "FAIL"
END) AS MATCH_TYPE
FROM DATAMART.TABLE_A a
LEFT JOIN (SELECT
DISTINCT '|| i.COLUMN_NAME ||'
FROM DATAMART.'|| i.JOIN_TABLE_NAME ||') z
ON a.'|| i.COLUMN_NAME ||' = z.'|| i.COLUMN_NAME ||'
) x
GROUP BY x.TABLE_NAME, x.JOIN_TABLE_NAME, x.HASH_KEY_NAME, x.MATCH_TYPE'
;
dbms_output.put_line( v_string1 );
--execute immediate v_string1;
END LOOP;
END;
**Which resolves to this (just showing 2 loops for simplicity). You will see the JOIN ON section is missing or incomplete before moving on to the next loop. **
INSERT INTO myself.FIN_HASHKEY_MATCH
SELECT
x.TABLE_NAME
,x.JOIN_TABLE_NAME
,x.HASH_KEY_NAME
,x.MATCH_TYPE
,count(*) AS TOTALS
FROM
(
SELECT
DISTINCT a.LINE_SCTGRY_D_SK
,'TABLE_B' AS JOIN_TABLE_NAME
,'LINE_SCTGRY_D_SK' AS HASH_KEY_NAME
,'TABLE_A' AS Table_Name
,(CASE
WHEN
a.LINE_SCTGRY_D_SK = z.LINE_SCTGRY_D_SK THEN "MATCH"
ELSE "FAIL"
END) AS MATCH_TYPE
FROM DATAMART.TABLE_A a
LEFT JOIN (SELECT
DISTINCT LINE_SCTGRY_D_SK
FROM DATAMART.TABLE_B
INSERT INTO myself.FIN_HASHKEY_MATCH
SELECT
x.TABLE_NAME
,x.JOIN_TABLE_NAME
,x.HASH_KEY_NAME
,x.MATCH_TYPE
,count(*) AS TOTALS
FROM
(
SELECT
DISTINCT a.MEMBER_D_SK
,'TABLE_B' AS JOIN_TABLE_NAME
,'MEMBER_D_SK' AS HASH_KEY_NAME
, 'TABLE_A' AS Table_Name
,(CASE
WHEN
a.MEMBER_D_SK = z.MEMBER_D_SK THEN "MATCH"
ELSE "FAIL"
END) AS MATCH_TYPE
FROM DATAMART.TABLE_A a
LEFT JOIN (SELECT
DISTINCT MEMBER_D_SK
FROM DATAMART.TABLE_B) z
ON a.MEMBER_D_SK = z.ME
I am having 20 tables ( Each table has a PK and data ), i want to find out what is the current MAX(PK) Value for each table.
I Want the result as follows :
TABLE_NAME MAX_VAL
-------------------- ----------
TABELE_A 114
TABELE_B 55
TABELE_C 14
TABELE_D 866
TABELE_3 4552
is there any way to accomplish this or else i have to write 20 times SELECT MAX(PK_COL) FROM TABLE ?
Assuming your currently connected schema is composed of those twenty tables, and each have identical primary key column name(pk_col), then consider the following code block containing an implicit cursor :
declare
v_max pls_integer;
begin
dbms_output.put_line('table_name max_val');
for c in ( select * from user_tables )
loop
execute immediate 'select max(pk_col) from '||c.table_name into v_max;
dbms_output.put_line(c.table_name||' '||v_max);
end loop;
end;
/
i have found another method which will bring TABLE_NAME,PK_COLUMN and MAX( PK_COLUMN ).
SELECT CASE
WHEN RN = 1 THEN
FORMATTED_QUERY_SET
ELSE
FORMATTED_QUERY_SET || ' UNION ALL '
END AS FORMATTED_QUERY_SET
FROM (SELECT ' SELECT NVL(MAX( ' || COL.COLUMN_NAME ||
' ),0) CURR_MAX_VAL, ''' || TAB.TABLE_NAME ||
''' TABLE_NAME,''' || COL.COLUMN_NAME ||
''' COLUMN_NAME FROM ' || TAB.TABLE_NAME AS FORMATTED_QUERY_SET,
TAB.TABLE_NAME,
ROW_NUMBER() OVER(ORDER BY TAB.TABLE_NAME DESC) AS RN
FROM USER_CONSTRAINTS TAB
JOIN USER_CONS_COLUMNS COL
ON TAB.TABLE_NAME = COL.TABLE_NAME
JOIN USER_TAB_COLUMNS COL2
ON COL.COLUMN_NAME = COL2.COLUMN_NAME
AND COL.TABLE_NAME = COL2.TABLE_NAME
WHERE TAB.CONSTRAINT_TYPE = 'P'
AND COL.CONSTRAINT_NAME LIKE '%_PK'
AND REGEXP_LIKE(COL2.DATA_TYPE, ('NUMB|INTE')))
ORDER BY TABLE_NAME;
Copy the output returned by the above query and execute.
Note : Remove the last ' UNION ALL ' operator from the query string.
Note : Please correct me if i am doing anything wrong .
I need to know what columns of one table have only null values. I understand that I should do a loop in user_tab_columns. But how detect only columns with null value?
Thanks and sorry for my English
To perform a query where you don't know the column identifies in advance, you need to use dynamic SQL. Assuming you already know the table is not empty, you could do something like:
declare
l_count pls_integer;
begin
for r in (
select table_name, column_name
from user_tab_columns
where table_name = 'T42'
and nullable = 'Y'
)
loop
execute immediate 'select count(*) '
|| ' from "' || r.table_name || '"'
|| ' where "' || r.column_name || '" is not null'
into l_count;
if l_count = 0 then
dbms_output.put_line('Table ' || r.table_name
|| ' column ' || r.column_name || ' only has nulls');
end if;
end loop;
end;
/
Remember to set serveroutput on or your client's equivalent before executing.
The cursor gets the columns from the table which are declared as nullable (if they aren't, not much point checking them; though this won't catch explicit check constraints). For each column it builds a query to count the rows where that column is not null. If that count is zero then it didn't find any that are not null, therefore they all are. Again, assuming you know the table isn't empty before you start.
I've included the table name in the cursor select list and references so you only need to change the name in one place to search a different table, or you could use a variable for that name. Or check multiple tables at once by changing that filter.
You may get better performance by selecting a dummy value from any non-null row, with a rownum stop check - which means it will stop as soon as it finds a non-null value, rather than having to check every row to get an actual count:
declare
l_flag pls_integer;
begin
for r in (
select table_name, column_name
from user_tab_columns
where table_name = 'T42'
and nullable = 'Y'
)
loop
begin -- inner block to allow exception trapping within loop
execute immediate 'select 42 '
|| ' from "' || r.table_name || '"'
|| ' where "' || r.column_name || '" is not null'
|| ' and rownum < 2'
into l_flag;
-- if this foudn anything there is a non-null value
exception
when no_data_found then
dbms_output.put_line('Table ' || r.table_name
|| ' column ' || r.column_name || ' only has nulls');
end;
end loop;
end;
/
or you could do something similar with an exists() check.
If you don't know that the table has data then you can do a simple count(*) from the table before the loop to check if it is empty, and report that instead:
...
begin
if l_count = 0 then
dbms_output.put_line('Table is empty');
return;
end if;
...
Or you could combine it with the cursor query, but this would need some work if you wanted to check multiple tables at once as it would stop as soon as it found any empty one (have to leave you something to do... *8-)
declare
l_count_any pls_integer;
l_count_not_null pls_integer;
begin
for r in (
select table_name, column_name
from user_tab_columns
where table_name = 'T42'
and nullable = 'Y'
)
loop
execute immediate 'select count(*),'
|| ' count(case when "' || r.column_name || '" is not null then 1 end)'
|| ' from "' || r.table_name || '"'
into l_count_any, l_count_not_null;
if l_count_any = 0 then
dbms_output.put_line('Table ' || r.table_name || ' is empty');
exit; -- only report once
elsif l_count_not_null = 0 then
dbms_output.put_line('Table ' || r.table_name
|| ' column ' || r.column_name || ' only has nulls');
end if;
end loop;
end;
/
You could of course populate a collection or make it a pipelined function or whatever if you didn't want to reply on dbms_output, but I assume this is a one-off check so it is probably acceptable.
You can loop though your columns and count null rows. If it is same with your table count, then that column has only null values.
The first question is: one column with zero row could be regarded as only (null) value containing column. But it can remain your decision: the scripts below provide solutions to both ways. (In my opinion: no. The empty columns is not a column with only (null) value)
If you want to know the (null) values about one table you can it with count(column):
select count(column) from table
and when the count(column) = 0 then the column has only (null) value or has no value. (So, you cannot make a correct decision).
E.g. The following three tables (x, y and z) has the following columns:
select * from x;
N_X M_X
---------------
100 (null)
200 (null)
300 (null)
select * from y;
N_Y M_Y
---------------
101 (null)
202 (null)
303 apple
select * from z;
N_Z M_Z
---------------
The count() selects:
select count(n_x), count(m_x) from x;
COUNT(N_X) COUNT(M_X)
-----------------------
3 0
select count(n_y), count(m_y) from y;
COUNT(N_Y) COUNT(M_Y)
-----------------------
3 1
select count(n_z), count(m_Z) from z;
COUNT(N_Z) COUNT(M_Z)
-----------------------
0 0
As you can see, the difference between x and y is appears but you cannot decide that the table z has no rows or only full of (null) values.
The general solution:
I have separeted the schema and the db level but the basic idea is common:
Schema level: the current user’s table
DB level: all users or a chosen schema
The number of (null) in one columns:
all_tab_columns.num_nulls
(Or: user_tab_columns, num_nulls).
And we need the num_rows of the table:
all_all_tables.num_rows
(Or: user_all_tables.num_rows)
Where the num_nulls equals to num_rows there are only (null) values.
Firstly, you need to run the DBMS_STATS for refresh the statistics.
on database level:
exec DBMS_STATS.GATHER_DATABASE_STATS;
(it can use a lot of resources)
on schema level:
EXEC DBMS_STATS.gather_schema_stats('TRANEE',DBMS_STATS.AUTO_SAMPLE_SIZE); (owner = tranee)
-- column with zero row = column has only (null) values -> exclude num_nulls > 0 condition
-- column with zero row <> column has only (null) values -> include num_nulls > 0 condition
the scripts:
-- 1. current user
select
a.table_name,
a.column_name,
a.num_nulls,
b.num_rows
from user_tab_columns a, user_all_tables b
where a.table_name = b.table_name
and num_nulls = num_rows
and num_nulls > 0;
-- 2. chosen user / all user -> exclude the a.owner = 'TRANEE' condition
select
a.owner,
a.table_name,
a.column_name,
a.num_nulls,
b.num_rows
from all_tab_columns a, all_all_tables b
where a.owner = b.owner
and a.table_name = b.table_name
and a.owner = 'TRANEE'
and num_nulls = num_rows
and num_nulls > 0;
TABLE_NAME COLUMN_NAME NUM_NULLS NUM_ROWS
----------------------------------------------------
LEADERS COMM 4 4
EMP_ACTION ACTION 12 12
X M_X 3 3
These tables and columns have only (null) values in tranee schema.
I am wanting to store a Rownum as a variable rather than use a costly Join.
I need to get this from a Select statement as the Rownum will be different on various environments so it cannot be a literal string in the code.
For context, this query is executed on Oracle Siebel CRM schema and it retrieves some products of specific type and attributes.
I tried using the following SQL code in Toad and Oracle SQL Developer, however I am getting the following error:
PLS-00428: an INTO clause is expected in this SELECT statement
Here is the code
DECLARE
PROD_ROW_ID varchar(10) := NULL;
BEGIN
SELECT ROW_ID INTO VIS_ROW_ID FROM SIEBEL.S_PROD_INT WHERE PART_NUM = 'S0146404';
BEGIN
SELECT rtrim(VIS.SERIAL_NUM) || ',' || rtrim(PLANID.DESC_TEXT) || ',' ||
CASE
WHEN PLANID.HIGH = 'TEST123'
THEN
CASE
WHEN to_date(PROD.START_DATE) + 30 > sysdate
THEN 'Y'
ELSE 'N'
END
ELSE 'N'
END
|| ',' || 'GB' || ',' ||
rtrim(to_char(PROD.START_DATE, 'YYYY-MM-DD'))
FROM SIEBEL.S_LST_OF_VAL PLANID
INNER JOIN SIEBEL.S_PROD_INT PROD
ON PROD.PART_NUM = PLANID.VAL
INNER JOIN SIEBEL.S_ASSET NETFLIX
ON PROD.PROD_ID = PROD.ROW_ID
INNER JOIN SIEBEL.S_ASSET VIS
ON VIS.PROM_INTEG_ID = PROD.PROM_INTEG_ID
INNER JOIN SIEBEL.S_PROD_INT VISPROD
ON VIS.PROD_ID = VISPROD.ROW_ID
WHERE PLANID.TYPE = 'Test Plan'
AND PLANID.ACTIVE_FLG = 'Y'
AND VISPROD.PART_NUM = VIS_ROW_ID
AND PROD.STATUS_CD = 'Active'
AND VIS.SERIAL_NUM IS NOT NULL;
END;
END;
/
In PLSQL block, columns of select statements must be assigned to variables, which is not the case in SQL statements.
The second BEGIN's SQL statement doesn't have INTO clause and that caused the error.
DECLARE
PROD_ROW_ID VARCHAR (10) := NULL;
VIS_ROW_ID NUMBER;
DSC VARCHAR (512);
BEGIN
SELECT ROW_ID
INTO VIS_ROW_ID
FROM SIEBEL.S_PROD_INT
WHERE PART_NUM = 'S0146404';
BEGIN
SELECT RTRIM (VIS.SERIAL_NUM)
|| ','
|| RTRIM (PLANID.DESC_TEXT)
|| ','
|| CASE
WHEN PLANID.HIGH = 'TEST123'
THEN
CASE
WHEN TO_DATE (PROD.START_DATE) + 30 > SYSDATE
THEN
'Y'
ELSE
'N'
END
ELSE
'N'
END
|| ','
|| 'GB'
|| ','
|| RTRIM (TO_CHAR (PROD.START_DATE, 'YYYY-MM-DD'))
INTO DSC
FROM SIEBEL.S_LST_OF_VAL PLANID
INNER JOIN SIEBEL.S_PROD_INT PROD
ON PROD.PART_NUM = PLANID.VAL
INNER JOIN SIEBEL.S_ASSET NETFLIX
ON PROD.PROD_ID = PROD.ROW_ID
INNER JOIN SIEBEL.S_ASSET VIS
ON VIS.PROM_INTEG_ID = PROD.PROM_INTEG_ID
INNER JOIN SIEBEL.S_PROD_INT VISPROD
ON VIS.PROD_ID = VISPROD.ROW_ID
WHERE PLANID.TYPE = 'Test Plan'
AND PLANID.ACTIVE_FLG = 'Y'
AND VISPROD.PART_NUM = VIS_ROW_ID
AND PROD.STATUS_CD = 'Active'
AND VIS.SERIAL_NUM IS NOT NULL;
END;
END;
/
References
http://docs.oracle.com/cd/E11882_01/appdev.112/e25519/static.htm#LNPLS00601
http://docs.oracle.com/cd/B19306_01/appdev.102/b14261/selectinto_statement.htm#CJAJAAIG
http://pls-00428.ora-code.com/
SELECT 'ALTER TABLE ' || uc.table_name || ' DROP CONSTRAINT ' || uc.constraint_name FROM user_constraints uc WHERE constraint_type = 'R';
returns i.e. some statements which I can execute manually like:
ALTER TABLE APPLICATIONUSERROLE DROP CONSTRAINT APP_APPUSERROLE_FK1
ALTER TABLE APPLICATIONUSERROLE DROP CONSTRAINT USER_APPUSERROLE_FK1
ALTER TABLE APPLICATIONUSERROLE DROP CONSTRAINT ROLE_APPUSERROLE_FK1
What do I have to do to execute them automatically?
For example I tried:
EXECUTE IMMEDIATE (SELECT 'ALTER TABLE ' || uc.table_name || ' DROP CONSTRAINT ' || uc.constraint_name FROM user_constraints uc WHERE constraint_type = 'R');
but thats not working at all throwing some errors.
Thanks in advance.
begin
for i in (SELECT 'ALTER TABLE ' || uc.table_name || ' DROP CONSTRAINT ' || uc.constraint_name as sql_text FROM user_constraints uc WHERE constraint_type = 'R')
loop
execute immediate i.sql_text;
end loop;
end;