Understading table dependencies caused by referential constraints in oracle

At work last week - I was doing some iterative development which required me to start from a clean database every time. The solution provided by the DBA’s in my team was either a complete reinitialization of the schema OR a script that disables every constraint, truncates all the tables and re-enables all the constraints. Both of these solutions were dreadfully slow.

This was a bit of a hassle because we have an extremely complex schema with a lot of inter-dependencies between tables. Therefore like any self respecting developer I decided to script my way out - so the problem was to:

1. Figure out all the referential constraints from the database

2. Order the deletes in a manner that none of the constraints are violated

A little bit of reading and research introduced me to the all_constraints table ..

SQL> desc all_constraints;
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------

 OWNER                                     NOT NULL VARCHAR2(30)
 CONSTRAINT_NAME                           NOT NULL VARCHAR2(30)
 CONSTRAINT_TYPE                                    VARCHAR2(1)
 TABLE_NAME                                NOT NULL VARCHAR2(30)
 SEARCH_CONDITION                                   LONG
 R_OWNER                                            VARCHAR2(30)
 R_CONSTRAINT_NAME                                  VARCHAR2(30)
 DELETE_RULE                                        VARCHAR2(9)
 STATUS                                             VARCHAR2(8)
 DEFERRABLE                                         VARCHAR2(14)
 DEFERRED                                           VARCHAR2(9)
 VALIDATED                                          VARCHAR2(13)
 GENERATED                                          VARCHAR2(14)
 BAD                                                VARCHAR2(3)
 RELY                                               VARCHAR2(4)
 LAST_CHANGE                                        DATE
 INDEX_OWNER                                        VARCHAR2(30)
 INDEX_NAME                                         VARCHAR2(30)
 INVALID                                            VARCHAR2(7)
 VIEW_RELATED                                       VARCHAR2(14)

SQL>

The columns of interest here is the R_CONSTRAINT_NAMEĀ  which is the constraint on which the constraint in question relies on. A join with the user_constraint table can then give the actual table on which the current table in question relies on -

select a.constraint_name as acn, a.table_name as atn, b.table_name as btn,
  b.constraint_name  as bcn from all_constraints a, user_constraints b where
  a.r_constraint_name = b.constraint_name

After this its a cake walk - here is the groovy script that deletes the table in the correct order.

import groovy.sql.Sql

def sql = Sql.newInstance("jdbc:oracle:thin:username/password@//localhost:1521/SID",
        "oracle.jdbc.driver.OracleDriver")
long start = System.currentTimeMillis()
def dependencies = [:]
def deleted = new HashSet();

sql.eachRow("select table_name from user_tables where temporary = 'N' and table_name not like 'MVC_%' and table_name not like 'MV_%'") {
  dependencies[it.table_name] = new HashSet();
}

sql.eachRow("""select a.constraint_name as acn, a.table_name as atn, b.table_name as btn,
  b.constraint_name  as bcn from all_constraints a, user_constraints b where
  a.r_constraint_name = b.constraint_name""") {
  dependencies[it.btn] << it.atn
}

println "Getting all tables and resolving referential constraints finished ${System.currentTimeMillis() - start} ms"

start = System.currentTimeMillis();

int size = dependencies.keySet().size()
while (deleted.size() < size) {
  int initSize = deleted.size();
  dependencies.keySet().each {table->
    def reducedSet = dependencies[table].grep {!deleted.contains(it)}
    if (reducedSet.size() ==0 || (reducedSet.size() == 1 && reducedSet.iterator().next() == table)) {
      deleted.add(table)
      println "deleting $table"
      String query = "delete from $table"
      sql.execute(query)
    }
  }

  if (initSize == deleted.size()) {
    println "PROBLEM .. "
    dependencies.keySet().each() {table->
      if (deleted.contains(table)) {
        return;
      }
      println "$table -> ${dependencies[table].grep {!deleted.contains(it)}}"
    }
    throw new IllegalStateException("Cannot delete tables because of cyclic dependencies")
  }
}

println "Deleting all tables completed in ${System.currentTimeMillis() - start}"

I hope this saves you as much time as it has already saved me.