F
For me data integrity is essential, yet often overlooked by others. I remember project with 1000+ tables and zero foreign keys, because "they were slowing them down". Trading performance for data integrity should be exception, not a rule. If you know little about constraints I suggest to start in latest documentation to gain some background. You can also explore other articles with constraint label.
To get a list of constraints in your schema you can query user_constraints view.
SELECT n.table_name, n.constraint_type, n.constraint_name, n.status, CASE WHEN n.generated = 'GENERATED NAME' THEN 'YES' END AS generated FROM user_constraints n WHERE n.table_name NOT IN (SELECT object_name FROM RECYCLEBIN) ORDER BY n.table_name, n.constraint_type;
Primary keys, foreign keys and unique constraints
These queries can be used for more detailed overview of primary keys, foreign keys and unique constraints. Checks are a bit complicated.
SELECT n.table_name, n.constraint_name, CASE WHEN n.generated = 'GENERATED NAME' THEN 'YES' END AS generated, LISTAGG(c.column_name, ', ') WITHIN GROUP (ORDER BY c.position) AS cols, n.status, n.delete_rule, n.deferrable, n.deferred FROM user_constraints n JOIN user_cons_columns c ON c.constraint_name = n.constraint_name WHERE n.constraint_type = 'P' -- 'U' is also possible GROUP BY n.table_name, n.constraint_name, n.generated, n.status, n.delete_rule, n.deferrable, n.deferred ORDER BY 1, 2;
SELECT n.table_name, n.constraint_name, CASE WHEN n.generated = 'GENERATED NAME' THEN 'YES' END AS generated, LISTAGG(c.column_name, ', ') WITHIN GROUP (ORDER BY c.position) AS cols, k.table_name AS primary_table, LISTAGG(p.column_name, ', ') WITHIN GROUP (ORDER BY p.position) AS primary_cols, n.r_constraint_name AS primary_constraint, n.status, n.delete_rule, n.deferrable, n.deferred FROM user_constraints n JOIN user_cons_columns c ON c.constraint_name = n.constraint_name JOIN user_cons_columns p ON p.constraint_name = n.r_constraint_name AND p.position = c.position JOIN user_constraints k ON k.constraint_name = n.r_constraint_name WHERE n.constraint_type = 'R' GROUP BY n.table_name, n.constraint_name, n.generated, k.table_name, n.r_constraint_name, n.status, n.delete_rule, n.deferrable, n.deferred ORDER BY 1, 2;
Identify NOT NULL constraints
Only way how to distinguish between these two is to check search_condition column in user_constraints view. Unfortunatelly it is a LONG type and thus it is diffucult to access it in query. But there are several workarounds. I like this one:
WITH x AS ( SELECT XMLTYPE(DBMS_XMLGEN.GETXML(q'[SELECT c.constraint_name AS name, c.search_condition AS text FROM user_constraints c WHERE c.constraint_type = 'C' ]')) AS xml_ FROM DUAL ), f AS ( SELECT EXTRACTVALUE(s.object_value, '/ROW/NAME') AS constraint_name, EXTRACTVALUE(s.object_value, '/ROW/TEXT') AS search_condition FROM x CROSS JOIN TABLE(XMLSEQUENCE(EXTRACT(x.xml_, '/ROWSET/ROW'))) s ) SELECT n.table_name, n.constraint_name, CASE WHEN n.generated = 'GENERATED NAME' THEN 'YES' END AS generated FROM user_constraints n JOIN user_cons_columns c ON c.constraint_name = n.constraint_name JOIN f ON f.constraint_name = n.constraint_name AND f.search_condition = '"' || c.column_name || '" IS NOT NULL' WHERE n.constraint_type = 'C' AND n.table_name NOT IN ( SELECT object_name FROM RECYCLEBIN ) ORDER BY 1, 2;
For checks you will do just the opposite.
WITH x AS ( SELECT XMLTYPE(DBMS_XMLGEN.GETXML(q'[SELECT c.constraint_name AS name, c.search_condition AS text FROM user_constraints c WHERE c.constraint_type = 'C' ]')) AS xml_ FROM DUAL ), f AS ( SELECT EXTRACTVALUE(s.object_value, '/ROW/NAME') AS constraint_name, EXTRACTVALUE(s.object_value, '/ROW/TEXT') AS search_condition FROM x CROSS JOIN TABLE(XMLSEQUENCE(EXTRACT(x.xml_, '/ROWSET/ROW'))) s ) SELECT n.table_name, n.constraint_name, CASE WHEN n.generated = 'GENERATED NAME' THEN 'YES' END AS generated, n.status, n.delete_rule, n.deferrable, n.deferred FROM user_constraints n JOIN user_cons_columns c ON c.constraint_name = n.constraint_name JOIN f ON f.constraint_name = n.constraint_name AND f.search_condition != '"' || c.column_name || '" IS NOT NULL' WHERE n.constraint_type = 'C' AND n.table_name NOT IN ( SELECT object_name FROM RECYCLEBIN ) ORDER BY 1, 2;
Enable or disable constraints (with cascade)
You can enable/disable selected types of constraints for specified table(s) with following script:
DECLARE in_tables_like CONSTANT VARCHAR2(30) := '%'; in_constraint_types CONSTANT VARCHAR2(4) := 'PURC'; -- [PURC] in_target_status CONSTANT VARCHAR2(30) := 'ENABLE'; BEGIN FOR c IN ( SELECT n.table_name, n.constraint_name, n.status FROM user_constraints n WHERE n.status != in_target_status || 'D' AND n.table_name LIKE UPPER(in_tables_like) AND n.table_name NOT IN (SELECT object_name FROM RECYCLEBIN) AND INSTR(UPPER(in_constraint_types), n.constraint_type) > 0 ORDER BY CASE WHEN in_target_status = 'ENABLE' THEN DECODE(n.constraint_type, 'P', 1, 'U', 2, 'R', 3, 'C', 4, 5) END, CASE WHEN in_target_status = 'DISABLE' THEN DECODE(n.constraint_type, 'C', 1, 'R', 2, 'U', 3, 'P', 4, 5) END, n.table_name, n.constraint_name ) LOOP DBMS_OUTPUT.PUT_LINE(c.table_name || ' ' || c.constraint_name); EXECUTE IMMEDIATE 'ALTER TABLE ' || c.table_name || ' ' || in_target_status || ' CONSTRAINT ' || c.constraint_name || CASE WHEN in_target_status = 'DISABLE' THEN ' CASCADE' END; END LOOP; END; /
In next article I will show you how to fix constraint names and why you should do that.
Comments
Post a Comment