On RDBMS, SQL and the DRY Principle, and Query Networks

I saw a link a week ago or so on my Twitter feed to an article published by one Lance Gutteridge on 1 June 2018: What I’m Telling Business People About Why Relational Databases Are So Bad. The article is written in a inflammatory style, here’s a sample quote:

Relational databases have been the worst technology to ever poison a field of endeavor

He classifies the ‘badness’ in three main categories:

  • SQL Injection
  • SQL “is a total violation of the DRY principle”
  • Object-Relational Impedance Mismatch

In this article I want to briefly discuss his criticisms under each of these categories, and then move on to discuss some interesting features of SQL queries and joins arising from the fact that SQL plainly does NOT violate the DRY principle. I’ll also discuss how the concept of the network, initially applied to table relationships, can be a very useful design concept in both data modelling and query design.

Part I: Comments on the Lance Gutteridge article
SQL Injection
From Wikipedia, SQL injection:

SQL injection is a code injection technique, used to attack data-driven applications, in which nefarious SQL statements are inserted into an entry field for execution (e.g. to dump the database contents to the attacker).

SQL injection has indeed been a real vulnerability for database systems in the past, but it is an avoidable problem today. As the Wikipedia article puts it:

An SQL injection is a well known attack and easily prevented by simple measures.

SQL “is a total violation of the DRY principle”
Dr. Gutteridge notes that relationships are defined in an RDBMS by foreign keys and primary keys on the tables, and that having to make join relations explicitly in SQL is a repetition of information already known, and hence violates the “Don’t Repeat Yourself” principle.

This criticism is easily dealt with: In general the table relationships do not in fact fully determine the joins in a query. A simple, and very common, example arises in order entry systems. Consider the following simplified 3-table data model:

Here we have an order entity with a foreign key link to a customer, and two foreign key links to the address entity. A customer may have multiple addresses that can serve as shipping or billing addresses on any given order. A particular query may require one or other, or both, or neither of the addresses for the order. The primary key/foreign key relationships cannot determine which tables and links to include without the query specifying them.

The usual way to specify this information in ANSI-standard SQL is to use JOIN/ON-clauses like this:

JOIN addresses add_b ON add_b.address_id = ord.billing_address_id

There are also situations in which joins can be expressed more concisely, and we’ll look at some of them in part II, but it’s clear that these clauses do not in any meaningful way violate the DRY principle.

Object-Relational Impedance Mismatch
In one of the few views on which I am inclined to agree with Dr. Gutteridge, he regards the term as “technobabble”, but it does describe a real phenomenon. Dr. Gutteridge expresses it thus:

…the data in a relational database is stored in ways more in keeping with a 1980s programming language than with a modern, object-oriented language

Though this mismatch does exist, it’s unlikely that dropping the relational model is the answer, because it solves a more fundamental problem. An article from 29 November 2017, Important Papers: Codd and the Relational Model, includes the following:

…Codd motivates the search for a better model by arguing that we need “data independence,” which he defines as “the independence of application programs and terminal activities from growth in data types and changes in data representation.” The relational model, he argues, “appears to be superior in several respects to the graph or network model presently in vogue,” partly because, among other benefits, the relational model “provides a means of describing data with its natural structure only.” By this he meant that programs could safely ignore any artificial structures (like trees) imposed upon the data for storage and retrieval purposes only.

I remember when I started my programming career in 1984 most of the work on any application was spent in writing code simply to store and retrieve data in application-specific formats. Within a few years that effort became largely unnecessary with the introduction of the Oracle RDBMS and SQL. Although modern big data requirements mean other approaches to data storage are also needed, the relational model isn’t going away.

In one of the unwitting ironies in Dr. Gutteridge’s article, he states towards the end that:

there are programmers who have never really seen any other kind of database and believe that all databases are relational

while apparently believing that all modern programming language are object-oriented. They aren’t, and while OOP isn’t going away, it has real deficiencies in modelling the real world that have led to growing interest in other paradigms such as functional programming, as well as old fashioned imperative programming. Here’s an interesting review of some of those deficiencies from 23 July 2016:
Goodbye, Object Oriented Programming

Part II: On SQL and DRY – Joins via NATURAL/USING/ON
In this second part we’ll use two subsets of Oracle’s HR demo schema as examples, and we’ll ignore any links in the tables to tables other than those depicted in the ERDs. Let’s see how, in some cases, we can use ANSI join syntax to avoid explicitly listing all the join column names, but that there are drawbacks to doing so.

Tree Data Model – Department 110, Location, Country, Region – NATURAL JOIN
The ERD below shows a simple linear tree structure.

Let’s start by considering a situation where we don’t need to specify the full join clause with fields on both sides.

 DEPARTMENT_NAME                STREET_ADDRESS                           CITY                           COUNTRY_NAME                             REGION_NAME
------------------------------ ---------------------------------------- ------------------------------ ---------------------------------------- -------------------------
Accounting                     2004 Charade Rd                          Seattle                        United States of America                 Americas

  1  SELECT department_name, street_address, city, country_name, region_name
  2    FROM departments
  3      NATURAL JOIN locations
  4      NATURAL JOIN countries
  5      NATURAL JOIN regions
  6*  WHERE department_id = 110

Here in this simple (linear) tree-structured data model we were able to join the three subsequent tables to the driving table, departments, simply by adding the table names after NATURAL JOIN.

So is this a case of the SQL engine reading the data model and constructing the joins without the need for repetition? No, it isn’t. As the documentation tells you, NATURAL JOIN joins by matching fields with the same names on either side. This can be dangerous as the next example shows.

The second example has only two tables, but there is a loop in the structure.

[In the underlying HR schema from which this is extracted there is also a self-join on employees, which we are excluding]
Department 110 employees: NATURAL JOIN gives wrong answer
There are two employees in department 110:


  2    FROM employees
  3*  WHERE department_id = 110

Let’s try to get the employees using NATURAL JOIN, like this:

DEPARTMENT_NAME                LAST_NAME                 FIRST_NAME           MANAGER_ID
------------------------------ ------------------------- -------------------- ----------
Accounting                     Gietz                     William                     205

  1  SELECT department_name, last_name, first_name, manager_id
  2    FROM departments
  3     NATURAL JOIN employees
  4*  WHERE department_id = 110

This returns only one of the two employees because NATURAL JOIN is matching on both department_id and manager_id as they appear in both tables.

Department 110 employees: USING department_id gives right answer
We can get the right answer by joining with the USING keyword, which assumes the column name to join on is the same on both tables, and mentions it explicitly.

DEPARTMENT_NAME                LAST_NAME                 FIRST_NAME
------------------------------ ------------------------- --------------------
Accounting                     Higgins                   Shelley
Accounting                     Gietz                     William

  1  SELECT department_name, last_name, first_name
  2    FROM departments
  3     JOIN employees USING (department_id)
  4*  WHERE department_id = 110

This example shows how USING resolves the earlier NATURAL JOIN error by specifying the field names in common to be used. The next example shows how this does not always work.

Department 110 manager: USING manager_id gives wrong answer

DEPARTMENT_NAME                LAST_NAME                 FIRST_NAME           MANAGER_ID
------------------------------ ------------------------- -------------------- ----------
Accounting                     Gietz                     William                     205

  1  SELECT department_name, last_name, first_name, manager_id
  2    FROM departments dep
  3     JOIN employees USING (manager_id)
  4*  WHERE dep.department_id = 110

From the first query above we know that the manager of department 110 is Shelley Higgins. It’s reported here instead as William Gietz, because his manager is the same as the department’s manager, but Shirley’s is not.

Department 110 manager: ON mgr.employee_id = dep.manager_id gives right answer

DEPARTMENT_NAME                LAST_NAME                 FIRST_NAME
------------------------------ ------------------------- --------------------
Accounting                     Higgins                   Shelley

  1   SELECT department_name, last_name, first_name
  2     FROM departments dep
  3     JOIN employees mgr ON mgr.employee_id = dep.manager_id
  4*  WHERE dep.department_id = 110

Here we we specify the join with the ON-clause linking the columns explicitly on each side of the join. This is the most usual approach to ANSI joins.

Department 110 manager: NATURAL JOIN subqueries
In a recent article (A tribute to Natural Join, 20 August 2018) Frank Pachot suggested that NATURAL JOIN could be more widely used if tables were replaced by subqueries in which all the columns were aliased in such a way that the join columns only would have the same names in the joined tables. The query above, implemented in this way might be written:

------------------------------ ------------------------- --------------------
Accounting                     Higgins                   Shelley

  1  SELECT department_name, mgr_last_name, mgr_first_name
  2    FROM
  3  (SELECT department_id, department_name, manager_id
  4     FROM departments) dep
  6  (SELECT employee_id manager_id, last_name mgr_last_name, first_name mgr_first_name
  7     FROM employees) mgr
  8*  WHERE dep.department_id = 110

This version is much more verbose and it’s much harder to see which are the join columns by scanning the select lists, compared with specifying them in ON clauses.

Conclusions on Joins via NATURAL/USING/ON

  • Very few people use NATURAL JOIN due to the limitation that the join column names, and only those, in each table or subquery have to be the same
  • USING tends to be used in simple ad hoc queries with small numbers of tables, and improves on NATURAL JOIN by listing the join columns explicitly, but again relies on the join column names being the same
  • The most commonly used join mechanism is the ON clause, with column names specified on each side. This avoids the possible pitfalls of the other mechanisms and for complex, real world queries generally results in more maintainable code

Regarding the DRY principle in SQL more generally, I wrote this,
Modularity in SQL: Patterns, Anti-Patterns and the Kitchen Sink, in September 2013 [tl;dr: Functions and complex views are fine as entry-points but using them as building blocks in SQL is usually a bad idea, and subquery factors (WITH clause) are a better approach to SQL modularity].

Part III: On Data Models and Queries Viewed as Networks
In the examples above we saw that when there are two ways of joining a pair of tables it’s no longer possible for the data model alone to determine the join. An entity relationship structure can be represented as a directed network, with entities as nodes and the relationships between them as links. The second example corresponds to a loop in the network, in which there are two ways of getting from the driving node, departments, to the employees node.

Where the relationships between tables are stored in constraints metadata we can use network analysis PL/SQL to show the network structure and then make diagrams to help in understanding schema structures, as I showed here in May 2015:
PL/SQL Pipelined Function for Network Analysis. This diagram, extracted from that article, shows the structure of Oracle’s demo schemas, with what’s known in graph theory as a spanning tree marked in red, and loop-closing links in blue.

Networks - PLSQL, v1.0 - HR

Queries as Networks
In 2009 I was asked to extend the functionality of an Oracle ERP invoice print report in order to support a move to a multi-org ERP structure. The report had a large number (I think around 30) of small queries in various places, such as format triggers and formula columns as well as in the main data model, and I started by combining most of them into a single, fairly complex query plus one smaller, global data query. The report ran much more quickly and I felt was more maintainable since almost all the logic was in one place, and the query could be tested through tools such as Toad. However, as the query was quite complex I was asked to produce some documentation on how it worked. This got me thinking about how ERDs are used to document data models, and whether we could extend those ideas to document queries too.

My initial thought was that a query can be thought of as a route through the data model network, with looping corresponding to repeated table instances in the query. However, it turns out to be much clearer to represent each table instance as its own node on a new network diagram. After I left the company I wrote my ideas up in a general form in a word document on Scribd in May 2009, A Structured Approach to SQL Query Design. Since then I have extended these ideas to include coverage of query constructs such as unions and subquery factors, and use of annotations for clarity. I wrote another article in August 2012 where I apply these extended ideas to some example queries taken from the OTN forum, Query Structure Diagramming. Here’s a diagram from that article:

You can also find examples in several of the articles on combinatorial SQL referenced in Knapsacks and Networks in SQL from December 2017.

How many tables is too many?
Have you ever heard the view expressed, usually by a DBA, that you should not put more than a small number of tables, say 10, in any query? The reasoning given is that the number of join orders for N tables is N!, which for N=10 is 3,628,800 and the query optimiser (CBO) won’t be able to handle that number of permutations. You will probably know from the discussion above why this reasoning is incorrect: The cost optimization problem is really a network path problem, rather than a permutation problem – you look to join (large) tables that are linked to the current rowset rather than than making cartesian joins, so most permutations are never considered.

SQL Developer: Importing Unit Test Repository via Data Modeler

Recently I started looking at the unit testing functionality within Oracle’s SQL Developer, as a possible alternative to ut/PLSQL. Oracle’s Jeff Smith has a useful starter page on this, Unit Testing Your PL/SQL with Oracle SQL Developer. As he notes, the first thing you need to do is set up a unit test repository, which is essentially a schema within an Oracle database that SQL Developer will create for you for the unit test metadata. The schema contains 24 tables, and when I set it up I thought it would be nice to see an entity-relationship diagram for it.

I often draw these myself after running an analysis query on the foreign key network, as described in a recent post, PL/SQL Pipelined Function for Network Analysis. However, in this case I remembered that SQL Developer also has a data modeler component, and I thought it would be a good opportunity to learn how to use this tool to reverse-engineer a diagram from the schema. It’s actually very easy, and in this article I will show the result (see another Jeff Smith post for learning resources on the data modeler, Data Modeling). For good measure, I will include outputs from my own metadata queries, and add similar for Oracle’s HR demo schema (and linked schemas, in version 12.1).

12 July 2015I added a section on the Oracle v11.2 Apex schema, APEX_040000, which has 425 tables, to see how the data modeler handles larger schemas.

Unit Test Repository Schema

I created the diagram by following the wizard from File/Import/Data Dictionary – see the link above for more information on how to use the Data Modeler.

Data Modeler Diagram


Network Analysis Output

Network                      #Links  #Nodes Lev  Node                                                     Link
---------------------------  ------  ------ ---  -------------------------------------------------------  --------------------------------------
UT_LIB_DYN_QUERIES|UT_REPOS      30      20   0  UT_LIB_DYN_QUERIES|UT_REPOS                              ROOT
                                              1  < UT_TEST_IMPL|UT_REPOS                                  ut_test_lib_dyn_queries_fk1|ut_repos
                                              2    < UT_TEST_COVERAGE_STATS|UT_REPOS                      ut_test_cov_stats_ut_t_fk1|ut_repos                                               3      > UT_TEST_IMPL_RESULTS|UT_REPOS                      ut_test_cov_stats_ut_t_fk2|ut_repos
                                              4        < UT_TEST_IMPL_VAL_RESULTS|UT_REPOS                ut_test_impl_val_res_fk3|ut_repos                                               5          > UT_TEST_IMPL|UT_REPOS*                         ut_test_impl_val_res_fk2|ut_repos
                                              5          > UT_VALIDATIONS|UT_REPOS                        ut_test_impl_val_res_fk1|ut_repos
                                              6            > UT_LIB_VALIDATIONS|UT_REPOS                  ut_validations_lib_validn_fk1|ut_repos
                                              6            > UT_TEST_IMPL|UT_REPOS*                       ut_validations_test_impl_fk1|ut_repos
                                              4        > UT_TEST_IMPL|UT_REPOS*                           ut_test_impl_results_ut_t_fk1|ut_repos
                                              4        > UT_TEST_RESULTS|UT_REPOS                         ut_test_impl_results_ut_t_fk2|ut_repos
                                              5          < UT_SUITE_ITEM_RESULTS|UT_REPOS                 ut_suite_item_results_fk2|ut_repos                                               6            > UT_SUITE_RESULTS|UT_REPOS                    ut_suite_item_results_fk1|ut_repos
                                              7              < UT_SUITE_ITEM_RESULTS|UT_REPOS*            ut_suite_item_results_fk3|ut_repos                                               7              > UT_SUITE|UT_REPOS                          ut_suite_results_fk1|ut_repos
                                              8                < UT_STARTUPS|UT_REPOS                     ut_startups_suite_fk1|ut_repos                                               9                  > UT_LIB_STARTUPS|UT_REPOS               ut_startups_lib_validn_fk1|ut_repos
                                              9                  > UT_TEST|UT_REPOS                       ut_startups_test_fk1|ut_repos
                                              0                    < UT_SUITE_ITEMS|UT_REPOS              ut_suite_items_fk2|ut_repos                                               1                      > UT_SUITE|UT_REPOS*                 ut_suite_items_fk1|ut_repos
                                              1                      > UT_SUITE|UT_REPOS*                 ut_suite_items_fk3|ut_repos
                                              0                    < UT_TEARDOWNS|UT_REPOS                ut_teardowns_test_fk1|ut_repos                                               1                      > UT_LIB_TEARDOWNS|UT_REPOS          ut_teardowns_lib_validn_fk1|ut_repos
                                              1                      > UT_SUITE|UT_REPOS*                 ut_teardowns_suite_fk1|ut_repos
                                              0                    < UT_TEST_ARGUMENTS|UT_REPOS           ut_test_arguments_fk|ut_repos
                                              1                      < UT_TEST_IMPL_ARGUMENTS|UT_REPOS    ut_test_impl_arguments_ut_fk1|ut_repos                                               2                        > UT_TEST_IMPL|UT_REPOS*           ut_test_impl_arguments_ut_fk2|ut_repos
                                              1                      < UT_TEST_IMPL_ARG_RESULTS|UT_REPOS  ut_test_impl_arg_results__fk2|ut_repos                                               2                        > UT_TEST_IMPL|UT_REPOS*           ut_test_impl_arg_results__fk1|ut_repos
                                              0                    < UT_TEST_IMPL|UT_REPOS*               ut_test_impl_ut_test_fk1|ut_repos
                                              0                    < UT_TEST_RESULTS|UT_REPOS*            ut_test_results_ut_test_fk1|ut_repos
UT_LOOKUP_CATEGORIES|UT_REPOS      2       3  0  UT_LOOKUP_CATEGORIES|UT_REPOS                            ROOT
                                              1  < UT_LOOKUP_DATATYPES|UT_REPOS                           ut_lookup_datatypes_ut_lo_fk1|ut_repos
                                              2    < UT_LOOKUP_VALUES|UT_REPOS                            ut_lookup_values_ut_looku_fk1|ut_repos

There is one table not shown as it has no links to any other table.

Schema Metadata Analysis Report

SQL> SELECT 'Start: '||dbs.name "Database", ses.sid "Session", ses.osuser "OS User", ses.machine "Machine", To_Char (SYSDATE,'DD-MON-YYYY HH24:MI:SS') "Time",
  2   Replace (Substr(ver.banner, 1, Instr(ver.banner, '64')-4), 'Enterprise Edition Release ', '') "Version"
  3    FROM v$database dbs, v$version ver, v$session ses
  4   WHERE ver.banner LIKE 'Oracle%'
  5     AND audsid = USERENV('sessionid');

Database              Session OS User    Machine              Time                 Version
-------------------- -------- ---------- -------------------- -------------------- ------------------------------
Start: XE                 137 HP-Brendan WORKGROUP\HP-BRENDAN 11-JUL-2015 13:21:25 Oracle Database 11g Express Ed
                              \Brend_000                                           ition Release

SQL> PROMPT '&owner' "Schema", '&tab' "Prefix"
'UT_REPOS' "Schema", '%' "Prefix"
SQL> PROMPT Tables Summary (excluding like '&nottab1', '&nottab2', '$' )
Tables Summary (excluding like '?', '?', '$' )
SQL> SELECT atc.table_name, Nvl2 (atc_w.column_name, 'Yes', NULL)    "Who?",
  2          CASE att.n_att WHEN 0 THEN To_Number(NULL) ELSE att.n_att END  "Attrs",
  3          ind_tot.n_ind "Indexes", Count(atc.column_name)      "Cols"
  4    FROM all_tab_columns   atc
  5      JOIN all_tables atb
  6        ON atb.table_name      = atc.table_name
  7       AND atb.owner     = atc.owner                             -- Join index count
  8        LEFT JOIN (SELECT i.table_owner, i.table_name, Count (i.index_name) n_ind
  9        FROM all_indexes     i
 10       GROUP BY i.table_owner, i.table_name)     ind_tot
 11          ON ind_tot.table_name    = atb.table_name
 12         AND ind_tot.table_owner   = atb.owner       -- Join attribute count
 13        LEFT JOIN (SELECT atc_att.table_name, atc_att.owner, Count(atc_att.column_name) n_att
 14              FROM all_tab_columns     atc_att
 15             WHERE atc_att.column_name   LIKE '%&notcol%'
 16             GROUP BY atc_att.table_name, atc_att.owner)   att
 17          ON att.table_name    = atb.table_name
 18         AND att.owner     = atb.owner
 19    LEFT JOIN all_tab_columns    atc_w         -- Join the Who column if it exists
 20           ON atc_w.table_name   = atb.table_name
 21          AND atc_w.owner      = atb.owner
 22          AND atc_w.column_name    = 'CREATED_BY'        -- Join index with its sequence for table
 23   WHERE atc.table_name      LIKE Upper('&tab'||'%')
 24     AND atc.table_name      NOT LIKE '%$%'
 25     AND atb.table_name      NOT LIKE '%&nottab1%'
 26     AND atb.table_name      NOT LIKE '%&nottab2%'
 27     AND atb.owner     LIKE Upper('&owner')
 28   GROUP BY atc.table_name,  Nvl2 (atc_w.column_name, 'Yes', NULL),
 29   CASE att.n_att WHEN 0 THEN To_Number(NULL) ELSE att.n_att END,
 30          ind_tot.n_ind
 31   ORDER BY 1;

TABLE_NAME                     Who?  Attrs Indexes Cols
------------------------------ ----- ----- ------- ----
UT_LIB_DYN_QUERIES             Yes               2    8
UT_LIB_STARTUPS                Yes               2    8
UT_LIB_TEARDOWNS               Yes               2    8
UT_LIB_VALIDATIONS             Yes               2    8
UT_LOOKUP_CATEGORIES           Yes               2    6
UT_LOOKUP_DATATYPES            Yes               3    8
UT_LOOKUP_VALUES               Yes               2    7
UT_METADATA                                      1    2
UT_STARTUPS                    Yes               4   10
UT_SUITE                       Yes               1    7
UT_SUITE_ITEMS                 Yes               4   10
UT_SUITE_ITEM_RESULTS          Yes               4    8
UT_SUITE_RESULTS               Yes               2   21
UT_TEARDOWNS                   Yes               4   10
UT_TEST                        Yes               2   12
UT_TEST_ARGUMENTS              Yes               2   30
UT_TEST_COVERAGE_STATS         Yes               3   13
UT_TEST_IMPL                   Yes               4   11
UT_TEST_IMPL_ARGUMENTS         Yes               3    9
UT_TEST_IMPL_ARG_RESULTS       Yes               4   12
UT_TEST_IMPL_RESULTS           Yes               3   29
UT_TEST_IMPL_VAL_RESULTS       Yes               4   15
UT_TEST_RESULTS                Yes               2   13
UT_VALIDATIONS                 Yes               3   10

24 rows selected.

SQL> PROMPT Triggers
SQL> SELECT  trg.table_name,
  2   trg.trigger_name    "Trigger",
  3   trg.trigger_type    "Type",
  4   trg.triggering_event    "Event"
  5    FROM all_triggers trg
  6   WHERE trg.table_name      LIKE Upper('&tab'||'%')
  7     AND trg.table_name      NOT LIKE '%$%'
  8     AND trg.table_name      NOT LIKE '%&nottab1%'
  9     AND trg.table_name      NOT LIKE '%&nottab2%'
 10     AND trg.owner     LIKE Upper('&owner')
 11   ORDER BY 1, 2;

TABLE_NAME                     Trigger                        Type                           Event
------------------------------ ------------------------------ ------------------------------ ------------------------------
UT_LIB_DYN_QUERIES             UT_LIB_DYN_QUERIES             BEFORE EACH ROW                INSERT
                               UT_LIB_DYN_QUERIES_UP_TRG      BEFORE EACH ROW                UPDATE
UT_LIB_STARTUPS                UT_LIB_STARTUPS                BEFORE EACH ROW                INSERT
                               UT_LIB_STARTUPS_UP_TRG         BEFORE EACH ROW                UPDATE
UT_LIB_TEARDOWNS               UT_LIB_TEARDOWNS               BEFORE EACH ROW                INSERT
                               UT_LIB_TEARDOWNS_UP_TRG        BEFORE EACH ROW                UPDATE
                               UT_LIB_VALIDATIONS_UP_TRG      BEFORE EACH ROW                UPDATE
                               UT_LOOKUP_CAT_UP_TRG           BEFORE EACH ROW                UPDATE
                               UT_LOOKUP_DATA_TRG             BEFORE EACH ROW                INSERT
                               UT_LOOKUP_VALUE_TRG            BEFORE EACH ROW                INSERT
UT_STARTUPS                    UT_STARTUPS_TRG                BEFORE EACH ROW                INSERT
                               UT_STARTUPS_UP_TRG             BEFORE EACH ROW                UPDATE
UT_SUITE                       UT_SUITE_TRG                   BEFORE EACH ROW                INSERT
                               UT_SUITE_UP_TRG                BEFORE EACH ROW                UPDATE
UT_SUITE_ITEMS                 UT_SUITE_ITEMS_TRG             BEFORE EACH ROW                INSERT
                               UT_SUITE_ITEMS_UP_TRG          BEFORE EACH ROW                UPDATE
                               UT_SUITE_ITEM_RESULTS_UP_TRG   BEFORE EACH ROW                UPDATE
                               UT_SUITE_RESULTS_UP_TRG        BEFORE EACH ROW                UPDATE
UT_TEARDOWNS                   UT_TEARDOWNS_TRG               BEFORE EACH ROW                INSERT
                               UT_TEARDOWNS_UP_TRG            BEFORE EACH ROW                UPDATE
UT_TEST                        UT_TEST_TRG                    BEFORE EACH ROW                INSERT
                               UT_TEST_UP_TRG                 BEFORE EACH ROW                UPDATE
                               UT_TEST_ARGUMENTS_UP_TRG       BEFORE EACH ROW                UPDATE
                               UT_TEST_COVERAGE_STATS_UP_TRG  BEFORE EACH ROW                UPDATE
UT_TEST_IMPL                   UT_TEST_IMPL_TRG               BEFORE EACH ROW                INSERT
                               UT_TEST_IMPL_UP_TRG            BEFORE EACH ROW                UPDATE
                               UT_TEST_IMPL_ARGUMENTS_UP_TRG  BEFORE EACH ROW                UPDATE
                               UT_TEST_IMPL_ARG_RES_UP_TRG    BEFORE EACH ROW                UPDATE
                               UT_TEST_IMPL_RESULTS_UP_TRG    BEFORE EACH ROW                UPDATE
                               UT_TEST_IMPL_VAL_RES_UP_TRG    BEFORE EACH ROW                UPDATE
UT_TEST_RESULTS                UT_TEST_RESULTS_TRG            BEFORE EACH ROW                INSERT
                               UT_TEST_RESULTS_UP_TRG         BEFORE EACH ROW                UPDATE
UT_VALIDATIONS                 UT_VALIDATIONS_TRG             BEFORE EACH ROW                INSERT
                               UT_VALIDATIONS_UP_TRG          BEFORE EACH ROW                UPDATE

46 rows selected.

SQL> PROMPT Foreign Keys
Foreign Keys
SQL> COLUMN "Constraint" FORMAT A30
SQL> BREAK ON "From Table" ON "To Table" ON "Constraint"
SQL> SELECT  con_f.table_name    "From Table",
  2   con_t.table_name    "To Table",
  3   con_f.constraint_name   "Constraint",
  4   col_f.position      "Seq",
  5   col_f.column_name   "Column"
  6    FROM all_constraints     con_f
  7    JOIN all_constraints     con_t
  8      ON con_t.constraint_name   = con_f.r_constraint_name
  9     AND con_t.owner     = con_f.r_owner
 10    JOIN all_cons_columns    col_f
 11      ON con_f.constraint_type   = 'R'
 12     AND col_f.constraint_name   = con_f.constraint_name
 13     AND col_f.owner     = con_f.owner
 14   WHERE
 15      (
 16        (con_f.table_name        LIKE Upper('&tab'||'%')
 17     AND con_f.table_name        NOT LIKE '%$%'
 18     AND con_f.table_name        NOT LIKE '%&nottab1%'
 19     AND con_f.table_name        NOT LIKE '%&nottab2%'
 20     AND con_f.owner       LIKE Upper('&owner')
 21         ) OR
 22        (con_t.table_name        LIKE Upper('&tab'||'%')
 23     AND con_t.table_name        NOT LIKE '%$%'
 24     AND con_t.table_name        NOT LIKE '%&nottab1%'
 25     AND con_t.table_name        NOT LIKE '%&nottab2%'
 26     AND con_t.owner       LIKE Upper('&owner')
 27        ))
 28   ORDER BY 1, 2, 3, 4;

From Table                     To Table                       Constraint                      Seq Column
------------------------------ ------------------------------ ------------------------------ ---- ------------------------------
UT_STARTUPS                    UT_LIB_STARTUPS                UT_STARTUPS_LIB_VALIDN_FK1        1 LIB_STARTUP_ID
                               UT_SUITE                       UT_STARTUPS_SUITE_FK1             1 UT_SID
                               UT_TEST                        UT_STARTUPS_TEST_FK1              1 UT_ID
UT_SUITE_ITEMS                 UT_SUITE                       UT_SUITE_ITEMS_FK1                1 UT_SID
                                                              UT_SUITE_ITEMS_FK3                1 UT_NSID
                               UT_TEST                        UT_SUITE_ITEMS_FK2                1 UT_ID
                                                              UT_SUITE_ITEM_RESULTS_FK3         1 UTR_NSID
                               UT_TEST_RESULTS                UT_SUITE_ITEM_RESULTS_FK2         1 UTR_ID
UT_SUITE_RESULTS               UT_SUITE                       UT_SUITE_RESULTS_FK1              1 UT_SID
                               UT_SUITE                       UT_TEARDOWNS_SUITE_FK1            1 UT_SID
                               UT_TEST                        UT_TEARDOWNS_TEST_FK1             1 UT_ID
UT_TEST_ARGUMENTS              UT_TEST                        UT_TEST_ARGUMENTS_FK              1 UT_ID
                               UT_TEST_IMPL_RESULTS           UT_TEST_COV_STATS_UT_T_FK2        1 UTIR_ID
                               UT_TEST                        UT_TEST_IMPL_UT_TEST_FK1          1 UT_ID
                               UT_TEST_IMPL                   UT_TEST_IMPL_ARGUMENTS_UT_FK2     1 UTI_ID
                               UT_TEST_IMPL                   UT_TEST_IMPL_ARG_RESULTS__FK1     1 UTI_ID
                               UT_TEST_RESULTS                UT_TEST_IMPL_RESULTS_UT_T_FK2     1 UTR_ID
                               UT_TEST_IMPL_RESULTS           UT_TEST_IMPL_VAL_RES_FK3          1 UTIR_ID
                               UT_VALIDATIONS                 UT_TEST_IMPL_VAL_RES_FK1          1 VAL_ID
UT_TEST_RESULTS                UT_TEST                        UT_TEST_RESULTS_UT_TEST_FK1       1 UT_ID
                               UT_TEST_IMPL                   UT_VALIDATIONS_TEST_IMPL_FK1      1 UTI_ID

32 rows selected.

SQL> BREAK ON table_name ON "Who?" ON "Attrs" ON "Indexes"
SQL> PROMPT Tables and Columns (omitting Who and %&notcol%)
Tables and Columns (omitting Who and %?%)
SQL> SELECT atc.table_name, Nvl2 (atc_w.column_name, 'Yes', NULL)      "Who?",
  2          CASE att.n_att WHEN 0 THEN To_Number(NULL) ELSE att.n_att END    "Attrs",
  3          ind_tot.n_ind                "Indexes",
  4          Lower (atc.column_name)|| CASE atc.nullable WHEN 'N' THEN '*' END  column_name,
  5   atc.data_type,
  6   atc.data_length               "Length",
  7                  Max(CASE ind.rn WHEN 1 THEN To_Char(aic.column_position) ELSE ' ' END) ||
  8                  Max(CASE ind.rn WHEN 2 THEN To_Char(aic.column_position) ELSE ' ' END) ||
  9                  Max(CASE ind.rn WHEN 3 THEN To_Char(aic.column_position) ELSE ' ' END) ||
 10                  Max(CASE ind.rn WHEN 4 THEN To_Char(aic.column_position) ELSE ' ' END) ||
 11                  Max(CASE ind.rn WHEN 5 THEN To_Char(aic.column_position) ELSE ' ' END) ||
 12                  Max(CASE ind.rn WHEN 6 THEN To_Char(aic.column_position) ELSE ' ' END) ||
 13                  Max(CASE ind.rn WHEN 7 THEN To_Char(aic.column_position) ELSE ' ' END) ||
 14                  Max(CASE ind.rn WHEN 8 THEN To_Char(aic.column_position) ELSE ' ' END) ||
 15                  Max(CASE ind.rn WHEN 9 THEN To_Char(aic.column_position) ELSE ' ' END) "Index Pos"
 16    FROM all_tab_columns     atc
 17      JOIN all_tables      atb
 18        ON atb.table_name      = atc.table_name
 19       AND atb.owner     = atc.owner                             -- Join index count
 20      LEFT JOIN (SELECT i.table_owner, i.table_name, Count (i.index_name) n_ind
 21              FROM all_indexes       i
 22           GROUP BY i.table_owner, i.table_name)   ind_tot
 23           ON ind_tot.table_name   = atb.table_name
 24          AND ind_tot.table_owner    = atb.owner       -- Join attribute count
 25    LEFT JOIN (SELECT atc_att.table_name, atc_att.owner, Count(atc_att.column_name) n_att
 26          FROM all_tab_columns       atc_att
 27         WHERE atc_att.column_name LIKE '%&notcol%'
 28         GROUP BY atc_att.table_name, atc_att.owner) att
 29           ON att.table_name   = atb.table_name
 30          AND att.owner      = atb.owner
 31    LEFT JOIN all_tab_columns atc_w            -- Join the Who column if it exists
 32           ON atc_w.table_name   = atb.table_name
 33          AND atc_w.owner      = atb.owner
 34          AND atc_w.column_name    = 'CREATED_BY'        -- Join index with its sequence for table
 35    LEFT JOIN (SELECT Row_Number () OVER (PARTITION BY i.table_name, i.table_owner
 36           ORDER BY i.table_name, i.table_owner, i.index_name) rn,
 37         i.table_name, i.table_owner, i.owner, i.index_name
 38          FROM all_indexes i)        ind
 39      ON ind.table_name      = atb.table_name
 40     AND ind.table_owner     = atb.owner
 41          LEFT JOIN all_ind_columns  aic         -- Join columns for the index
 42            ON aic.index_owner   = ind.owner
 43           AND aic.index_name    = ind.index_name
 44           AND aic.column_name   = atc.column_name
 45   WHERE atc.column_name     NOT LIKE '%&notcol%'
 46     AND atc.table_name      LIKE Upper('&tab'||'%')
 47     AND atc.table_name      NOT LIKE '%$%'
 48     AND atb.table_name      NOT LIKE '%&nottab1%'
 49     AND atb.table_name      NOT LIKE '%&nottab2%'
 50     AND atb.owner     LIKE Upper('&owner')
 52   GROUP BY atc.table_name, Nvl2 (atc_w.column_name, 'Yes', NULL), Lower (atc.column_name)||CASE atc.nullable WHEN 'N' THEN '*' END,
 53          CASE att.n_att WHEN 0 THEN To_Number(NULL) ELSE att.n_att END,
 54          ind_tot.n_ind, atc.data_type, atc.data_length
 55   ORDER BY 1, 2;

TABLE_NAME                     Who?  Attrs Indexes COLUMN_NAME                    DATA_TYPE     Length Index Pos
------------------------------ ----- ----- ------- ------------------------------ ------------- ------ ----------
UT_LIB_DYN_QUERIES             Yes               2 created_on*                    TIMESTAMP(6)      11
                                                   lib_dyn_query*                 CLOB            4000
                                                   lib_dyn_query_class*           VARCHAR2         120
                                                   lib_dyn_query_id*              VARCHAR2          40  1
                                                   lib_dyn_query_name*            VARCHAR2         120 1
                                                   updated_by*                    VARCHAR2         120
                                                   updated_on*                    TIMESTAMP(6)      11
UT_LIB_STARTUPS                Yes               2 created_on*                    TIMESTAMP(6)      11
                                                   lib_startup*                   CLOB            4000
                                                   lib_startup_class*             VARCHAR2         120
                                                   lib_startup_id*                VARCHAR2          40  1
                                                   lib_startup_name*              VARCHAR2         120 1
                                                   updated_by*                    VARCHAR2         120
                                                   updated_on*                    TIMESTAMP(6)      11
UT_LIB_TEARDOWNS               Yes               2 created_on*                    TIMESTAMP(6)      11
                                                   lib_teardown*                  CLOB            4000
                                                   lib_teardown_class*            VARCHAR2         120
                                                   lib_teardown_id*               VARCHAR2          40  1
                                                   lib_teardown_name*             VARCHAR2         120 1
                                                   updated_by*                    VARCHAR2         120
                                                   updated_on*                    TIMESTAMP(6)      11
UT_LIB_VALIDATIONS             Yes               2 created_on*                    TIMESTAMP(6)      11
                                                   lib_validation*                CLOB            4000
                                                   lib_validation_class*          VARCHAR2         120
                                                   lib_validation_id*             VARCHAR2          40  1
                                                   lib_validation_name*           VARCHAR2         120 1
                                                   updated_by*                    VARCHAR2         120
                                                   updated_on*                    TIMESTAMP(6)      11
UT_LOOKUP_CATEGORIES           Yes               2 created_on*                    TIMESTAMP(6)      11
                                                   id*                            VARCHAR2          40 1
                                                   name                           VARCHAR2         120  1
                                                   updated_by*                    VARCHAR2         120
                                                   updated_on*                    TIMESTAMP(6)      11
UT_LOOKUP_DATATYPES            Yes               3 cat_id                         VARCHAR2          40  11
                                                   created_on*                    TIMESTAMP(6)      11
                                                   id*                            VARCHAR2          40 1
                                                   type_id                        NUMBER            22
                                                   type_string                    VARCHAR2         120  2
                                                   updated_by*                    VARCHAR2         120
                                                   updated_on*                    TIMESTAMP(6)      11
UT_LOOKUP_VALUES               Yes               2 created_on*                    TIMESTAMP(6)      11
                                                   data_id                        VARCHAR2          40  1
                                                   id*                            VARCHAR2          40 1
                                                   updated_by*                    VARCHAR2         120
                                                   updated_on*                    TIMESTAMP(6)      11
                                                   value                          CLOB            4000
UT_METADATA                                      1 name*                          VARCHAR2         120 1
                                                   value*                         VARCHAR2        2000
UT_STARTUPS                    Yes               4 created_on*                    TIMESTAMP(6)      11
                                                   index_no*                      NUMBER            22
                                                   lib_startup_id                 VARCHAR2          40 1
                                                   startup                        CLOB            4000
                                                   startup_id*                    VARCHAR2          40  1
                                                   updated_by*                    VARCHAR2         120
                                                   updated_on*                    TIMESTAMP(6)      11
                                                   ut_id                          VARCHAR2          40    1
                                                   ut_sid                         VARCHAR2          40   1
UT_SUITE                       Yes               1 coverage                       NUMBER            22
                                                   created_on*                    TIMESTAMP(6)      11
                                                   name*                          VARCHAR2         120
                                                   updated_by*                    VARCHAR2         120
                                                   updated_on*                    TIMESTAMP(6)      11
                                                   ut_sid*                        VARCHAR2          40 1
UT_SUITE_ITEMS                 Yes               4 created_on*                    TIMESTAMP(6)      11
                                                   run_start*                     VARCHAR2           1
                                                   run_tear*                      VARCHAR2           1
                                                   sequence*                      NUMBER            22
                                                   updated_by*                    VARCHAR2         120
                                                   updated_on*                    TIMESTAMP(6)      11
                                                   ut_id                          VARCHAR2          40   12
                                                   ut_nsid                        VARCHAR2          40  1 3
                                                   ut_sid*                        VARCHAR2          40 1  1
UT_SUITE_ITEM_RESULTS          Yes               4 created_on*                    TIMESTAMP(6)      11
                                                   sequence*                      NUMBER            22
                                                   updated_by*                    VARCHAR2         120
                                                   updated_on*                    TIMESTAMP(6)      11
                                                   utr_id                         VARCHAR2          40  1 2
                                                   utr_nsid                       VARCHAR2          40   13
                                                   utsr_id*                       VARCHAR2          40 1  1
UT_SUITE_RESULTS               Yes               2 coverage*                      NUMBER            22
                                                   created_on*                    TIMESTAMP(6)      11
                                                   message                        VARCHAR2        2000
                                                   name*                          VARCHAR2         120
                                                   run_date*                      TIMESTAMP(6)      11
                                                   startup_duration               NUMBER            22
                                                   startup_end_time               VARCHAR2          20
                                                   startup_message                VARCHAR2        2000
                                                   startup_start_time             VARCHAR2          20
                                                   startup_status                 VARCHAR2          20
                                                   status*                        VARCHAR2          20
                                                   teardown_duration              NUMBER            22
                                                   teardown_end_time              VARCHAR2          20
                                                   teardown_message               VARCHAR2        2000
                                                   teardown_start_time            VARCHAR2          20
                                                   teardown_status                VARCHAR2          20
                                                   updated_by*                    VARCHAR2         120
                                                   updated_on*                    TIMESTAMP(6)      11
                                                   ut_sid*                        VARCHAR2          40 1
                                                   utsr_id*                       VARCHAR2          40  1
UT_TEARDOWNS                   Yes               4 created_on*                    TIMESTAMP(6)      11
                                                   index_no*                      NUMBER            22
                                                   lib_teardown_id                VARCHAR2          40 1
                                                   teardown                       CLOB            4000
                                                   teardown_id*                   VARCHAR2          40  1
                                                   updated_by*                    VARCHAR2         120
                                                   updated_on*                    TIMESTAMP(6)      11
                                                   ut_id                          VARCHAR2          40    1
                                                   ut_sid                         VARCHAR2          40   1
UT_TEST                        Yes               2 connection_name                VARCHAR2         120
                                                   coverage                       NUMBER            22
                                                   created_on*                    TIMESTAMP(6)      11
                                                   name*                          VARCHAR2         120  1
                                                   object_call                    VARCHAR2         120
                                                   object_name*                   VARCHAR2         120
                                                   object_owner*                  VARCHAR2         120
                                                   object_type*                   VARCHAR2         120
                                                   updated_by*                    VARCHAR2         120
                                                   updated_on*                    TIMESTAMP(6)      11
                                                   ut_id*                         VARCHAR2          40 1
UT_TEST_ARGUMENTS              Yes               2 arg_id*                        VARCHAR2          40  1
                                                   argument_name                  VARCHAR2          30
                                                   char_length                    NUMBER            22
                                                   char_used                      VARCHAR2           1
                                                   character_set_name             VARCHAR2          44
                                                   created_on*                    TIMESTAMP(6)      11
                                                   data_length                    NUMBER            22
                                                   data_level*                    NUMBER            22
                                                   data_precision                 NUMBER            22
                                                   data_scale                     NUMBER            22
                                                   data_type                      VARCHAR2          30
                                                   default_length                 NUMBER            22
                                                   in_out                         VARCHAR2           9
                                                   object_id*                     NUMBER            22
                                                   object_name                    VARCHAR2          30
                                                   overload                       VARCHAR2          40
                                                   owner*                         VARCHAR2          30
                                                   package_name                   VARCHAR2          30
                                                   pls_type                       VARCHAR2          30
                                                   position*                      NUMBER            22
                                                   radix                          NUMBER            22
                                                   sequence*                      NUMBER            22
                                                   type_link                      VARCHAR2         128
                                                   type_name                      VARCHAR2          30
                                                   type_owner                     VARCHAR2          30
                                                   type_subname                   VARCHAR2          30
                                                   updated_by*                    VARCHAR2         120
                                                   updated_on*                    TIMESTAMP(6)      11
                                                   ut_id                          VARCHAR2          40 1
UT_TEST_COVERAGE_STATS         Yes               3 created_on*                    TIMESTAMP(6)      11
                                                   line                           NUMBER            22
                                                   text                           VARCHAR2        4000
                                                   total_occur                    NUMBER            22
                                                   total_time                     NUMBER            22
                                                   unit_name                      VARCHAR2          30
                                                   unit_owner                     VARCHAR2          30
                                                   updated_by*                    VARCHAR2         120
                                                   updated_on*                    TIMESTAMP(6)      11
                                                   utc_id*                        VARCHAR2          40 1
                                                   uti_id                         VARCHAR2          40  1
                                                   utir_id                        VARCHAR2          40   1
UT_TEST_IMPL                   Yes               4 created_on*                    TIMESTAMP(6)      11
                                                   dynamic_value_query            CLOB            4000
                                                   expected_return                VARCHAR2          20
                                                   expected_return_error          VARCHAR2         200
                                                   lib_dyn_query_id               VARCHAR2          40    1
                                                   name*                          VARCHAR2         120  2
                                                   updated_by*                    VARCHAR2         120
                                                   updated_on*                    TIMESTAMP(6)      11
                                                   ut_id*                         VARCHAR2          40  11
                                                   uti_id*                        VARCHAR2          40 1
UT_TEST_IMPL_ARGUMENTS         Yes               3 arg_id*                        VARCHAR2          40 21
                                                   created_on*                    TIMESTAMP(6)      11
                                                   input_value                    CLOB            4000
                                                   output_value                   CLOB            4000
                                                   test_outval*                   NUMBER            22
                                                   updated_by*                    VARCHAR2         120
                                                   updated_on*                    TIMESTAMP(6)      11
                                                   uti_id*                        VARCHAR2          40 1 1
UT_TEST_IMPL_ARG_RESULTS       Yes               4 arg_id*                        VARCHAR2          40  1
                                                   created_on*                    TIMESTAMP(6)      11
                                                   message                        VARCHAR2        2000
                                                   name                           VARCHAR2         120
                                                   run_date*                      TIMESTAMP(6)      11
                                                   status*                        VARCHAR2          20
                                                   updated_by*                    VARCHAR2         120
                                                   updated_on*                    TIMESTAMP(6)      11
                                                   uti_id*                        VARCHAR2          40 1
                                                   utiar_id*                      VARCHAR2          40    1
                                                   utir_id                        VARCHAR2          40   1
UT_TEST_IMPL_RESULTS           Yes               3 created_on*                    TIMESTAMP(6)      11
                                                   duration                       NUMBER            22
                                                   end_time                       VARCHAR2          20
                                                   message                        VARCHAR2        2000
                                                   name*                          VARCHAR2         120
                                                   op_call_duration               NUMBER            22
                                                   op_call_end_time               VARCHAR2          20
                                                   op_call_message                VARCHAR2        2000
                                                   op_call_start_time             VARCHAR2          20
                                                   op_call_status                 VARCHAR2          20
                                                   run_date*                      TIMESTAMP(6)      11
                                                   start_time                     VARCHAR2          20
                                                   startup_duration               NUMBER            22
                                                   startup_end_time               VARCHAR2          20
                                                   startup_message                VARCHAR2        2000
                                                   startup_start_time             VARCHAR2          20
                                                   startup_status                 VARCHAR2          20
                                                   status*                        VARCHAR2          20
                                                   teardown_duration              NUMBER            22
                                                   teardown_end_time              VARCHAR2          20
                                                   teardown_message               VARCHAR2        2000
                                                   teardown_start_time            VARCHAR2          20
                                                   teardown_status                VARCHAR2          20
                                                   updated_by*                    VARCHAR2         120
                                                   updated_on*                    TIMESTAMP(6)      11
                                                   uti_id*                        VARCHAR2          40  1
                                                   utir_id*                       VARCHAR2          40 1
                                                   utr_id*                        VARCHAR2          40   1
UT_TEST_IMPL_VAL_RESULTS       Yes               4 created_on*                    TIMESTAMP(6)      11
                                                   message                        VARCHAR2        2000
                                                   run_date                       TIMESTAMP(6)      11
                                                   status*                        VARCHAR2          20
                                                   updated_by*                    VARCHAR2         120
                                                   updated_on*                    TIMESTAMP(6)      11
                                                   uti_id*                        VARCHAR2          40  1
                                                   utir_id                        VARCHAR2          40   1
                                                   utivr_id*                      VARCHAR2          40    1
                                                   val_duration                   NUMBER            22
                                                   val_end_time                   VARCHAR2          20
                                                   val_id*                        VARCHAR2          40 1
                                                   val_start_time                 VARCHAR2          20
                                                   val_type*                      VARCHAR2          40
UT_TEST_RESULTS                Yes               2 connection_name                VARCHAR2         120
                                                   coverage*                      NUMBER            22
                                                   created_on*                    TIMESTAMP(6)      11
                                                   message                        VARCHAR2        2000
                                                   name*                          VARCHAR2         120
                                                   run_date*                      TIMESTAMP(6)      11
                                                   status*                        VARCHAR2          20
                                                   test_user_name                 VARCHAR2         120
                                                   updated_by*                    VARCHAR2         120
                                                   updated_on*                    TIMESTAMP(6)      11
                                                   ut_id*                         VARCHAR2          40  1
                                                   utr_id*                        VARCHAR2          40 1
UT_VALIDATIONS                 Yes               3 apply_validation*              NUMBER            22
                                                   created_on*                    TIMESTAMP(6)      11
                                                   index_no*                      NUMBER            22
                                                   lib_validation_id              VARCHAR2          40 1
                                                   updated_by*                    VARCHAR2         120
                                                   updated_on*                    TIMESTAMP(6)      11
                                                   uti_id*                        VARCHAR2          40   1
                                                   validation                     CLOB            4000
                                                   validation_id*                 VARCHAR2          40  1

252 rows selected.

SQL> BREAK ON table_name ON index_name
SQL> SELECT  atb.table_name, ind.index_name||CASE ind.uniqueness WHEN 'UNIQUE' THEN '*' END index_name,
  2   aic.column_position "Seq", Lower(aic.column_name) column_name
  3    FROM all_tables      atb
  4    LEFT JOIN all_indexes      ind
  5           ON ind.table_name   = atb.table_name
  6          AND ind.table_owner    = atb.owner
  7         LEFT JOIN all_ind_columns aic
  8                ON aic.index_name    = ind.index_name
  9               AND aic.index_owner = ind.owner
 10   WHERE atb.table_name      LIKE Upper('&tab'||'%')
 11     AND atb.table_name      NOT LIKE '%$%'
 12     AND atb.table_name      NOT LIKE '%&nottab1%'
 13     AND atb.table_name      NOT LIKE '%&nottab2%'
 14     AND atb.owner     LIKE Upper('&owner')
 15   ORDER BY 1, 2, 3;

TABLE_NAME                     INDEX_NAME                       Seq COLUMN_NAME
------------------------------ ------------------------------- ---- ------------------------------
UT_LIB_DYN_QUERIES             UT_LIB_DYN_QUERIES_NAME*           1 lib_dyn_query_name
                               UT_LIB_DYN_QUERIES_PK*             1 lib_dyn_query_id
UT_LIB_STARTUPS                UT_LIB_STARTUPS_NAME*              1 lib_startup_name
                               UT_LIB_STARTUPS_PK*                1 lib_startup_id
UT_LIB_TEARDOWNS               UT_LIB_TEARDOWNS_NAME*             1 lib_teardown_name
                               UT_LIB_TEARDOWNS_PK*               1 lib_teardown_id
UT_LIB_VALIDATIONS             UT_LIB_VALIDATIONS_NAME*           1 lib_validation_name
                               UT_LIB_VALIDATIONS_PK*             1 lib_validation_id
                               UT_LOOKUP_CATEGORIES_UK1*          1 name
                               UT_LOOKUP_DATATYPES_UK1*           1 cat_id
                                                                  2 type_string
                               UT_LOOKUP_DTS_CAT_ID_IX            1 cat_id
UT_LOOKUP_VALUES               UT_LOOKUP_VALUES_PK*               1 id
                               UT_LOOKUP_VALUES_UT_LOOKU_IX       1 data_id
UT_METADATA                    UT_METADATA_PK*                    1 name
UT_STARTUPS                    UT_STARTUPS_LIB_VALIDN_IX          1 lib_startup_id
                               UT_STARTUPS_PK*                    1 startup_id
                               UT_STARTUPS_SUITE_IX               1 ut_sid
                               UT_STARTUPS_TEST_IX                1 ut_id
UT_SUITE                       UT_SUITE_PK*                       1 ut_sid
UT_SUITE_ITEMS                 UT_SUITE_ITEMS_IX1                 1 ut_sid
                               UT_SUITE_ITEMS_IX2                 1 ut_nsid
                               UT_SUITE_ITEMS_IX3                 1 ut_id
                               UT_SUITE_ITEMS_UK*                 1 ut_sid
                                                                  2 ut_id
                                                                  3 ut_nsid
                               UT_SUITE_ITEM_RESULTS_FK2          1 utr_id
                               UT_SUITE_ITEM_RESULTS_FK3          1 utr_nsid
                               UT_SUITE_ITEM_RESULTS_UK*          1 utsr_id
                                                                  2 utr_id
                                                                  3 utr_nsid
UT_SUITE_RESULTS               UT_SUITE_RESULTS_IX                1 ut_sid
                               UT_SUITE_RESULTS_PK*               1 utsr_id
UT_TEARDOWNS                   UT_TEARDOWNS_LIB_VALIDN_IX         1 lib_teardown_id
                               UT_TEARDOWNS_PK*                   1 teardown_id
                               UT_TEARDOWNS_SUITE_IX              1 ut_sid
                               UT_TEARDOWNS_TEST_IX               1 ut_id
UT_TEST                        UT_TEST_PK*                        1 ut_id
                               UT_TEST_UK1*                       1 name
UT_TEST_ARGUMENTS              UT_TEST_ARGUMENTS_IX               1 ut_id
                               UT_TEST_ARGUMENTS_PK*              1 arg_id
                               UT_TEST_COV_STATS_UT_T_IX1         1 uti_id
                               UT_TEST_COV_STATS_UT_T_IX2         1 utir_id
UT_TEST_IMPL                   UT_TEST_IMPL_PK*                   1 uti_id
                               UT_TEST_IMPL_UK1*                  1 ut_id
                                                                  2 name
                               UT_TEST_IMPL_UT_TEST_IX            1 ut_id
                               UT_TEST_LIB_DYN_QUERIES_IX         1 lib_dyn_query_id
                                                                  2 arg_id
                               UT_TEST_IMPL_ARGUMENTS_UT_IX1      1 arg_id
                               UT_TEST_IMPL_ARGUMENTS_UT_IX2      1 uti_id
                               UT_TEST_IMPL_ARG_RESULTS_IX2       1 arg_id
                               UT_TEST_IMPL_ARG_RESULTS_IX3       1 utir_id
                               UT_TEST_IMPL_ARG_RESULTS_PK*       1 utiar_id
UT_TEST_IMPL_RESULTS           UT_TEST_IMPL_RESULTS_PK*           1 utir_id
                               UT_TEST_IMPL_RESULTS_UT_T_IX1      1 uti_id
                               UT_TEST_IMPL_RESULTS_UT_T_IX2      1 utr_id
                               UT_TEST_IMPL_VAL_RES_IX2           1 uti_id
                               UT_TEST_IMPL_VAL_RES_IX3           1 utir_id
                               UT_TEST_IMPL_VAL_RES_PK*           1 utivr_id
UT_TEST_RESULTS                UT_TEST_RESULTS_PK*                1 utr_id
                               UT_TEST_RESULTS_UT_TEST_IX1        1 ut_id
UT_VALIDATIONS                 UT_VALIDATIONS_LIB_VALIDN_IX       1 lib_validation_id
                               UT_VALIDATIONS_PK*                 1 validation_id
                               UT_VALIDATIONS_TEST_IMPL_IX        1 uti_id

72 rows selected.

SQL> PROMPT Tables with no Who columns / No unique indexes/ Id only unique indexes
Tables with no Who columns / No unique indexes/ Id only unique indexes
SQL> SELECT atb.table_name,  CASE WHEN atc_w.column_name IS NULL THEN 'X' END "Who?",
  2                          CASE WHEN uni.maxind IS NULL THEN 'X' END "No UID?",
  3                          CASE WHEN uni.maxind = 1 THEN 'X' END "Id UID?"
  4    FROM all_tables      atb
  5    LEFT JOIN all_tab_columns    atc_w
  6           ON atc_w.table_name   = atb.table_name
  7          AND atc_w.owner      = atb.owner
  8          AND atc_w.column_name    = 'CREATED_BY'
  9    LEFT JOIN (SELECT ind.table_name, ind.table_owner, Max(CASE WHEN Substr(aic.column_name, Length(aic.column_name)-2) IS NULL THEN 0
 10                     WHEN Substr(aic.column_name, Length(aic.column_name)-2) = '_ID' THEN 1
 11                     ELSE 2 END) maxind
 12                 FROM all_indexes ind
 13                 LEFT JOIN all_ind_columns aic
 14                        ON aic.index_name         = ind.index_name
 15                       AND aic.index_owner        = ind.owner
 16                       AND aic.column_position    = 1
 17                WHERE ind.uniqueness   = 'UNIQUE'
 18                GROUP BY ind.table_name, ind.table_owner) uni
 19           ON uni.table_name     = atb.table_name
 20          AND uni.table_owner      = atb.owner
 21   WHERE atb.table_name      LIKE Upper('&tab'||'%')
 22     AND atb.table_name      NOT LIKE '%$%'
 23     AND atb.table_name      NOT LIKE '%&nottab1%'
 24     AND atb.table_name      NOT LIKE '%&nottab2%'
 25     AND atb.owner     LIKE Upper('&owner')
 26     AND (atc_w.column_name    IS NULL OR uni.maxind IN (0, 1))
 27   ORDER BY 1;

TABLE_NAME                     Who?  No UID? Id UID?
------------------------------ ----- ------- -------
UT_METADATA                    X
UT_STARTUPS                                  X
UT_SUITE_ITEM_RESULTS                        X
UT_SUITE_RESULTS                             X
UT_TEARDOWNS                                 X
UT_TEST_ARGUMENTS                            X
UT_TEST_COVERAGE_STATS                       X
UT_TEST_IMPL                                 X
UT_TEST_IMPL_ARGUMENTS                       X
UT_TEST_IMPL_ARG_RESULTS                     X
UT_TEST_IMPL_RESULTS                         X
UT_TEST_IMPL_VAL_RESULTS                     X
UT_TEST_RESULTS                              X
UT_VALIDATIONS                               X

14 rows selected.

SQL> SELECT 'End: '||name "Database", '&owner' "Schema", '&tab' "Prefix", To_Char(SYSDATE,'DD-MON-YYYY HH24:MI:SS') FROM v$database;

Database             Schema          Prefix     TO_CHAR(SYSDATE,'DD-MON-YYYYH
-------------------- --------------- ---------- -----------------------------
End: XE              UT_REPOS        %          11-JUL-2015 13:21:35

HR Demo Schemas (v12)

I had to import the three schemas (HR, OE and PM) one at a time, merging with the previous one, am not sure if you can do it in one go, or if the diagram is affected by the order of import.

Data Modeler Diagram


Manual Visio Diagram

For comparison, here is a manual diagram, deliberately omitting column and other detail. It was done as an illustration of the network analysis program output, and I therefore did not include useful information such as on relationship optionality.

Networks - HR, v1.0

Network Analysis Output

I copied this from my article above.

Network       #Links  #Nodes Lev  Node                                     Link
------------  ------  ------ ---  ---------------------------------------  -------------------------------
COUNTRIES|HR      21      16   0  COUNTRIES|HR                             ROOT
                               1  < LOCATIONS|HR                           loc_c_id_fk|hr
                               2    < DEPARTMENTS|HR                       dept_loc_fk|hr                                3      > EMPLOYEES|HR                       dept_mgr_fk|hr
                               4        < CUSTOMERS|OE                     customers_account_manager_fk|oe
                               5          < ORDERS|OE                      orders_customer_id_fk|oe                                6            > EMPLOYEES|HR*                orders_sales_rep_fk|oe
                               6            < ORDER_ITEMS|OE               order_items_order_id_fk|oe                                7              > PRODUCT_INFORMATION|OE     order_items_product_id_fk|oe
                               8                < INVENTORIES|OE           inventories_product_id_fk|oe                                9                  > WAREHOUSES|OE          inventories_warehouses_fk|oe
                              10                    > LOCATIONS|HR*        warehouses_location_fk|oe
                               8                < ONLINE_MEDIA|PM          loc_c_id_fk|pm
                               8                < PRINT_MEDIA|PM           printmedia_fk|pm
                               8                < PRODUCT_DESCRIPTIONS|OE  pd_product_id_fk|oe                                4        > DEPARTMENTS|HR*                  emp_dept_fk|hr
                               4        = EMPLOYEES|HR*                    emp_manager_fk|hr
                               4        > JOBS|HR                          emp_job_fk|hr
                               5          < JOB_HISTORY|HR                 jhist_job_fk|hr                                6            > DEPARTMENTS|HR*              jhist_dept_fk|hr
                               6            > EMPLOYEES|HR*                jhist_emp_fk|hr
                               1  > REGIONS|HR                             countr_reg_fk|hr

Schema Metadata Analysis Report – not included

Apex Schema – APEX_040000 (v11.22)
This imported quickly, with 425 tables. However, the diagram was not so useful. Trying to print it to .png or .jpg (via File/Print Diagram/To Image File) silently failed; printing to .pdf worked, but the reader opens at a zoom level of 1.41% and it’s not practical to navigate the links. Maybe text-based analysis reports are more useful for the larger schemas.

Data Modeler Diagram

Here is a screenshot of the modeler diagram:
Network Analysis Output

I include my standard summary listings in this case, showing that the schema splits into 21 sub-networks, with 298 tables having foreign key links, the remaining 127 being thereby excluded from this report.

SQL> @..\sql\R_Net
Network detail

Network                                     #Links  #Nodes    Lev Node                                                                   Link
------------------------------------------ ------- ------- ------ ---------------------------------------------------------------------- ------------------------------------------
APEX$_WS_FILES|APEX_040000                       4       5      0 APEX$_WS_FILES|APEX_040000                                             ROOT
                                                                1 > APEX$_WS_ROWS|APEX_040000                                            apex$_ws_files_fk|apex_040000
                                                                2   < APEX$_WS_LINKS|APEX_040000                                         apex$_ws_links_fk|apex_040000
                                                                2   < APEX$_WS_NOTES|APEX_040000                                         apex$_ws_notes_fk|apex_040000
                                                                2   < APEX$_WS_TAGS|APEX_040000                                          apex$_ws_tags_fk|apex_040000
WWV_FLOWS|APEX_040000                          286     236      0 WWV_FLOWS|APEX_040000                                                  ROOT
                                                                1 < WWV_FLOW_ALTERNATE_CONFIG|APEX_040000                                wwv_flow_alt_cfg_fk|apex_040000
                                                                2   < WWV_FLOW_ALT_CONFIG_DETAIL|APEX_040000                             wwv_flow_alt_cfg_d_fk|apex_040000
                                                                1 < WWV_FLOW_APP_COMMENTS|APEX_040000                                    wwv_flow_app_comments_fk|apex_040000
                                                                1 < WWV_FLOW_BANNER|APEX_040000                                          wwv_flow_banner_fk|apex_040000
                                                                1 < WWV_FLOW_BUTTON_TEMPLATES|APEX_040000                                wwv_flow_buttont_fk|apex_040000
                                                                1 < WWV_FLOW_CALS|APEX_040000                                            wwv_flow_cal_to_flow_fk|apex_040000
                                                                2   > WWV_FLOW_PAGE_PLUGS|APEX_040000                                    wwv_flow_plug_calendar_fk|apex_040000
                                                                3     > WWV_FLOWS|APEX_040000*                                           wwv_flow_plug_to_flow_fk|apex_040000
                                                                3     < WWV_FLOW_FLASH_CHARTS_5|APEX_040000                              wwv_flow_flash_charts_5_fk2|apex_040000
                                                                4       > WWV_FLOWS|APEX_040000*                                         wwv_flow_flash_charts_5_fk|apex_040000
                                                                4       < WWV_FLOW_FLASH_CHART5_SERIES|APEX_040000                       wwv_flow_flash_5_series_fk|apex_040000
                                                                4       < WWV_FLOW_FLASH_CHARTS_5_DASH|APEX_040000                       wwv_flow_flash_charts5_dash_fk|apex_040000
                                                                3     < WWV_FLOW_FLASH_CHARTS|APEX_040000                                wwv_flow_flash_charts_fk2|apex_040000
                                                                4       > WWV_FLOWS|APEX_040000*                                         wwv_flow_flash_charts_fk|apex_040000
                                                                4       < WWV_FLOW_FLASH_CHART_SERIES|APEX_040000                        wwv_flow_flash_chart_series_fk|apex_040000
                                                                3     < WWV_FLOW_PAGE_DA_ACTIONS|APEX_040000                             wwv_flow_page_da_a_ar_fk|apex_040000
                                                                4       > WWV_FLOW_PAGE_DA_EVENTS|APEX_040000                            wwv_flow_page_da_a_evnt_fk|apex_040000
                                                                5         > WWV_FLOWS|APEX_040000*                                       wwv_flow_page_da_e_flow_fk|apex_040000
                                                                5         > WWV_FLOW_PAGE_PLUGS|APEX_040000*                             wwv_flow_page_da_e_tr_fk|apex_040000
                                                                5         > WWV_FLOW_STEPS|APEX_040000                                   wwv_flow_page_da_e_page_fk|apex_040000
                                                                6           > WWV_FLOWS|APEX_040000*                                     wwv_flow_steps_fk|apex_040000
                                                                6           < WWV_FLOW_PAGE_DA_ACTIONS|APEX_040000*                      wwv_flow_page_da_a_page_fk|apex_040000
                                                                6           < WWV_FLOW_PAGE_PLUGS|APEX_040000*                           wwv_flow_plug_to_page_fk|apex_040000
                                                                6           < WWV_FLOW_STEP_BRANCHES|APEX_040000                         wwv_flow_step_branches_fk2|apex_040000
                                                                7             > WWV_FLOWS|APEX_040000*                                   wwv_flow_step_branches_fk|apex_040000
                                                                7             < WWV_FLOW_STEP_BRANCH_ARGS|APEX_040000                    wwv_flow_step_branch_args_fk|apex_040000
                                                                6           < WWV_FLOW_STEP_BUTTONS|APEX_040000                          wwv_flow_step_buttons_fk2|apex_040000
                                                                7             > WWV_FLOWS|APEX_040000*                                   wwv_flow_step_buttons_fk1|apex_040000
                                                                7             > WWV_FLOW_PAGE_PLUGS|APEX_040000*                         wwv_flow_step_buttons_plug_fk|apex_040000
                                                                6           < WWV_FLOW_STEP_COMPUTATIONS|APEX_040000                     wwv_flow_step_comp_fk2|apex_040000
                                                                7             > WWV_FLOWS|APEX_040000*                                   wwv_flow_step_comp_fk|apex_040000
                                                                6           < WWV_FLOW_STEP_ITEMS|APEX_040000                            wwv_flow_step_items_fk2|apex_040000
                                                                7             > WWV_FLOWS|APEX_040000*                                   wwv_flow_step_items_fk|apex_040000
                                                                7             > WWV_FLOW_PAGE_PLUGS|APEX_040000*                         wwv_flow_step_items_plug_fk|apex_040000
                                                                7             < WWV_FLOW_STEP_ITEM_HELP|APEX_040000                      wwv_flow_item_helptext_fk|apex_040000
                                                                8               > WWV_FLOWS|APEX_040000*                                 wwv_flow_page_helptext_fk|apex_040000
                                                                6           < WWV_FLOW_STEP_PROCESSING|APEX_040000                       wwv_flow_step_proc_fk2|apex_040000
                                                                7             > WWV_FLOWS|APEX_040000*                                   wwv_flow_step_proc_fk|apex_040000
                                                                7             < WWV_FLOW_WS_PROCESS_PARMS_MAP|APEX_040000                wwv_flow_ws_map_fk2|apex_040000
                                                                8               > WWV_FLOW_WS_PARAMETERS|APEX_040000                     wwv_flows_ws_map_fk1|apex_040000
                                                                9                 > WWV_FLOW_WS_OPERATIONS|APEX_040000                   wwv_flow_ws_parms_fk|apex_040000
                                                               10                   > WWV_FLOW_SHARED_WEB_SERVICES|APEX_040000           wwv_flow_ws_opers_fk|apex_040000
                                                               11                     > WWV_FLOWS|APEX_040000*                           wwv_flow_ws_fk|apex_040000
                                                                6           < WWV_FLOW_STEP_VALIDATIONS|APEX_040000                      wwv_flow_step_val_fk2|apex_040000
                                                                7             > WWV_FLOWS|APEX_040000*                                   wwv_flow_step_val_fk|apex_040000
                                                                7             > WWV_FLOW_PAGE_PLUGS|APEX_040000*                         wwv_flow_step_val_to_reg_fk|apex_040000
                                                                3     < WWV_FLOW_PAGE_GENERIC_ATTR|APEX_040000                           wwv_flow_genattr_to_region_fk|apex_040000
                                                                3     = WWV_FLOW_PAGE_PLUGS|APEX_040000*                                 wwv_flow_plug_parent_fk|apex_040000
                                                                3     < WWV_FLOW_QUERY_DEFINITION|APEX_040000                            query_def_to_region_fk|apex_040000
                                                                4       < WWV_FLOW_QUERY_COLUMN|APEX_040000                              query_column_to_query_fk|apex_040000
                                                                5         > WWV_FLOW_QUERY_OBJECT|APEX_040000                            query_column_to_qry_object_fk|apex_040000
                                                                6           > WWV_FLOW_QUERY_DEFINITION|APEX_040000*                     query_object_to_query_fk|apex_040000
                                                                4       < WWV_FLOW_QUERY_CONDITION|APEX_040000                           query_condition_to_query_fk|apex_040000
                                                                3     < WWV_FLOW_REGION_CHART_SER_ATTR|APEX_040000                       wwv_flow_seattr_to_region_fk|apex_040000
                                                                3     < WWV_FLOW_REGION_REPORT_COLUMN|APEX_040000                        report_column_to_region_fk|apex_040000
                                                                3     < WWV_FLOW_REGION_REPORT_FILTER|APEX_040000                        sys_c004963|apex_040000
                                                                3     < WWV_FLOW_REGION_UPD_RPT_COLS|APEX_040000                         wwv_flow_urc_to_plug_fk|apex_040000
                                                                4       > WWV_FLOWS|APEX_040000*                                         wwv_flow_urc_to_flow_fk|apex_040000
                                                                3     < WWV_FLOW_TREE_REGIONS|APEX_040000                                wwv_flow_treeregion_fk2|apex_040000
                                                                4       > WWV_FLOWS|APEX_040000*                                         wwv_flow_treeregion_fk|apex_040000
                                                                3     < WWV_FLOW_WORKSHEETS|APEX_040000                                  wwv_flow_worksheets_reg_fk|apex_040000
                                                                4       > WWV_FLOWS|APEX_040000*                                         wwv_flow_worksheets_flow_fk|apex_040000
                                                                4       < WWV_FLOW_WORKSHEET_COLUMNS|APEX_040000                         wwv_flow_worksheet_columns_fk|apex_040000
                                                                5         > WWV_FLOWS|APEX_040000*                                       wwv_flow_worksheet_col_fk|apex_040000
                                                                5         > WWV_FLOW_WORKSHEET_COL_GROUPS|APEX_040000                    wwv_flow_worksheet_col_grps_fk|apex_040000
                                                                6           > WWV_FLOWS|APEX_040000*                                     wwv_flow_worksheet_col_grp_fk|apex_040000
                                                                6           > WWV_FLOW_WORKSHEETS|APEX_040000*                           wwv_flow_worksheet_col_grws_fk|apex_040000
                                                                6           > WWV_FLOW_WS_WEBSHEET_ATTR|APEX_040000                      wwv_flow_worksheet_col_grp_fk2|apex_040000
                                                                7             > WWV_FLOW_WORKSHEETS|APEX_040000*                         wwwv_flow_ws_websheet_attr_fk|apex_040000
                                                                7             < WWV_FLOW_WORKSHEET_COLUMNS|APEX_040000*                  wwv_flow_worksheet_col_fk2|apex_040000
                                                                7             < WWV_FLOW_WORKSHEET_COMPUTATION|APEX_040000               wwv_flow_ws_computation_fk|apex_040000
                                                                8               > WWV_FLOW_WORKSHEET_RPTS|APEX_040000                    wwv_flow_ws_comp_cols_fk|apex_040000
                                                                9                 > WWV_FLOW_WORKSHEET_CATEGORIES|APEX_040000            wwv_flow_worksheet_rpts_fk|apex_040000
                                                                9                 < WWV_FLOW_WORKSHEET_CONDITIONS|APEX_040000            wwv_flow_worksheet_cond_fk|apex_040000
                                                               10                   > WWV_FLOW_WS_WEBSHEET_ATTR|APEX_040000*             wwv_flow_ws_condition_fk|apex_040000
                                                                9                 < WWV_FLOW_WORKSHEET_GROUP_BY|APEX_040000              wwv_flow_ws_groupby_fk2|apex_040000
                                                               10                   > WWV_FLOW_WS_WEBSHEET_ATTR|APEX_040000*             wwv_flow_ws_groupby_fk|apex_040000
                                                                9                 < WWV_FLOW_WORKSHEET_NOTIFY|APEX_040000                wwv_flow_worksheet_notify_fk2|apex_040000
                                                               10                   > WWV_FLOW_WORKSHEETS|APEX_040000*                   wwv_flow_worksheet_notify_fk|apex_040000
                                                               10                   > WWV_FLOW_WS_WEBSHEET_ATTR|APEX_040000*             wwv_flow_worksheet_notify_fk4|apex_040000
                                                                9                 > WWV_FLOW_WS_WEBSHEET_ATTR|APEX_040000*               wwv_flow_ws_rpt_fk|apex_040000
                                                                7             < WWV_FLOW_WORKSHEET_LOVS|APEX_040000                      wwv_flow_worksheet_lovs_fk2|apex_040000
                                                                8               > WWV_FLOW_WORKSHEETS|APEX_040000*                       wwv_flow_worksheet_lovs_fk|apex_040000
                                                                8               < WWV_FLOW_WORKSHEET_LOV_ENTRIES|APEX_040000             wwv_flow_worksheet_lov_ent_fk2|apex_040000
                                                                9                 > WWV_FLOW_WORKSHEETS|APEX_040000*                     wwv_flow_worksheet_lov_ent_fk|apex_040000
                                                                9                 > WWV_FLOW_WS_WEBSHEET_ATTR|APEX_040000*               wwv_flow_worksheet_lov_ent_fk3|apex_040000
                                                                7             > WWV_FLOW_WS_APPLICATIONS|APEX_040000                     wwv_flow_ws_websheet_attr_fk2|apex_040000
                                                                8               < WWV_FLOW_WS_APP_SUG_OBJECTS|APEX_040000                wwv_flow_ws_app_so_fk1|apex_040000
                                                                8               < WWV_FLOW_WS_COL_VALIDATIONS|APEX_040000                wwv_flow_ws_col_val_fk3|apex_040000
                                                                9                 > WWV_FLOW_WORKSHEETS|APEX_040000*                     wwv_flow_ws_col_val_fk|apex_040000
                                                                9                 > WWV_FLOW_WS_WEBSHEET_ATTR|APEX_040000*               wwv_flow_ws_col_val_fk2|apex_040000
                                                                8               < WWV_FLOW_WS_CUSTOM_AUTH_SETUPS|APEX_040000             wwv_flow_ws_auth_setups_fk|apex_040000
                                                                8               < WWV_FLOW_WS_WEBPAGES|APEX_040000                       wwv_flow_ws_webpages_fk|apex_040000
                                                                9                 = WWV_FLOW_WS_WEBPAGES|APEX_040000*                    wwv_flow_ws_webpages_fk2|apex_040000
                                                                1 < WWV_FLOW_CAL_TEMPLATES|APEX_040000                                   wwv_flow_cal_templ_to_flow_fk|apex_040000
                                                                1 < WWV_FLOW_COMPOUND_CONDITIONS|APEX_040000                             wwv_flow_comp_cond_fk|apex_040000
                                                                1 < WWV_FLOW_COMPUTATIONS|APEX_040000                                    wwv_flow_computations_fk|apex_040000
                                                                1 < WWV_FLOW_CUSTOM_AUTH_SETUPS|APEX_040000                              wwv_flow_auth_setups_fk|apex_040000
                                                                1 < WWV_FLOW_ENTRY_POINTS|APEX_040000                                    wwv_flow_entry_points_fk|apex_040000
                                                                2   < WWV_FLOW_ENTRY_POINT_ARGS|APEX_040000                              wwv_flow_entry_point_args_fk|apex_040000
                                                                1 < WWV_FLOW_FIELD_TEMPLATES|APEX_040000                                 wwv_flow_field_temp_f_fk|apex_040000
                                                                1 < WWV_FLOW_ICON_BAR_ATTRIBUTES|APEX_040000                             wwv_flow_iconbarattr_fk|apex_040000
                                                                1 < WWV_FLOW_ICON_BAR|APEX_040000                                        wwv_flow_icon_bar_fk|apex_040000
                                                                1 < WWV_FLOW_INSTALL_BUILD_OPT|APEX_040000                               wwv_flow_install_build_opt_fk|apex_040000
                                                                2   > WWV_FLOW_INSTALL|APEX_040000                                       wwv_flow_install_build_opt_fk3|apex_040000
                                                                3     > WWV_FLOWS|APEX_040000*                                           wwv_flow_install_fk|apex_040000
                                                                3     < WWV_FLOW_INSTALL_CHECKS|APEX_040000                              wwv_flow_install_checks_fk3|apex_040000
                                                                4       > WWV_FLOWS|APEX_040000*                                         wwv_flow_install_checks_fk|apex_040000
                                                                3     < WWV_FLOW_INSTALL_SCRIPTS|APEX_040000                             wwv_flow_install_scripts_fk3|apex_040000
                                                                4       > WWV_FLOWS|APEX_040000*                                         wwv_flow_install_scripts_fk|apex_040000
                                                                2   > WWV_FLOW_PATCHES|APEX_040000                                       wwv_flow_install_build_opt_fk4|apex_040000
                                                                3     > WWV_FLOWS|APEX_040000*                                           wwv_flow_patches_fk|apex_040000
                                                                1 < WWV_FLOW_ITEMS|APEX_040000                                           wwv_flow_items_fk|apex_040000
                                                                1 < WWV_FLOW_LANGUAGE_MAP|APEX_040000                                    wwv_flow_lang_flow_id_fk|apex_040000
                                                                1 < WWV_FLOW_LISTS_OF_VALUES$|APEX_040000                                wwv_flow_lov_fk|apex_040000
                                                                2   < WWV_FLOW_LIST_OF_VALUES_DATA|APEX_040000                           wwv_flow_lov_data_fk|apex_040000
                                                                1 < WWV_FLOW_LISTS|APEX_040000                                           wwv_flow_lists_flow_fk|apex_040000
                                                                2   < WWV_FLOW_LIST_ITEMS|APEX_040000                                    wwv_flow_list_items_fk|apex_040000
                                                                3     = WWV_FLOW_LIST_ITEMS|APEX_040000*                                 parent_list_item_fk|apex_040000
                                                                1 < WWV_FLOW_LIST_TEMPLATES|APEX_040000                                  wwv_flow_list_template_fk|apex_040000
                                                                1 < WWV_FLOW_LOCK_PAGE|APEX_040000                                       sys_c004810|apex_040000
                                                                1 < WWV_FLOW_MENUS|APEX_040000                                           wwv_flow_menus_flow_fk|apex_040000
                                                                2   < WWV_FLOW_MENU_OPTIONS|APEX_040000                                  wwv_flow_opt_menus_fk|apex_040000
                                                                1 < WWV_FLOW_MENU_TEMPLATES|APEX_040000                                  wwv_flow_menus_t_flow_fk|apex_040000
                                                                1 < WWV_FLOW_MESSAGES$|APEX_040000                                       wwv_flow_messages_fk|apex_040000
                                                                1 < WWV_FLOW_PAGES_RESERVED|APEX_040000                                  wwv_flow_pages_reserved_fk|apex_040000
                                                                1 < WWV_FLOW_PAGE_CACHE|APEX_040000                                      wwv_flow_page_cache_fk|apex_040000
                                                                2   < WWV_FLOW_PAGE_CODE_CACHE|APEX_040000                               wwv_flow_page_code_cache_fk|apex_040000
                                                                1 < WWV_FLOW_PAGE_GROUPS|APEX_040000                                     sys_c004993|apex_040000
                                                                1 < WWV_FLOW_PAGE_PLUG_TEMPLATES|APEX_040000                             wwv_flow_plug_temp_fk|apex_040000
                                                                1 < WWV_FLOW_PAGE_SUBMISSIONS|APEX_040000                                wwv_flow_page_sub_fk|apex_040000
                                                                2   > WWV_FLOW_SESSIONS$|APEX_040000                                     wwv_flow_page_sub_sess_fk|apex_040000
                                                                3     < WWV_FLOW_COLLECTIONS$|APEX_040000                                wwv_flow_collection_fk|apex_040000
                                                                4       < WWV_FLOW_COLLECTION_MEMBERS$|APEX_040000                       wwv_flow_collection_membes_fk|apex_040000
                                                                3     < WWV_FLOW_DATA|APEX_040000                                        wwv_flow_data_fk|apex_040000
                                                                3     < WWV_FLOW_REQUEST_VERIFICATIONS|APEX_040000                       wwv_flow_request_verif_fk|apex_040000
                                                                3     < WWV_FLOW_SC_TRANS|APEX_040000                                    wwv_flow_sc_trans_fk2|apex_040000
                                                                3     < WWV_FLOW_TREE_STATE|APEX_040000                                  wwv_flow_tree_state$fk|apex_040000
                                                                1 < WWV_FLOW_PLUGINS|APEX_040000                                         wwv_flow_plugin_flow_fk|apex_040000
                                                                2   < WWV_FLOW_PLUGIN_ATTRIBUTES|APEX_040000                             wwv_flow_plugin_attr_parent_fk|apex_040000
                                                                3     > WWV_FLOWS|APEX_040000*                                           wwv_flow_plugin_attr_flow_fk|apex_040000
                                                                3     = WWV_FLOW_PLUGIN_ATTRIBUTES|APEX_040000*                          wwv_flow_plugin_attr_depend_fk|apex_040000
                                                                3     < WWV_FLOW_PLUGIN_ATTR_VALUES|APEX_040000                          wwv_flow_plugin_attrv_attr_fk|apex_040000
                                                                4       > WWV_FLOWS|APEX_040000*                                         wwv_flow_plugin_attrv_flow_fk|apex_040000
                                                                2   < WWV_FLOW_PLUGIN_EVENTS|APEX_040000                                 wwv_flow_plugin_evnt_parent_fk|apex_040000
                                                                3     > WWV_FLOWS|APEX_040000*                                           wwv_flow_plugin_evnt_flow_fk|apex_040000
                                                                2   < WWV_FLOW_PLUGIN_FILES|APEX_040000                                  wwv_flow_plugin_file_parent_fk|apex_040000
                                                                3     > WWV_FLOWS|APEX_040000*                                           wwv_flow_plugin_file_flow_fk|apex_040000
                                                                1 < WWV_FLOW_POPUP_LOV_TEMPLATE|APEX_040000                              wwv_flow_fk_poplov_temp|apex_040000
                                                                1 < WWV_FLOW_PROCESSING|APEX_040000                                      wwv_flow_processing_fk|apex_040000
                                                                1 < WWV_FLOW_REPORT_LAYOUTS|APEX_040000                                  wwv_flow_report_layoutse_fk|apex_040000
                                                                1 < WWV_FLOW_REQUIRED_ROLES|APEX_040000                                  wwv_flow_req_roles_fk|apex_040000
                                                                1 < WWV_FLOW_ROW_TEMPLATES|APEX_040000                                   wwv_flow_row_template_fk|apex_040000
                                                                1 < WWV_FLOW_SECURITY_SCHEMES|APEX_040000                                wwv_flow_sec_schemes_fk|apex_040000
                                                                1 < WWV_FLOW_SHARED_QRY_SQL_STMTS|APEX_040000                            wwv_flow_sqry_sql_flow_fk|apex_040000
                                                                2   > WWV_FLOW_SHARED_QUERIES|APEX_040000                                wwv_flow_sqry_sql_sqry_fk|apex_040000
                                                                3     > WWV_FLOWS|APEX_040000*                                           wwv_flow_shdqry_flow_fk|apex_040000
                                                                1 < WWV_FLOW_SHORTCUTS|APEX_040000                                       wwv_flow_shortcuts_to_flow_fk|apex_040000
                                                                1 < WWV_FLOW_TABS|APEX_040000                                            wwv_flow_tabs_fk|apex_040000
                                                                1 < WWV_FLOW_TEMPLATES|APEX_040000                                       wwv_flow_templates_fk|apex_040000
                                                                1 < WWV_FLOW_TEMPLATE_PREFERENCES|APEX_040000                            wwv_flow_templ_pref_fk|apex_040000
                                                                1 < WWV_FLOW_THEMES|APEX_040000                                          wwv_flow_themes_2f_fk|apex_040000
                                                                1 < WWV_FLOW_TOPLEVEL_TABS|APEX_040000                                   wwv_flow_toplev_tab_fk|apex_040000
                                                                1 < WWV_FLOW_TRANSLATABLE_TEXT$|APEX_040000                              wwv_flow_trans_text_fk|apex_040000
                                                                1 < WWV_FLOW_TREES|APEX_040000                                           wwv_flow_tree_fk|apex_040000
                                                                1 < WWV_FLOW_VALIDATIONS|APEX_040000                                     wwv_flow_val_fk|apex_040000
                                                                1 < WWV_MIG_GENERATED_APPLICATIONS|APEX_040000                           wwv_mig_gen_app_flow_id_fk|apex_040000
                                                                2   > WWV_MIG_PROJECTS|APEX_040000                                       wwv_mig_gen_app_proj_id_fk|apex_040000
                                                                3     < WWV_MIG_ACCESS|APEX_040000                                       wwv_mig_acc_fk|apex_040000
                                                                3     < WWV_MIG_FORMS|APEX_040000                                        wwv_mig_forms_project_id_fk|apex_040000
                                                                4       < WWV_MIG_FRM_MODULES|APEX_040000                                wwv_mig_frm_modules_file_id_fk|apex_040000
                                                                5         < WWV_MIG_FRM_FORMMODULES|APEX_040000                          wwv_mig_frm_frmmdl_mdl_id_fk|apex_040000
                                                                6           < WWV_MIG_FRM_ALERTS|APEX_040000                             wwv_mig_frm_alrt_frmmdl_id_fk|apex_040000
                                                                6           < WWV_MIG_FRM_ATTACHEDLIBRARY|APEX_040000                    wwv_mig_frm_atlib_frmmdl_id_fk|apex_040000
                                                                6           < WWV_MIG_FRM_BLOCKS|APEX_040000                             wwv_mig_frm_blk_frmmdl_id_fk|apex_040000
                                                                7             < WWV_MIG_FRM_BLK_DSA|APEX_040000                          wwv_mig_frm_blk_dsa_blk_id_fk|apex_040000
                                                                7             < WWV_MIG_FRM_BLK_DSC|APEX_040000                          wwv_mig_frm_blk_dsc_blk_id_fk|apex_040000
                                                                7             < WWV_MIG_FRM_BLK_ITEMS|APEX_040000                        wwv_mig_frm_bi_blk_id_fk|apex_040000
                                                                8               < WWV_MIG_FRM_BLK_ITEM_LIE|APEX_040000                   wwv_mig_frm_bi_lie_item_id_fk|apex_040000
                                                                8               < WWV_MIG_FRM_BLK_ITEM_RADIO|APEX_040000                 wwv_mig_frm_bir_item_id_fk|apex_040000
                                                                8               < WWV_MIG_FRM_BLK_ITEM_TRIGGERS|APEX_040000              wwv_mig_frm_bi_trg_item_id_fk|apex_040000
                                                                8               < WWV_MIG_FRM_REV_BLK_ITEMS|APEX_040000                  wwv_mig_frm_rev_bi_item_id_fk|apex_040000
                                                                7             < WWV_MIG_FRM_BLK_RELATIONS|APEX_040000                    wwv_mig_frm_blk_rel_blk_id_fk|apex_040000
                                                                7             < WWV_MIG_FRM_BLK_TRIGGERS|APEX_040000                     wwv_mig_frm_blk_trg_blk_id_fk|apex_040000
                                                                7             < WWV_MIG_FRM_REV_BLOCKS|APEX_040000                       wwv_mig_frm_rev_blocks_id_fk|apex_040000
                                                                6           < WWV_MIG_FRM_CANVAS|APEX_040000                             wwv_mig_frm_canvs_frmmdl_id_fk|apex_040000
                                                                7             < WWV_MIG_FRM_CNVS_GRAPHICS|APEX_040000                    wwv_mig_frm_cg_cnvs_id_fk|apex_040000
                                                                8               < WWV_MIG_FRM_CNVG_COMPOUNDTEXT|APEX_040000              wwv_mig_frm_cpdtxt_grphs_id_fk|apex_040000
                                                                9                 < WWV_MIG_FRM_CPDTXT_TEXTSEGMENT|APEX_040000           wwv_mig_frm_txtsgmt_cpd_id_fk|apex_040000
                                                                7             < WWV_MIG_FRM_CNVS_TABPAGE|APEX_040000                     wwv_mig_frm_ctp_cnvs_id_fk|apex_040000
                                                                6           < WWV_MIG_FRM_COORDINATES|APEX_040000                        wwv_mig_frm_crdnt_frmmdl_id_fk|apex_040000
                                                                6           < WWV_MIG_FRM_EDITOR|APEX_040000                             wwv_mig_frm_edtr_frmmdl_id_fk|apex_040000
                                                                6           < WWV_MIG_FRM_FMB_MENU|APEX_040000                           wwv_mig_frm_menu_frmmdl_id_fk|apex_040000
                                                                7             < WWV_MIG_FRM_FMB_MENU_MENUITEM|APEX_040000                wwv_mig_fmb_menuitem_menuid_fk|apex_040000
                                                                8               < WWV_MIG_FRM_FMB_MENUITEM_ROLE|APEX_040000              wwv_mig_fmb_mnuitemrl_mitm_fk|apex_040000
                                                                6           < WWV_MIG_FRM_LOV|APEX_040000                                wwv_mig_frm_lov_frmmdl_id_fk|apex_040000
                                                                7             < WWV_MIG_FRM_LOVCOLUMNMAPPING|APEX_040000                 wwv_mig_frm_lvcm_frmmdl_id_fk|apex_040000
                                                                8               < WWV_MIG_FRM_REV_LOVCOLMAPS|APEX_040000                 wwv_mig_frm_rev_lcm_id_fk|apex_040000
                                                                7             < WWV_MIG_FRM_REV_LOV|APEX_040000                          wwv_mig_frm_rev_lov_id_fk|apex_040000
                                                                6           < WWV_MIG_FRM_MODULEPARAMETER|APEX_040000                    wwv_mig_frm_mdlpr_frmmdl_id_fk|apex_040000
                                                                6           < WWV_MIG_FRM_OBJECTGROUP|APEX_040000                        wwv_mig_frm_objgp_frmmdl_id_fk|apex_040000
                                                                7             < WWV_MIG_FRM_OBJECTGROUPCHILD|APEX_040000                 wwv_mig_frm_objgpc_objgp_id_fk|apex_040000
                                                                6           < WWV_MIG_FRM_PROGRAMUNIT|APEX_040000                        wwv_mig_frm_pgut_frmmdl_id_fk|apex_040000
                                                                6           < WWV_MIG_FRM_PROPERTYCLASS|APEX_040000                      wwv_mig_frm_ppcl_frmmdl_id_fk|apex_040000
                                                                6           < WWV_MIG_FRM_RECORDGROUPS|APEX_040000                       wwv_mig_frm_recgp_frmmdl_id_fk|apex_040000
                                                                7             < WWV_MIG_FRM_RECORDGROUPCOLUMN|APEX_040000                wwv_mig_frm_rgc_recgp_id_fk|apex_040000
                                                                6           < WWV_MIG_FRM_REPORT|APEX_040000                             wwv_mig_frm_rpt_frmmdl_id_fk|apex_040000
                                                                6           < WWV_MIG_FRM_REV_FORMMODULES|APEX_040000                    wwv_mig_frm_rev_frmmdl_id_fk|apex_040000
                                                                6           < WWV_MIG_FRM_TRIGGERS|APEX_040000                           wwv_mig_frm_trg_frmmdl_id_fk|apex_040000
                                                                6           < WWV_MIG_FRM_VISUALATTRIBUTES|APEX_040000                   wwv_mig_frm_visat_frmmdl_id_fk|apex_040000
                                                                6           < WWV_MIG_FRM_WINDOWS|APEX_040000                            wwv_mig_frm_wndow_frmmdl_id_fk|apex_040000
                                                                3     < WWV_MIG_FRM_MENUS|APEX_040000                                    wwv_mig_menus_project_id_fk|apex_040000
                                                                4       < WWV_MIG_FRM_MENUS_MODULES|APEX_040000                          wwv_mig_mnu_modules_file_id_fk|apex_040000
                                                                5         < WWV_MIG_FRM_MENUS_MENUMODULES|APEX_040000                    wwv_mig_mnu_mnumdl_mdl_id_fk|apex_040000
                                                                6           < WWV_MIG_FRM_MENUSMODULEROLES|APEX_040000                   wwv_mig_mmodrole_id_fk|apex_040000
                                                                6           < WWV_MIG_FRM_MENUS_PROGRAMUNIT|APEX_040000                  wwv_mig_mnu_progunit_id_fk|apex_040000
                                                                6           < WWV_MIG_FRM_MENU|APEX_040000                               wwv_mig_mnu_id_fk|apex_040000
                                                                7             < WWV_MIG_FRM_MENU_MENUITEM|APEX_040000                    wwv_mig_mnuitem_id_fk|apex_040000
                                                                8               < WWV_MIG_FRM_MENUITEM_ROLE|APEX_040000                  wwv_mig_mnuitemrole_id_fk|apex_040000
                                                                3     < WWV_MIG_FRM_REV_APEX_APP|APEX_040000                             wwv_mig_frm_rev_apex_app_fk|apex_040000
                                                                3     < WWV_MIG_OLB|APEX_040000                                          wwv_mig_olb_project_id_fk|apex_040000
                                                                4       < WWV_MIG_OLB_MODULES|APEX_040000                                wwv_mig_olb_modules_file_id_fk|apex_040000
                                                                5         < WWV_MIG_OLB_OBJECTLIBRARY|APEX_040000                        wwv_mig_olb_objlib_mdl_id_fk|apex_040000
                                                                6           < WWV_MIG_OLB_BLOCK|APEX_040000                              wwv_mig_olb_block_objlib_id_fk|apex_040000
                                                                7             < WWV_MIG_OLB_BLK_DATASOURCECOL|APEX_040000                wwv_mig_olb_blk_dsc_blk_id_fk|apex_040000
                                                                7             < WWV_MIG_OLB_BLK_ITEM|APEX_040000                         wwv_mig_olb_blk_item_blk_id_fk|apex_040000
                                                                8               < WWV_MIG_OLB_BLK_ITEM_LIE|APEX_040000                   wwv_mig_olb_bil_item_id_fk|apex_040000
                                                                8               < WWV_MIG_OLB_BLK_ITEM_TRIGGER|APEX_040000               wwv_mig_olb_bit_item_id_fk|apex_040000
                                                                7             < WWV_MIG_OLB_BLK_TRIGGER|APEX_040000                      wwv_mig_olb_blk_trgr_blk_id_fk|apex_040000
                                                                6           < WWV_MIG_OLB_CANVAS|APEX_040000                             wwv_mig_olb_canvs_objlib_id_fk|apex_040000
                                                                7             < WWV_MIG_OLB_CNVS_GRAPHICS|APEX_040000                    wwv_mig_olb_cg_cnvs_id_fk|apex_040000
                                                                8               < WWV_MIG_OLB_CG_COMPOUNDTEXT|APEX_040000                wwv_mig_olb_cg_ct_grphs_id_fk|apex_040000
                                                                9                 < WWV_MIG_OLB_CG_CT_TEXTSEGMENT|APEX_040000            wwv_mig_olb_cg_ct_ts_ct_id_fk|apex_040000
                                                                6           < WWV_MIG_OLB_OBJECTLIBRARYTAB|APEX_040000                   wwv_mig_olb_olt_objlib_id_fk|apex_040000
                                                                7             < WWV_MIG_OLB_OLT_ALERT|APEX_040000                        wwv_mig_olb_olt_alrt_olt_id_fk|apex_040000
                                                                7             < WWV_MIG_OLB_OLT_BLOCK|APEX_040000                        wwv_mig_olb_t_block_olt_id_fk|apex_040000
                                                                8               < WWV_MIG_OLB_OLT_BLK_ITEM|APEX_040000                   wwv_mig_olb_olt_bi_blk_id_fk|apex_040000
                                                                9                 < WWV_MIG_OLB_OLT_BLK_ITEM_TRIGR|APEX_040000           wwv_mig_olb_olt_bit_item_id_fk|apex_040000
                                                                7             < WWV_MIG_OLB_OLT_CANVAS|APEX_040000                       wwv_mig_olb_t_canvas_olt_id_fk|apex_040000
                                                                8               < WWV_MIG_OLB_OLT_CNVS_GRAPHICS|APEX_040000              wwv_mig_olb_olt_cg_cnvs_id_fk|apex_040000
                                                                7             < WWV_MIG_OLB_OLT_GRAPHICS|APEX_040000                     wwv_mig_olb_t_grphcs_olt_id_fk|apex_040000
                                                                7             < WWV_MIG_OLB_OLT_ITEM|APEX_040000                         wwv_mig_olb_olt_item_olt_id_fk|apex_040000
                                                                7             < WWV_MIG_OLB_OLT_MENU|APEX_040000                         wwv_mig_olb_olt_menu_olt_id_fk|apex_040000
                                                                8               < WWV_MIG_OLB_OLT_MENU_MENUITEM|APEX_040000              wwv_mig_olb_olt_mmi_menu_id_fk|apex_040000
                                                                7             < WWV_MIG_OLB_OLT_OBJECTGROUP|APEX_040000                  wwv_mig_olb_t_objgrp_olt_id_fk|apex_040000
                                                                8               < WWV_MIG_OLB_OLT_OB_OBJGRPCHILD|APEX_040000             wwv_mig_olb_olt_ob_ogc_obid_fk|apex_040000
                                                                7             < WWV_MIG_OLB_OLT_REPORT|APEX_040000                       wwv_mig_olb_t_report_olt_id_fk|apex_040000
                                                                7             < WWV_MIG_OLB_OLT_TABPAGE|APEX_040000                      wwv_mig_olb_t_tabpage_oltid_fk|apex_040000
                                                                8               < WWV_MIG_OLB_OLT_TABPG_GRAPHICS|APEX_040000             wwv_mig_olb_olt_tpg_tp_id_fk|apex_040000
                                                                9                 < WWV_MIG_OLB_T_TP_G_GRAPHICS|APEX_040000              wwv_mig_olb_t_tp_gg_g_id_fk|apex_040000
                                                               10                   < WWV_MIG_OLB_T_TP_GG_CPDTXT|APEX_040000             wwv_mig_olb_t_tp_gg_ct_g_id_fk|apex_040000
                                                               11                     < WWV_MIG_OLB_T_TP_GG_CT_TXTSGT|APEX_040000        wwv_mig_olb_ttpggctts_ctid_fk|apex_040000
                                                               10                   < WWV_MIG_OLB_T_TP_GG_GRAPHICS|APEX_040000           wwv_mig_olb_t_tp_ggg_g_id_fk|apex_040000
                                                               11                     < WWV_MIG_OLB_T_TP_GGG_CPDTXT|APEX_040000          wwv_mig_olb_ttp_ggg_ct_gid_fk|apex_040000
                                                               12                       < WWV_MIG_OLB_T_TP_GGG_CT_TXTSGT|APEX_040000     wwv_mig_olb_ttpgggctts_ctid_fk|apex_040000
                                                               11                     < WWV_MIG_OLB_T_TP_GGG_GRAPHICS|APEX_040000        wwv_mig_olb_t_tp_gggg_g_id_fk|apex_040000
                                                               12                       < WWV_MIG_OLB_T_TP_GGGG_CPDTXT|APEX_040000       wwv_mig_olb_ttpggggct_g_id_fk|apex_040000
                                                               13                         < WWV_MIG_OLB_T_TP_GGGG_CT_TXSGT|APEX_040000   wwv_mig_olb_ttpggggcts_ctid_fk|apex_040000
                                                               12                       < WWV_MIG_OLB_T_TP_GGGG_GRAPHICS|APEX_040000     wwv_mig_olb_ttpggggg_g_id_fk|apex_040000
                                                               13                         < WWV_MIG_OLB_T_TP_GGGGG_CPDTXT|APEX_040000    wwv_mig_olb_ttpgggggct_g_id_fk|apex_040000
                                                               14                           < WWV_MIG_OLB_T_TP_GGGGG_CT_TXST|APEX_040000 wwv_mig_olb_ttp5gcts_ct_id_fk|apex_040000
                                                                7             < WWV_MIG_OLB_OLT_VISUALATTRBUTE|APEX_040000               wwv_mig_olb_olt_va_olt_id_fk|apex_040000
                                                                7             < WWV_MIG_OLB_OLT_WINDOW|APEX_040000                       wwv_mig_olb_olt_wndow_oltid_fk|apex_040000
                                                                6           < WWV_MIG_OLB_PROGRAMUNIT|APEX_040000                        wwv_mig_olb_pu_objlib_id_fk|apex_040000
                                                                6           < WWV_MIG_OLB_PROPERTYCLASS|APEX_040000                      wwv_mig_olb_pc_objlib_id_fk|apex_040000
                                                                6           < WWV_MIG_OLB_VISUALATTRIBUTE|APEX_040000                    wwv_mig_olb_va_objlib_id_fk|apex_040000
                                                                6           < WWV_MIG_OLB_WINDOW|APEX_040000                             wwv_mig_olb_wndow_objlib_id_fk|apex_040000
                                                                3     < WWV_MIG_PLSQL_LIBS|APEX_040000                                   wwv_mig_plls_project_id_fk|apex_040000
                                                                3     < WWV_MIG_PROJECT_COMPONENTS|APEX_040000                           wwv_mig_proj_comp_fk|apex_040000
                                                                3     < WWV_MIG_PROJECT_TRIGGERS|APEX_040000                             wwv_mig_proj_trig_fk|apex_040000
                                                                3     < WWV_MIG_RPTS|APEX_040000                                         wwv_mig_rpts_project_id_fk|apex_040000
                                                                4       < WWV_MIG_REPORT|APEX_040000                                     wwv_mig_rep_file_id_fk|apex_040000
                                                                5         < WWV_MIG_RPT_DATA|APEX_040000                                 wwv_mig_repdata_id_fk|apex_040000
                                                                6           < WWV_MIG_RPT_DATASRC|APEX_040000                            wwv_mig_repsrc_id_fk|apex_040000
                                                                7             < WWV_MIG_RPT_DATASRC_GRP|APEX_040000                      wwv_mig_grp_id_fk|apex_040000
                                                                8               < WWV_MIG_RPT_GRP_DATAITEM|APEX_040000                   wwv_mig_grp_dataitem_id_fk|apex_040000
                                                                9                 < WWV_MIG_RPT_GRP_DATAITEM_DESC|APEX_040000            wwv_mig_grp_itemdesc_id_fk|apex_040000
                                                                9                 < WWV_MIG_RPT_GRP_DATAITEM_PRIV|APEX_040000            wwv_mig_grp_itempriv_id_fk|apex_040000
                                                                8               < WWV_MIG_RPT_GRP_FIELD|APEX_040000                      wwv_mig_grp_fld_id_fk|apex_040000
                                                                8               < WWV_MIG_RPT_GRP_FILTER|APEX_040000                     wwv_mig_grp_fltr_id_fk|apex_040000
                                                                8               < WWV_MIG_RPT_GRP_FORMULA|APEX_040000                    wwv_mig_grp_form_id_fk|apex_040000
                                                                8               < WWV_MIG_RPT_GRP_ROWDELIM|APEX_040000                   wwv_mig_grp_row_id_fk|apex_040000
                                                                8               < WWV_MIG_RPT_GRP_SUMMARY|APEX_040000                    wwv_mig_grp_sum_id_fk|apex_040000
                                                                7             < WWV_MIG_RPT_DATASRC_SELECT|APEX_040000                   wwv_mig_select_id_fk|apex_040000
                                                                6           < WWV_MIG_RPT_DATA_SUMMARY|APEX_040000                       wwv_mig_repsum_id_fk|apex_040000
                                                                5         < WWV_MIG_RPT_REPORTPRIVATE|APEX_040000                        wwv_mig_rptpriv_id_fk|apex_040000
WWV_FLOW_ADVISOR_CATEGORIES|APEX_040000          2       3      0 WWV_FLOW_ADVISOR_CATEGORIES|APEX_040000                                ROOT
                                                                1 < WWV_FLOW_ADVISOR_CHECKS|APEX_040000                                  wwv_flow_adv_chk_cat_fk|apex_040000
                                                                2   < WWV_FLOW_ADVISOR_CHECK_MSGS|APEX_040000                            wwv_flow_adv_chk_msg_check_fk|apex_040000
WWV_FLOW_BUGS|APEX_040000                       13      11      0 WWV_FLOW_BUGS|APEX_040000                                              ROOT
                                                                1 < WWV_FLOW_TEAMDEV_TAG_CLOUD|APEX_040000                               wwv_flow_teamdev_tc_b|apex_040000
                                                                2   > WWV_FLOW_FEATURES|APEX_040000                                      wwv_flow_teamdev_tc_f|apex_040000
                                                                3     = WWV_FLOW_FEATURES|APEX_040000*                                   wwv_flow_features_par_feat_fk|apex_040000
                                                                3     < WWV_FLOW_FEATURE_HISTORY|APEX_040000                             wwv_flow_feature_hist_fk|apex_040000
                                                                3     < WWV_FLOW_FEATURE_PROGRESS|APEX_040000                            wwv_flow_feature_prog_fk|apex_040000
                                                                3     < WWV_FLOW_TEAM_FILES|APEX_040000                                  wwv_flow_team_files_fk1|apex_040000
                                                                4       > WWV_FLOW_EVENTS|APEX_040000                                    wwv_flow_team_files_fk3|apex_040000
                                                                4       > WWV_FLOW_FEEDBACK|APEX_040000                                  wwv_flow_team_files_fk4|apex_040000
                                                                5         < WWV_FLOW_FEEDBACK_FOLLOWUP|APEX_040000                       wwv_flow_feedback_fup_fk|apex_040000
                                                                5         < WWV_FLOW_TEAM_FILES|APEX_040000*                             wwv_flow_team_files_fk5|apex_040000
                                                                4       > WWV_FLOW_TASKS|APEX_040000                                     wwv_flow_team_files_fk2|apex_040000
                                                                5         < WWV_FLOW_TASK_PROGRESS|APEX_040000                           wwv_flow_task_prog_fk|apex_040000
                                                                5         < WWV_FLOW_TEAMDEV_TAG_CLOUD|APEX_040000*                      wwv_flow_teamdev_tc_t|apex_040000
WWV_FLOW_DATA_LOAD_BAD_LOG|APEX_040000           1       2      0 WWV_FLOW_DATA_LOAD_BAD_LOG|APEX_040000                                 ROOT
                                                                1 > WWV_FLOW_DATA_LOAD_UNLOAD|APEX_040000                                wwv_flow_data_load_bad_log_fk1|apex_040000
WWV_FLOW_DICTIONARY_VIEWS|APEX_040000            1       1      0 WWV_FLOW_DICTIONARY_VIEWS|APEX_040000                                  ROOT
                                                                1 = WWV_FLOW_DICTIONARY_VIEWS|APEX_040000*                               wwv_flow_dict_view_parent_fk|apex_040000
WWV_FLOW_FILE_OBJECTS$|FLOWS_FILES               5       6      0 WWV_FLOW_FILE_OBJECTS$|FLOWS_FILES                                     ROOT
                                                                1 < WWV_FLOW_IMPORT_EXPORT|APEX_040000                                   wwv_flow_import_export_fk|apex_040000
                                                                1 < WWV_FLOW_SW_BINDS|APEX_040000                                        wwv_flow_sw_bind_fk|apex_040000
                                                                1 < WWV_FLOW_SW_RESULTS|APEX_040000                                      wwv_flow_sw_result_fk|apex_040000
                                                                2   < WWV_FLOW_SW_DETAIL_RESULTS|APEX_040000                             wwv_flow_sw_d_result_fk|apex_040000
                                                                1 < WWV_FLOW_SW_STMTS|APEX_040000                                        wwv_flow_sw_stmts_fk|apex_040000
WWV_FLOW_FLASH_MAP_FILES|APEX_040000             2       3      0 WWV_FLOW_FLASH_MAP_FILES|APEX_040000                                   ROOT
                                                                1 > WWV_FLOW_FLASH_MAP_FOLDERS|APEX_040000                               wwv_flow_flash_map_files_fk|apex_040000
                                                                1 < WWV_FLOW_FLASH_MAP_REGIONS|APEX_040000                               wwv_flow_flash_map_reg_fk|apex_040000
WWV_FLOW_FND_GROUP_USERS|APEX_040000             1       2      0 WWV_FLOW_FND_GROUP_USERS|APEX_040000                                   ROOT
                                                                1 > WWV_FLOW_FND_USER_GROUPS|APEX_040000                                 wwv_flow_fnd_gu_int_g_fk|apex_040000
WWV_FLOW_HNT_ARGUMENT_INFO|APEX_040000           1       2      0 WWV_FLOW_HNT_ARGUMENT_INFO|APEX_040000                                 ROOT
                                                                1 > WWV_FLOW_HNT_PROCEDURE_INFO|APEX_040000                              wwv_flow_hnt_arg_info_proc_fk|apex_040000
WWV_FLOW_HNT_COLUMN_DICT|APEX_040000             1       2      0 WWV_FLOW_HNT_COLUMN_DICT|APEX_040000                                   ROOT
                                                                1 < WWV_FLOW_HNT_COL_DICT_SYN|APEX_040000                                wwv_flow_hnt_col_dict_syn_fk|apex_040000
WWV_FLOW_HNT_COLUMN_INFO|APEX_040000             4       4      0 WWV_FLOW_HNT_COLUMN_INFO|APEX_040000                                   ROOT
                                                                1 > WWV_FLOW_HNT_GROUPS|APEX_040000                                      wwv_flow_hnt_col_info_grp_fk|apex_040000
                                                                2   > WWV_FLOW_HNT_TABLE_INFO|APEX_040000                                wwv_flow_hnt_groups_tab_fk|apex_040000
                                                                3     < WWV_FLOW_HNT_COLUMN_INFO|APEX_040000*                            wwv_flow_hnt_col_info_tab_fk|apex_040000
                                                                1 < WWV_FLOW_HNT_LOV_DATA|APEX_040000                                    wwv_flow_hnt_lov_data_col_fk|apex_040000
WWV_FLOW_MAIL_ATTACHMENTS|APEX_040000            1       2      0 WWV_FLOW_MAIL_ATTACHMENTS|APEX_040000                                  ROOT
                                                                1 > WWV_FLOW_MAIL_QUEUE|APEX_040000                                      wwv_flow_mail_attachments_fk1|apex_040000
WWV_FLOW_MODELS|APEX_040000                      4       4      0 WWV_FLOW_MODELS|APEX_040000                                            ROOT
                                                                1 < WWV_FLOW_MODEL_PAGES|APEX_040000                                     wwv_flow_model_pages_fk|apex_040000
                                                                2   = WWV_FLOW_MODEL_PAGES|APEX_040000*                                  wwv_flow_model_pages_fk2|apex_040000
                                                                2   < WWV_FLOW_MODEL_PAGE_REGIONS|APEX_040000                            wwv_flow_mpr_fk|apex_040000
                                                                3     < WWV_FLOW_MODEL_PAGE_COLS|APEX_040000                             wwv_flow_model_page_cols_fk|apex_040000
WWV_FLOW_QB_SAVED_COND|APEX_040000               3       4      0 WWV_FLOW_QB_SAVED_COND|APEX_040000                                     ROOT
                                                                1 > WWV_FLOW_QB_SAVED_QUERY|APEX_040000                                  sys_c005031|apex_040000
                                                                2   < WWV_FLOW_QB_SAVED_JOIN|APEX_040000                                 sys_c005038|apex_040000
                                                                2   < WWV_FLOW_QB_SAVED_TABS|APEX_040000                                 sys_c005045|apex_040000
WWV_FLOW_RESTRICTED_SCHEMAS|APEX_040000          1       2      0 WWV_FLOW_RESTRICTED_SCHEMAS|APEX_040000                                ROOT
                                                                1 < WWV_FLOW_RSCHEMA_EXCEPTIONS|APEX_040000                              wwv_flow_rschema_exceptions_fk|apex_040000
WWV_MIG_FRM_OLB_XMLTAGTABLEMAP|APEX_040000       1       1      0 WWV_MIG_FRM_OLB_XMLTAGTABLEMAP|APEX_040000                             ROOT
                                                                1 = WWV_MIG_FRM_OLB_XMLTAGTABLEMAP|APEX_040000*                          wwv_mig_olb_xmltagtablemap_fk|apex_040000
WWV_MIG_FRM_XMLTAGTABLEMAP|APEX_040000           1       1      0 WWV_MIG_FRM_XMLTAGTABLEMAP|APEX_040000                                 ROOT
                                                                1 = WWV_MIG_FRM_XMLTAGTABLEMAP|APEX_040000*                              wwv_mig_frm_xmltagtablemap_fk|apex_040000
WWV_MIG_MENU_XMLTAGTABLEMAP|APEX_040000          1       1      0 WWV_MIG_MENU_XMLTAGTABLEMAP|APEX_040000                                ROOT
                                                                1 = WWV_MIG_MENU_XMLTAGTABLEMAP|APEX_040000*                             wwv_mig_mnu_xmltagtablemap_fk|apex_040000
WWV_MIG_RPT_XMLTAGTABLEMAP|APEX_040000           1       1      0 WWV_MIG_RPT_XMLTAGTABLEMAP|APEX_040000                                 ROOT
                                                                1 = WWV_MIG_RPT_XMLTAGTABLEMAP|APEX_040000*                              wwv_mig_rpt_xmltagtablemap_fk|apex_040000
WWV_PURGE_DATAFILES|APEX_040000                  4       5      0 WWV_PURGE_DATAFILES|APEX_040000                                        ROOT
                                                                1 > WWV_PURGE_WORKSPACES|APEX_040000                                     wwv_purge_datafiles_fk1|apex_040000
                                                                2   < WWV_PURGE_EMAILS|APEX_040000                                       wwv_purge_emails_fk1|apex_040000
                                                                3     < WWV_PURGE_WORKSPACE_RESPONSES|APEX_040000                        wwv_purge_workspace_resp_fk1|apex_040000
                                                                2   < WWV_PURGE_SCHEMAS|APEX_040000                                      wwv_purge_schemas_fk1|apex_040000

359 rows selected.

Elapsed: 00:00:01.81
Network summary 1 - by network

Network                                     #Links  #Nodes    Max Lev
------------------------------------------ ------- ------- ----------
WWV_FLOW_HNT_COLUMN_DICT|APEX_040000             2       2          1
WWV_MIG_RPT_XMLTAGTABLEMAP|APEX_040000           2       1          1
WWV_MIG_MENU_XMLTAGTABLEMAP|APEX_040000          2       1          1
WWV_MIG_FRM_XMLTAGTABLEMAP|APEX_040000           2       1          1
WWV_MIG_FRM_OLB_XMLTAGTABLEMAP|APEX_040000       2       1          1
WWV_FLOW_RESTRICTED_SCHEMAS|APEX_040000          2       2          1
WWV_FLOW_MAIL_ATTACHMENTS|APEX_040000            2       2          1
WWV_FLOW_HNT_ARGUMENT_INFO|APEX_040000           2       2          1
WWV_FLOW_FND_GROUP_USERS|APEX_040000             2       2          1
WWV_FLOW_DICTIONARY_VIEWS|APEX_040000            2       1          1
WWV_FLOW_DATA_LOAD_BAD_LOG|APEX_040000           2       2          1
WWV_FLOW_FLASH_MAP_FILES|APEX_040000             3       3          1
WWV_FLOW_ADVISOR_CATEGORIES|APEX_040000          3       3          2
WWV_FLOW_QB_SAVED_COND|APEX_040000               4       4          2
WWV_PURGE_DATAFILES|APEX_040000                  5       5          3
APEX$_WS_FILES|APEX_040000                       5       5          2
WWV_FLOW_MODELS|APEX_040000                      5       4          3
WWV_FLOW_HNT_COLUMN_INFO|APEX_040000             5       4          3
WWV_FLOW_FILE_OBJECTS$|FLOWS_FILES               6       6          2
WWV_FLOW_BUGS|APEX_040000                       14      11          5
WWV_FLOWS|APEX_040000                          287     236         14

21 rows selected.

Elapsed: 00:00:00.01
Network summary 2 - grouped by numbers of nodes

 #Nodes  #Networks
------- ----------
      1          5
      2          6
      3          2
      4          3
      5          2
      6          1
     11          1
    236          1

8 rows selected.

Elapsed: 00:00:00.01

Schema Metadata Analysis Report

This is too long to embed so is included as an attachment.

A Design Pattern for Oracle eBusiness Audit Trail Reports with XML Publisher

Oracle eBusiness applications allow audit history records to be automatically maintained on database tables, as explained in the release 12 System Administrator’s guide, Reporting On AuditTrail Data.

Oracle E-Business Suite provides an auditing mechanism based on Oracle database triggers. AuditTrail stores change information in a “shadow table” of the audited table. This mechanism saves audit data in an uncompressed but “sparse” format, and you enable auditing for particular tables and groups of tables (“audit groups”).

Oracle provides an Audit Query Navigator page where it is possible to search for changes by primary key values. For reporting purposes, the manual says:

You should write audit reports as needed. Audit Trail provides the views of your shadow tables to make audit reporting easier; you can write your reports to use these views.

In fact the views are of little practical use, and it is quite hard to develop reports that are user-friendly, efficient and not over-complex, owing, amongst other things, to the “sparse” data format. However, once you have developed one you can use that as a design pattern and starting point for audit reporting on any of the eBusiness tables.

In this article I provide such a report for auditing external bank account changes on Oracle eBusiness 12.1. The report displays the current record, with lookup information, followed by a list of the changes within an input date range. Only records that have changes within that range are included, and for each change only the fields that were changed are listed. The lookup information includes a list of detail records, making the report overall pretty general in structure: It has a master entity with two independent detail entities, and therefore requires a minimum of two queries. This minimum number of queries is usually the best choice and is what I have implemented (it’s fine to have an extra query for global data, but I don’t have any here). The main query makes extensive use of analytic functions, case expressions and subquery factors to achieve the necessary data transformations as simply and efficiently as possible.

The report is implemented in XML (or BI) Publisher, which is the main batch reporting tool for Oracle eBusiness.

I start by showing sample output from the report, followed by the RTF template. The queries are then documented, starting with query structure diagrams with annotations explaining the logic. A link is included to a zip file with all the code and templates needed to install the report. Oracle provides extensive documentation on the setup of Auditing and developing in XML Publisher, so I will not cover this.

Report Layout
Example Output in Excel Format

  • There are three regions
    • Bank Account Current Record – the master record
    • Owners – first detail block, listing the owners of the bank account
    • Bank Account Changes – the second detail block, listing the audit history. Note that unchanged fields are omitted
  • Note that some audit fields are displayed directly, such as account number, while for others, such as branch number, the display value is on a referenced table

XML Publisher RTF Tempate

  • Note that each audit field has its own row in the table, but the if-block excludes it if both old and new values are null

Audit Query
Query Structure Diagram
Audit Query QSD
Subquery Tabulation


WITH audit_range AS (
SELECT DISTINCT ext_bank_account_id
  FROM iby_ext_bank_accounts_a aup
 WHERE 1=1
), audit_union AS (
SELECT ext_bank_account_id acc_id,
       CASE WHEN Substr (audit_true_nulls, 2, 1) = 'Y' OR audit_transaction_type = 'I' THEN '*NULL*' ELSE bank_account_name END acc_name,
       CASE WHEN Substr (audit_true_nulls, 3, 1) = 'Y' OR audit_transaction_type = 'I' THEN '*NULL*' ELSE bank_account_num END acc_num,
       CASE WHEN Substr (audit_true_nulls, 8, 1) = 'Y' OR audit_transaction_type = 'I' THEN '*NULL*' ELSE iban END iban,
       CASE WHEN Substr (audit_true_nulls, 7, 1) = 'Y' OR audit_transaction_type = 'I' THEN '*NULL*' ELSE currency_code END curr,
       CASE WHEN Substr (audit_true_nulls, 6, 1) = 'Y' OR audit_transaction_type = 'I' THEN '*NULL*' ELSE country_code END coun,
       CASE WHEN Substr (audit_true_nulls, 4, 1) = 'Y' OR audit_transaction_type = 'I' THEN 0 ELSE bank_id END bank_id,
       CASE WHEN Substr (audit_true_nulls, 5, 1) = 'Y' OR audit_transaction_type = 'I' THEN 0 ELSE branch_id END branch_id,
       audit_sequence_id seq_id,
       audit_user_name a_user,
       CASE WHEN audit_transaction_type = 'I' THEN 'INSERT' ELSE 'UPDATE' END a_type
  FROM iby_ext_bank_accounts_a aup
 WHERE aup.ext_bank_account_id IN (SELECT ext_bank_account_id FROM audit_range)
SELECT bac.ext_bank_account_id,
  FROM iby_ext_bank_accounts            bac
  JOIN fnd_user                         usr
    ON usr.user_id                      = bac.last_updated_by
 WHERE bac.ext_bank_account_id IN (SELECT ext_bank_account_id FROM audit_range)
), audit_pairs AS (
SELECT acc_id,
       First_Value (bank_id IGNORE NULLS) OVER 
        (PARTITION BY acc_id ORDER BY audit_timestamp, seq_id ROWS BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING) bank_id_n,
       First_Value (branch_id IGNORE NULLS) OVER 
        (PARTITION BY acc_id ORDER BY audit_timestamp, seq_id ROWS BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING) branch_id_n,
       First_Value (acc_name IGNORE NULLS) OVER 
        (PARTITION BY acc_id ORDER BY audit_timestamp, seq_id ROWS BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING) acc_name_n,
       First_Value (acc_num IGNORE NULLS) OVER 
        (PARTITION BY acc_id ORDER BY audit_timestamp, seq_id ROWS BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING) acc_num_n,
       First_Value (iban IGNORE NULLS) OVER 
        (PARTITION BY acc_id ORDER BY audit_timestamp, seq_id ROWS BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING) iban_n,
       First_Value (curr IGNORE NULLS) OVER 
        (PARTITION BY acc_id ORDER BY audit_timestamp, seq_id ROWS BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING) curr_n,
       First_Value (iban IGNORE NULLS) OVER 
        (PARTITION BY acc_id ORDER BY audit_timestamp, seq_id ROWS BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING) coun_n,
  FROM audit_union
SELECT aup.acc_id,
       par_bnk.party_name bank_name,
       par_brn.party_name bra_name,
       orp.bank_or_branch_number bra_num,
       bac.bank_account_name acc_name,
       bac.bank_account_num acc_num,
       bac.country_code coun,
       bac.currency_code curr,
       To_Char (bac.creation_date, 'DD-MON-YYYY HH24:MI:SS') creation_date,
       usr.user_name created_by,
       To_Char (aup.audit_timestamp, 'DD-MON-YYYY HH24:MI:SS') a_time,
attr: 1. NULL -> no change; 2. 0/'*NULL*' -> change from null; 3. 'other'-> change from not null
        old: 1 and 2 both return NULL; 3 returns old not null value
        new: only return value for 2 and 3, meaning some change
       CASE WHEN aup.bank_id != 0 THEN par_bnk_o.party_name END bank_name_o,
       CASE WHEN aup.bank_id IS NOT NULL THEN CASE WHEN aup.bank_id_n != 0 THEN par_bnk_o.party_name END END bank_name_n,
       CASE WHEN aup.branch_id != 0 THEN par_brn_o.party_name END bra_name_o,
       CASE WHEN aup.branch_id IS NOT NULL THEN CASE WHEN aup.branch_id_n != 0 THEN par_brn_n.party_name END END bra_name_n,
       CASE WHEN aup.branch_id != 0 THEN orp_o.bank_or_branch_number END bra_num_o,
       CASE WHEN aup.branch_id IS NOT NULL THEN CASE WHEN aup.branch_id_n != 0 THEN orp_n.bank_or_branch_number END END bra_num_n,
       CASE WHEN aup.acc_name != '*NULL*' THEN aup.acc_name END acc_name_o,
       CASE WHEN aup.acc_name IS NOT NULL THEN CASE WHEN aup.acc_name_n != '*NULL*' THEN aup.acc_name_n END END acc_name_n,
       CASE WHEN aup.acc_num != '*NULL*' THEN aup.acc_num END acc_num_o,
       CASE WHEN aup.acc_num IS NOT NULL THEN CASE WHEN aup.acc_num_n != '*NULL*' THEN aup.acc_num_n END END acc_num_n,
       CASE WHEN aup.iban != '*NULL*' THEN aup.iban END iban_o,
       CASE WHEN aup.iban IS NOT NULL THEN CASE WHEN aup.iban_n != '*NULL*' THEN aup.iban_n END END iban_n,
       CASE WHEN aup.curr != '*NULL*' THEN aup.curr END curr_o,
       CASE WHEN aup.curr IS NOT NULL THEN CASE WHEN aup.curr_n != '*NULL*' THEN aup.curr_n END END curr_n,
       CASE WHEN aup.coun != '*NULL*' THEN aup.coun END coun_o,
       CASE WHEN aup.coun IS NOT NULL THEN CASE WHEN aup.coun_n != '*NULL*' THEN aup.coun_n END END coun_n
  FROM audit_pairs                      aup
  JOIN iby_ext_bank_accounts            bac
    ON bac.ext_bank_account_id          = aup.acc_id
  LEFT JOIN hz_parties                  par_bnk
    ON par_bnk.party_id                 = bac.bank_id
  LEFT JOIN hz_parties                  par_bnk_o
    ON par_bnk_o.party_id               = aup.bank_id
  LEFT JOIN hz_parties                  par_bnk_n
    ON par_bnk_n.party_id               = aup.bank_id_n
  LEFT JOIN hz_parties                  par_brn
    ON par_brn.party_id                 = bac.branch_id
  LEFT JOIN hz_organization_profiles    orp
    ON orp.party_id                     = par_brn.party_id
   AND SYSDATE BETWEEN Trunc (orp.effective_start_date) AND Nvl (Trunc (orp.effective_end_date), SYSDATE+1)
  LEFT JOIN hz_parties                  par_brn_o
    ON par_brn_o.party_id               = aup.branch_id
  LEFT JOIN hz_organization_profiles    orp_o
    ON orp_o.party_id                   = par_brn_o.party_id
   AND SYSDATE BETWEEN Trunc (orp_o.effective_start_date) AND Nvl (Trunc (orp_o.effective_end_date), SYSDATE+1)
  LEFT JOIN hz_parties                  par_brn_n
    ON par_brn_n.party_id               = aup.branch_id_n
  LEFT JOIN hz_organization_profiles    orp_n
    ON orp_n.party_id                   = par_brn_n.party_id
   AND SYSDATE BETWEEN Trunc (orp_n.effective_start_date) AND Nvl (Trunc (orp_n.effective_end_date), SYSDATE+1)
  JOIN fnd_user                         usr
    ON usr.user_id                      = bac.created_by
 WHERE aup.seq_id                       IS NOT NULL
 ORDER BY aup.acc_id, aup.audit_timestamp DESC, aup.seq_id DESC

Owners Query
Query Structure Diagram
Owners Query
Subquery Tabulation


SELECT par.party_name owner,
  FROM iby_account_owners               own
  JOIN hz_parties                       par
    ON par.party_id                     = own.account_owner_party_id
  LEFT JOIN ap_suppliers                sup
    ON sup.party_id                     = par.party_id
 WHERE own.ext_bank_account_id          = :ACC_ID

Code for Bank Account Auditing XML Publisher Report


See also A Generic Unix Script for Uploading Oracle eBusiness Concurrent Programs

Design Patterns for Extracting Relational Data from XML

I recently replicated a very complicated Informatica data load from XML files into two Oracle staging tables in a much simpler way using just three SQL insert statements. I was able to load over 13,000 XML files into the tables in around 3 minutes on a windows PC running Oracle 11.2 XE, populating about 2 million records. This article will focus on the general design patterns involved in selecting relational data from XML using SQL in Oracle 11.2.

XML Selection Scenarios

The hierarchical structure of XML files means that the data may be stored at various different levels, but a relational SELECT statement returns data in a flat format. One way of converting between hierarchical and flat formats is to have separate SELECT statements for each level of data, and have any additional post-processing performed after inserting into staging tables. However, it is also possible to flatten the hierarchy within a single SELECT query clause in the case of a strict linear hierarchy.

It is instructive to consider the general case where any number of entities can occur within a linear hierarchy. If we understand how to deal with the case of two entities in a master-detail relationship, along with global values, then we will know how to deal with the general case, and so I will construct just such a test example.

XML Test Data

Here is the test XML file

    <elem-1-global attr-1-global="id_1_global">

The file represents data in a master-detail relationship, plus data that are global to the file. As can be seen, there is a global entity, elem-1-global, that occurs exactly once, a master entity, elem-1-master, that may occur multiple times and that contains a child entity, elem-2-detail, that may occur multiple times. Each entity has been assigned a single sample field. One entity has an XML attribute.

There are three master records, having two, two and zero detail records respectively. We will filter out one of the second master’s detail records.

In an article from January 2012, Data Modelling of XML SOAP Documents, I introduced my own diagram notation for hierarchical data structures, and re-use it below, showing the links to entities in relational form. I have added rounded corners to distinguish attributes from fields.


Reading the XML File

CREATE TABLE xml_design_pattern (
       xml_field   XMLTYPE) 
INSERT INTO xml_design_pattern VALUES (
       XMLTYPE (BFilename ('BRENDAN_IN_DIR', 'DESIGN_PATTERN.xml'),                      
                           NLS_Charset_id ('AL32UTF8'))

Unfiltered, Outer Join on Detail

       x1.global_id, x1.global_val, x2.master, x3.detail
  FROM xml_design_pattern t,
     XMLTable ('/elem-0-body'
                   PASSING t.xml_field
                   COLUMNS global_id           VARCHAR2(100) PATH 'elem-1-global/@attr-1-global',
                           global_val          VARCHAR2(100) PATH 'elem-1-global/field-1-global',
                           l2_xml              XMLTYPE       PATH 'list-1-master') x1,
     XMLTable ('/list-1-master/elem-1-master'
                   PASSING x1.l2_xml
                   COLUMNS master              VARCHAR2(100) PATH 'field-1-master',
                           l3_xml              XMLTYPE       PATH 'list-2-detail') x2,
     XMLTable ('/list-2-detail/elem-2-detail'
                   PASSING x2.l3_xml
                   COLUMNS detail              VARCHAR2(100) PATH 'field-2-detail') (+) x3
ORDER BY 1, 2, 3, 4


--------------- --------------- --------------- --------------------
id_1_global     val_global      val_master_m1
id_1_global     val_global      val_master_m2
id_1_global     val_global      val_master_m3

The result shows that none of the detail records have been joined, while the expected result would be that all detail records would be joined with null detail only for m3.

Filtered, Outer Join on Detail

SELECT x1.global_id, x1.global_val, x2.master, x3.detail
  FROM xml_design_pattern t,
     XMLTable ('/elem-0-body'
                   PASSING t.xml_field
                   COLUMNS global_id           VARCHAR2(100) PATH 'elem-1-global/@attr-1-global',
                           global_val          VARCHAR2(100) PATH 'elem-1-global/field-1-global',
                           l2_xml              XMLTYPE       PATH 'list-1-master') x1,
     XMLTable ('/list-1-master/elem-1-master'
                   PASSING x1.l2_xml
                   COLUMNS master              VARCHAR2(100) PATH 'field-1-master',
                           l3_xml              XMLTYPE       PATH 'list-2-detail') x2,
     XMLTable ('/list-2-detail/elem-2-detail[field-2-detail != "val_detail_m2_d1"]'
                   PASSING x2.l3_xml
                   COLUMNS detail              VARCHAR2(100) PATH 'field-2-detail') (+) x3
ORDER BY 1, 2, 3, 4


no rows selected

The result shows that no records have been returned, while the expected result would be that all master records would be returned, with detail records joined for all except the filtered out detail, with null detail only for m3.

Unfiltered, Outer Joins on all

SELECT x1.global_id, x1.global_val, x2.master, x3.detail
  FROM xml_design_pattern t,
     XMLTable ('/elem-0-body'
                   PASSING t.xml_field
                   COLUMNS global_id           VARCHAR2(100) PATH 'elem-1-global/@attr-1-global',
                           global_val          VARCHAR2(100) PATH 'elem-1-global/field-1-global',
                           l2_xml              XMLTYPE       PATH 'list-1-master') (+) x1,
    XMLTable ('/list-1-master/elem-1-master'
                   PASSING x1.l2_xml
                   COLUMNS master              VARCHAR2(100) PATH 'field-1-master',
                           l3_xml              XMLTYPE       PATH 'list-2-detail') (+) x2,
     XMLTable ('/list-2-detail/elem-2-detail'
                   PASSING x2.l3_xml
                   COLUMNS detail              VARCHAR2(100) PATH 'field-2-detail') (+) x3
ORDER BY 1, 2, 3, 4


--------------- --------------- --------------- --------------------
id_1_global     val_global      val_master_m1   val_detail_m1_d1
id_1_global     val_global      val_master_m1   val_detail_m1_d2
id_1_global     val_global      val_master_m2   val_detail_m2_d1
id_1_global     val_global      val_master_m2   val_detail_m2_d2
id_1_global     val_global      val_master_m3

The result shows that all records are returned, as expected.

Unfiltered, Inner Joins
The attached file shows that, without the outer joins, records are returned only for the first two master records that have detail records, as expected.

Filtered, Outer Joins on all


SELECT x1.global_id, x1.global_val, x2.master, x3.detail
  FROM xml_design_pattern t,
     XMLTable ('/elem-0-body'
                   PASSING t.xml_field
                   COLUMNS global_id           VARCHAR2(100) PATH 'elem-1-global/@attr-1-global',
                           global_val          VARCHAR2(100) PATH 'elem-1-global/field-1-global',
                           l2_xml              XMLTYPE       PATH 'list-1-master') (+) x1,
     XMLTable ('/list-1-master/elem-1-master'
                   PASSING x1.l2_xml
                   COLUMNS master              VARCHAR2(100) PATH 'field-1-master',
                           l3_xml              XMLTYPE       PATH 'list-2-detail') (+) x2,
     XMLTable ('/list-2-detail/elem-2-detail[field-2-detail != "val_detail_m2_d1"]'
                   PASSING x2.l3_xml
                   COLUMNS detail              VARCHAR2(100) PATH 'field-2-detail') (+) x3
ORDER BY 1, 2, 3, 4


--------------- --------------- --------------- --------------------
id_1_global     val_global      val_master_m1   val_detail_m1_d1
id_1_global     val_global      val_master_m1   val_detail_m1_d2
id_1_global     val_global      val_master_m2   val_detail_m2_d2
id_1_global     val_global      val_master_m3

The result shows that, with all joins outer joins, all records are returned except for the filtered-out detail record, as expected.

Filtered, Ansi Outer Joins on all


SELECT x1.global_id, x1.global_val, x2.master, x3.detail
  FROM xml_design_pattern t
     XMLTable ('/elem-0-body'
                   PASSING t.xml_field
                   COLUMNS global_id           VARCHAR2(100) PATH 'elem-1-global/@attr-1-global',
                           global_val          VARCHAR2(100) PATH 'elem-1-global/field-1-global',
                           l2_xml              XMLTYPE       PATH 'list-1-master') x1 ON 1=1
     XMLTable ('/list-1-master/elem-1-master'
                   PASSING x1.l2_xml
                   COLUMNS master              VARCHAR2(100) PATH 'field-1-master',
                           l3_xml              XMLTYPE       PATH 'list-2-detail') x2 ON 1=1
     XMLTable ('/list-2-detail/elem-2-detail[field-2-detail != "val_detail_m2_d1"]'
                   PASSING x2.l3_xml
                   COLUMNS detail              VARCHAR2(100) PATH 'field-2-detail') x3 ON 1=1
ORDER BY 1, 2, 3, 4

Output: The result in the attached file shows that, with all joins Ansi outer joins, all records are returned except for the filtered-out detail record, as expected.

Notes on SQL

XMLTable and XPath Syntax
Thh XMLTable function represents a rowset retrieved from an XML fragment. The first string within the call represents an XPath expression for the root element defining the rowset in the XML fragment passed. The number of occurrences in the fragment is the rowset cardinality.

The PASSING clause passes in the XMLTYPE field from a prior table or XMLTable instance from which the XML fragment is extracted. For a detail entity, the field will be passed from a master record in a a prior instance, and the instance order matters.

The COLUMNS clause defines the columns that can be included in the select list, and specifies a field for each column by an XPath expression. The expression is relative to the XMLTable root and must specify a single element instance.

XQuery Error (ORA-19279)
A common error in development is:
‘ORA-19279: XPTY0004 – XQuery dynamic type mismatch: expected singleton sequence – got multi-item sequence’
The error occurs when the XPath expression for a column selects more than one instance.

Filtering of the XNL elements is effected using standard XPath notation, with conditions placed in square brackets after the element name.

Fields and Attributes
Elements may contain attributes, which are denoted in standard XML syntax by preceding the attribute name with ‘@’. Further details on XML syntax for Oracle, including additional features such as namespaces, can be found in Oracle XML DB Developer’s Guide

Ansi and Oracle Join Syntaxes
Oracle introduced the Ansi standard join syntax in version 9, and it is generally to be preferred to its older proprietary syntax. However, this is debatable in the special case of SQL incorporating XMLTable because joining occurs in a non-standard way within the PASSING and other clauses of XMLTable, and not in the mandatory (except for cross joins) ON clause. In my examples, I have had to join ON 1=1 as a work-around.

Outer Joins
The examples show that outer-joining only the detail table does not work correctly. This appears to be a bug in Oracle 11.2. We have worked around it by outer-joining all tables.

Also note that the outer-join (+) needs to be after the table instance, unlike in standard SQL, where it goes after the join columns.

XMLTYPE Storage Clause
The XMLTYPE column in the table used to load the XML file has a storage clause that can specify either CLOB or BINARY XML (and maybe others) as the storage mode. It is vital for performance to choose BINARY XML if the table is going to be queried using XMLTable.

XPlan Statistics
I included calls to DBMS_XPlan in some of the queries in the attached files, and it can be seen that the CBO cardinalities are extermely inaccurate. This is perhaps not surprising as there is only one record in the table, which is exploded within the SQL, and the CBO is obviously not designed to optimise this.
SQL XML Design Patterns Files

List Aggregation in Oracle – Comparing Three Methods

In my last article, Grouping by Unique Subsequences in Oracle, I compared three solutions to a querying problem in Oracle. I found that a solution using a pipelined function was fastest across a range of test data sets, while another using Oracle’s Model clause turned out to be extremely inefficient, and very unscaleable owing to quadratic variation in execution times.

I mentioned in the article the issue of possible poor cardinality estimates by Oracle’s Cost-Based Optimiser (CBO) for pipelined functions, referencing an article by Adrian Billington, setting cardinality for pipelined and table functions, that considers four techniques for improving these estimates.

In this article, I want to use another, very common, querying problem, to see in more detail how one of these techniques works, namely the dynamic sampling hint, and to compare the performance again of pipelined functions against the Model clause on a second example amenable to both methods.

In previous articles I have generally focussed on elapsed and CPU times to measure performance, but Oracle provides a range of metrics in instrumenting query execution. My benchmarking framework captures many of these, and we’ll use them to try to understand the performance variation, again using a 2-dimensional domain of test data. We also capture the execution plans used across the domain and display visually the changes across the domain.

The problem is that of list aggregation, and various solution methods are available depending on one’s Oracle version. In Oracle v11.2 a specific built-in function has been provided, Listagg, and Adrian Billington has compared it with earlier SQL techniques (listagg function in 11g release 2), including a Model solution. He looks only at pure SQL solutions, but we will take both the Model and Listagg solutions, and add in a pipelined function solution. We’ll take a simple test problem using Oracle’s demo HR schema, which will be: Return, for each department, its id, manager name, and a comma-separated, ordered list of its employee names.

Test Data
The HR tables employees and departments were copied structurally to test versions with _t suffixes and records were inserted programmatically by the performance testing packages.


Listagg Solution
How It Works
The first solution for this problem uses the aggregation function ListAgg, which is new in Oracle v11.2. The query groups employees by department, joins departments to get the name, and employees again to get the manager’s name.

Query Diagram


SELECT e.department_id,
       m.last_name manager,
       ListAgg (e.last_name, ',') WITHIN GROUP (ORDER BY e.last_name) emp_names
  FROM employees_t e
  JOIN departments_t d
    ON d.department_id = e.department_id
  JOIN employees_t m
    ON m.employee_id = d.manager_id
 ORDER BY e.department_id

Model Solution
How It Works

  1. Within an inline view, form the basic Select, with the department_id column, and append placeholders for the employee list and row number
  2. Add the Model keyword, partitioning by department_id, dimensioning by analytic function Row_Number, ordering by name within department, with name and name list as measures
  3. Define the only rule to prepend the list from the next element with the current name, going backwards (so the first record will be the one you want)
  4. Join the other tables to the inline view in the main query, strip off the last ‘,’, and filter out all except the first record for the department

Query Diagram


SELECT v.department_id, 
       e.last_name manager,
       RTrim (v.emp_names, ',') emp_names  
  FROM (
SELECT department_id,
  FROM employees_t 
      PARTITION BY (department_id)  
      DIMENSION BY (Row_Number() OVER 
                       (PARTITION BY department_id ORDER BY last_name) rn)
      MEASURES (last_name, CAST(NULL AS VARCHAR2(4000)) emp_names) 
      RULES (
        emp_names[ANY] ORDER BY rn DESC = last_name[CV()] || ',' || emp_names[CV()+1] 
) v
  JOIN departments_t d
    ON d.department_id = v.department_id
  JOIN employees_t e
    ON e.employee_id = d.manager_id
 WHERE v.rn = 1 
 ORDER BY v.department_id

Pipelined Function Solution
How It Works
This approach is based on pipelined database functions, which are specified to return array types. Pipelining means that Oracle transparently returns the records in batches while processing continues, thus avoiding memory problems, and returning initial rows more quickly. Within the function there is a simple cursor loop over the employees, joining departments to get the manager id. A string variable accumulates the list of employees, until the department changes, when the record is piped out, and the string reset to the new employee. The last record has to be piped after exiting the loop.

Two database types are specified, the first being an object with fields for the department and manager ids and the employee name list; the second is an array of the nested table form with elements of the first type.

Function Pseudocode

Loop over a cursor selecting the records in order 
    If the department changes or first record then
        If the department changes then
            Pipe the row out
        End if
        Reset variables to current record values
        Append the current employee name to the name list
    End if
End loop
If the last department is not null then
    Pipe the row out using saved values
End if

Just select the fields named in the record from the function wrapped in the TABLE keyword, and join employees to get the manager name.

Query Diagram

Function Definition (within package)

FUNCTION Dep_Emps RETURN dep_emps_list_type PIPELINED IS

  l_emp_names	        VARCHAR2(4000);
  old_manager_id        PLS_INTEGER;
  old_department_id     PLS_INTEGER;


  FOR r_val IN (SELECT e.department_id, d.manager_id, e.last_name 
                  FROM employees_t e
                  JOIN departments_t d
                    ON d.department_id = e.department_id
                 ORDER BY e.department_id, e.last_name) LOOP

    IF r_val.department_id != old_department_id OR old_department_id IS NULL THEN

      IF r_val.department_id != old_department_id THEN

        PIPE ROW (dep_emps_type (r_val.department_id, r_val.manager_id, l_emp_names));

      END IF;
      old_department_id := r_val.department_id;
      old_manager_id := r_val.manager_id;
      l_emp_names := r_val.last_name;


      l_emp_names := l_emp_names || ',' || r_val.last_name;

    END IF;


  IF old_department_id IS NOT NULL THEN
    PIPE ROW (dep_emps_type (old_department_id, old_manager_id, l_emp_names));

END Dep_Emps;


SELECT d.department_id,
       e.last_name manager,
  FROM TABLE (Stragg.Dep_Emps) d
  JOIN employees_t e
    ON e.employee_id = d.manager_id
 ORDER BY d.department_id

Performance Analysis
As in the previous article, I have benchmarked across a 2-dimensional domain, in this case width being the number of employees per department, and depth the number of departments. For simplicity, a single department, ‘Accounting’, and employee, ‘John Chen’, were used as templates and inserted repeatedly with suffixes on names and new ids.

The three queries above were run on all data points, and in addition the function query was run with a hint: DYNAMIC_SAMPLING (d 5).

Record Counts (total employees and employees per department)


In the embedded Excel file above, the four solutions are labelled as follows:

  • F = Pipelined Function solution
  • D = Pipelined Function with Dynamic Sampling hint solution
  • L = Listagg solution
  • M = Model solution

For the data points, font colour and fill colour signify:

  • Fill colours correspond to distinct execution plans whose hash values can be found later in the same tab. The formatted outputs can be found in the Plans tab for all distinct plans for selected data points (with hyperlinks)
  • White font signifies that the smallest elapsed time for the given data point occurred for the given solution, for all the tables except CPU time
  • For the CPU time table higher in the tab, white font signifies that the smallest CPU time for the given data point occurred for the given solution
  • Red font indicates that the solution incurred more than 500 disk reads (see the disk reads table later in the tab)


The CPU time for all four queries increases approximately in proportion with either width or depth dimension when the other is fixed, which is not surprising. The elapsed times are very similar to the CPU times for all except the larger problems using Model, which we’ll discuss in a later section. For the largest data point, the times rank in the following order:

The following points can be made:

  • The function query without the dynamic sampling hint was fastest for the largest data point, and by a significant margin over the next best, Listagg
  • The earlier detailed tables show that this was also true for the triangle of data points starting three points back in each dimension, indicating that this is the case once the problem size gets large enough
  • Similarly, the function query with dynamic sampling was in third place for all these larger problems
  • Model was always the slowest query, generally by a factor of about 8 over the fastest in terms of CPU time, but very much worse in elapsed time for problems above a certain size, and we’ll look at this in more detail next.

Model Performance Discontinuity
In Adrian Billington’s article mentioned above (listagg function in 11g release 2), he took a single large-ish data point for his problem and found that his Model query took 308 seconds compared with 6 seconds for Listagg. He puts the Model performance down to ‘an enormous number of direct path reads/writes to/from the temporary tablespace‘. In my last article, I also quoted the anonymous author of MODEL Performance Tuning: ‘In some cases MODEL query performance can even be so poor that it renders the query unusable. One of the keys to writing efficient and scalable MODEL queries seems to be keeping session memory use to a minimum’.

My benchmarking framework records the metrics from the view v$sql_plan_stats_all, which is used by DBMS_XPlan.Display_Cursor to write the execution plan, and prints to a CSV file aggregates over the plan of several of them, for example: Max (last_disk_reads). These are are shown for the Model solution in the tab displayed below of the embedded Excel file (the next tab has 3-d graphs but they may not display in a browser).

The tables show that memory increases with the problem size in each direction up to a maximum, at which points the number of disk reads jumps from a very low level and then rises with problem size. The maximum memory points have red font. These transitions represent discontinuities where there is a jump in the elapsed times, although CPU times continue to rise smoothly. So while Model is always slower than the other solutions, its much greater use of memory causes the discrepancy to increase dramatically when the processing spills to disk, which is consistent with, and extends, the observations of the authors mentioned.

Model and Listagg Cardinality Estimates
Here is the execution plan for the last data point:

| Id  | Operation               | Name          | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  | Writes |  OMem |  1Mem | Used-Mem | Used-Tmp|
|   0 | SELECT STATEMENT        |               |      1 |        |   1024 |00:01:25.46 |    5798 |    164K|    164K|       |       |          |         |
|   1 |  SORT ORDER BY          |               |      1 |    260K|   1024 |00:01:25.46 |    5798 |    164K|    164K|  2320K|   704K| 2062K (0)|         |
|*  2 |   HASH JOIN             |               |      1 |    260K|   1024 |00:01:20.86 |    5798 |    164K|    164K|   909K|   909K| 1230K (0)|         |
|*  3 |    HASH JOIN            |               |      1 |   1024 |   1024 |00:00:00.11 |    2901 |      0 |      0 |   935K|   935K| 1228K (0)|         |
|   4 |     TABLE ACCESS FULL   | DEPARTMENTS_T |      1 |   1024 |   1024 |00:00:00.01 |       6 |      0 |      0 |       |       |          |         |
|   5 |     TABLE ACCESS FULL   | EMPLOYEES_T   |      1 |    260K|    262K|00:00:00.40 |    2895 |      0 |      0 |       |       |          |         |
|*  6 |    VIEW                 |               |      1 |    260K|   1024 |00:01:20.68 |    2897 |    164K|    164K|       |       |          |         |
|   7 |     BUFFER SORT         |               |      1 |    260K|    262K|00:01:19.56 |    2897 |    164K|    164K|   297M|  5726K|   45M (0)|     265K|
|   8 |      SQL MODEL ORDERED  |               |      1 |    260K|    262K|01:28:30.86 |    2895 |    131K|    131K|  1044M|    28M|   51M (1)|         |
|   9 |       WINDOW SORT       |               |      1 |    260K|    262K|00:00:01.01 |    2895 |      0 |      0 |  7140K|  1067K| 6346K (0)|         |
|  10 |        TABLE ACCESS FULL| EMPLOYEES_T   |      1 |    260K|    262K|00:00:00.50 |    2895 |      0 |      0 |       |       |          |         |

Predicate Information (identified by operation id):

   2 - access("D"."DEPARTMENT_ID"="V"."DEPARTMENT_ID")
   3 - access("E"."EMPLOYEE_ID"="D"."MANAGER_ID")
   6 - filter("V"."RN"=1)

Notice that at line 6 the cardinality estimate is 260K while the actual rows returned was 1024: The CBO has simply ignored the filtering down to department level by row number, and assumed the number of employees! We may ask whether this mis-estimate has affected the subsequent plan. Well the result set at line 6 makes the second step in a hash join to another row set of actual cardinality 1024, so probably it has not made a significant difference in this case. It’s worth comparing the execution plan for Listagg:

| Id  | Operation            | Name          | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
|   0 | SELECT STATEMENT     |               |      1 |        |   1024 |00:00:02.11 |    5796 |       |       |          |
|   1 |  SORT GROUP BY       |               |      1 |    185K|   1024 |00:00:02.11 |    5796 |    15M|  1999K|   14M (0)|
|*  2 |   HASH JOIN          |               |      1 |    260K|    262K|00:00:02.17 |    5796 |   921K|   921K| 1195K (0)|
|*  3 |    HASH JOIN         |               |      1 |   1024 |   1024 |00:00:00.11 |    2901 |   935K|   935K| 1229K (0)|
|   4 |     TABLE ACCESS FULL| DEPARTMENTS_T |      1 |   1024 |   1024 |00:00:00.01 |       6 |       |       |          |
|   5 |     TABLE ACCESS FULL| EMPLOYEES_T   |      1 |    260K|    262K|00:00:00.41 |    2895 |       |       |          |
|   6 |    TABLE ACCESS FULL | EMPLOYEES_T   |      1 |    260K|    262K|00:00:00.46 |    2895 |       |       |          |

Predicate Information (identified by operation id):

   2 - access("D"."DEPARTMENT_ID"="E"."DEPARTMENT_ID")
   3 - access("M"."EMPLOYEE_ID"="D"."MANAGER_ID")

Notice that the cardinality estimates are accurate apart from at line 1 for the Sort Group By, where a similar error has been made in not allowing for the reduction in rows caused by the grouping. Again it doesn’t seem to have affected the plan adversely.

Memory Usage and Buffers
A few observations can be made about these statistics:

  • Buffers is sometimes seen as a good, fundamental measure of performance, but we can see that both function solutions have figures of about 9K, while the other two have very similar figures of about 6K, and these do not correlate well with actual time performance here
  • The buffers figure for Model at the minimum data point is more than half that for the maximum, unlike the other solutions where it is 1-3%. The ratio of records is only 0.2%, so this is hard to understand
  • Similarly, the memory usage for Model starts very high, 3.1M, before rising to its maximum of 54M. For pipelined functions the figures go from 6K to 2.8M, and for Listagg from 29K to 15M

Dynamic Sampling Effects
Here is the execution plan for the pipelined function solution at point (W256-D512), with my own timing output from ‘Timer Set…’ on:

| Id  | Operation                           | Name        | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
|   0 | SELECT STATEMENT                    |             |      1 |        |    512 |00:00:00.65 |    6097 |       |       |          |
|   1 |  SORT ORDER BY                      |             |      1 |   8168 |    512 |00:00:00.65 |    6097 |  1186K|   567K| 1054K (0)|
|*  2 |   HASH JOIN                         |             |      1 |   8168 |    512 |00:00:00.62 |    6097 |  1520K|   901K| 1706K (0)|
|   3 |    COLLECTION ITERATOR PICKLER FETCH| DEP_EMPS    |      1 |   8168 |    512 |00:00:00.54 |    3202 |       |       |          |
|   4 |    TABLE ACCESS FULL                | EMPLOYEES_T |      1 |    130K|    131K|00:00:00.20 |    2895 |       |       |          |

Predicate Information (identified by operation id):

   2 - access("E"."EMPLOYEE_ID"=VALUE(KOKBF$))

Timer Set: Cursor, Constructed at 23 Sep 2012 07:49:43, written at 07:49:45
[Timer timed: Elapsed (per call): 0.05 (0.000045), CPU (per call): 0.04 (0.000040), calls: 1000, '***' denotes corrected line below]

Timer                  Elapsed          CPU          Calls        Ela/Call        CPU/Call
-----------------   ----------   ----------   ------------   -------------   -------------
Open cursor               0.01         0.00              1         0.01000         0.00000
First fetch               0.65         0.63              1         0.64700         0.63000
Write to file             0.06         0.06              2         0.03050         0.03000
Remaining fetches         0.00         0.00              1         0.00000         0.00000
Write plan                0.64         0.61              1         0.64200         0.61000
(Other)                   0.11         0.05              1         0.11400         0.05000
-----------------   ----------   ----------   ------------   -------------   -------------
Total                     1.47         1.35              7         0.21057         0.19286
-----------------   ----------   ----------   ------------   -------------   -------------

Here is the output for the pipelined function solution with dynamic sampling at the same point (W256-D512):

| Id  | Operation                            | Name        | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
|   0 | SELECT STATEMENT                     |             |      1 |        |    512 |00:00:00.59 |    4228 |       |       |          |
|   1 |  SORT ORDER BY                       |             |      1 |    512 |    512 |00:00:00.59 |    4228 |  1186K|   567K| 1054K (0)|
|   2 |   NESTED LOOPS                       |             |      1 |        |    512 |00:00:00.59 |    4228 |       |       |          |
|   3 |    NESTED LOOPS                      |             |      1 |    512 |    512 |00:00:00.58 |    3716 |       |       |          |
|   4 |     COLLECTION ITERATOR PICKLER FETCH| DEP_EMPS    |      1 |    512 |    512 |00:00:00.56 |    3202 |       |       |          |
|*  5 |     INDEX UNIQUE SCAN                | EMP_PK      |    512 |      1 |    512 |00:00:00.01 |     514 |       |       |          |
|   6 |    TABLE ACCESS BY INDEX ROWID       | EMPLOYEES_T |    512 |      1 |    512 |00:00:00.01 |     512 |       |       |          |

Predicate Information (identified by operation id):

   5 - access("E"."EMPLOYEE_ID"=VALUE(KOKBF$))

   - dynamic sampling used for this statement (level=2)

Timer Set: Cursor, Constructed at 23 Sep 2012 07:49:45, written at 07:49:47
[Timer timed: Elapsed (per call): 0.05 (0.000045), CPU (per call): 0.05 (0.000050), calls: 1000, '***' denotes corrected line below]

Timer                  Elapsed          CPU          Calls        Ela/Call        CPU/Call
-----------------   ----------   ----------   ------------   -------------   -------------
Open cursor               0.55         0.55              1         0.54500         0.55000
First fetch               0.59         0.57              1         0.59300         0.57000
Write to file             0.06         0.06              2         0.03100         0.03000
Remaining fetches         0.00         0.00              1         0.00000         0.00000
Write plan                0.59         0.58              1         0.59300         0.58000
(Other)                   0.06         0.03              1         0.05800         0.03000
-----------------   ----------   ----------   ------------   -------------   -------------
Total                     1.85         1.79              7         0.26443         0.25571
-----------------   ----------   ----------   ------------   -------------   -------------

Notice that in the plan without dynamic sampling the cardinality estimate for the row set returned from the function is 8168, nearly 8 times the actual rows. When dynamic sampling is added the cardinality estimate is exactly right. Also the times reported in the plan are similar but show dynamic sampling to be faster. How can that be, when the table of results earlier showed the query with dynamic sampling taking nearly twice as long?

My framework breaks down the times for the steps in running the query and writing the results out. From these we see that the difference is largely accounted for by the function query taking only 0.01 seconds to open the cursor while with dynamic sampling it takes .55 seconds. Evidently the dynamic sampling hint is causing a call to the function at the query parsing stage which is not accounted for in the execution plan statistics. (Notice incidentally that Oracle is apparaently performing a deferred open in both cases, where the actual cursor opening is performed at the time of first fetching.)

If one looks at the colour-coded table of elapsed times above, it can be seen that dynamic sampling causes a single plan to be chosen for all data points with depth below 1024, while without the hint two plans are chosen that with dynamic sampling are chosen only at depth 1024. It can also be seen that the dynamic sampling version is faster overall in most cases, but at the highest depth is slower because the non-hinted plan is the same. The default cardinality estimate (8168) was too high until that point.

We have applied three techniques for list aggregation to one specific problem, and for that problem the following concluding remarks can be made:

  • The Model solution is much inferior in performance to both the new Listagg native function, and custom pipelined function solutions
  • The variation in performance has been analysed and for the model solution shown to become dramatically worse when problem size causes earlier in-memory processing to spill to disk
  • The dynamic sampling hint has been applied to the pipelined function solution and shown to give correct cardinality estimates. In our example, the performance effect was negative for the largest problem sizes owing to the overhead involved, but in other cases it was positive
  • We have oberved that both Model and Listagg solutions also have cardinality estimation problems, but have not analysed these further
  • The native Listagg solution is significantly, and surprisingly, slower than the custom pipelined function solution at the largest data points considered

We may say more generally:

  • Performance analysis across a 2-dimensional domain of data sets can provide more insight than just looking at one large zero-dimensional case
  • Microsoft Excel graphs and other functionality have proved very useful in helping to visualise what is happening in terms of performance
  • Our findings tend to bear out a common suspicion of Model clause on performance grounds, and further support the view that pipelined functions can be very performance-effective, used appropriately

Query Structure Diagramming

Last bank holiday Monday I posted a solution to an SQL problem on OTN, and I later thought that the SQL would make a nice example to illustrate my Query Structure Diagramming (QSD) technique. I published my first example of this in May 2009 on scribd,


and have continued to develop it in subsequent articles. I use the technique here to illustrate the SQL structure for the OTN example mentioned and also for a second OTN example that I posted shortly after. Both examples structure the queries using subquery factors.

SQL Subquery Factors

Subquery factors were introduced in Oracle Database v9.2 and have since become a key technique in developing queries of any complexity. They generalise the v8 inline view technique, allowing subqueries now to be declared with an alias (using the ‘WITH’ clause), then referenced as often as desired later in the query. When referenced multiple times, Oracle’s Cost Based Optimiser (CBO) normally executes the subquery once and writes the results to temporary space to aid performance (this can be seen in the Explain Plan as a LOAD AS SELECT action). Using subquery factors can make queries much easier to read, even when they are referenced only once, in which case CBO normally restructures them internally to incorporate them within another subquery or the main query. It is important to note, though, that subquery factors, when retained by CBO, will be joined by full scans when referenced later in the query and in some cases it is more efficient to retain the table references to allow indexed joins (my next blog post will include an example of this).

Subquery factors, along with inline views, are the building blocks of modern SQL, as subroutines are of other languages. My QSDs are intended to show how they allow a procedural flow at the structural level, while retaining the set-based logic within the subqueries.

Leave and Attendance Query

The problem here is that the poster has a daily attendance table and a leave table, where leave is stored as date ranges, but wants a single query that outputs data in daily form. The keys to this are:

  • Realising that you cannot drive from the event tables but must generate a continuous set of days to drive from, for each employee (assuming you don’t have them in a separate reference table)
  • Converting the leave ranges to leave days by joining to the generated days rowset
  • Joining the leave daily rowset with the attendance table by a union

Read more here (I’m BrendanP on OTN): Attendance and Leave table Join


Note that tables were not provided for Employee and Day in the OTN post, but it is useful to include them as entities nonetheless.


Note that in the query below, both the date range and the employee set are generated from the transactional data, which is obviously an artificial feature arising from this being for a problem on a forum, but it’s no harm in terms of the purpose of this article.

WITH ext AS (
SELECT Min (att_date) min_date, Max (att_date) - Min (att_date) + 1 n_days
  FROM attendance
), dys AS (
SELECT min_date + LEVEL - 1 day
  FROM ext
CONNECT BY LEVEL < n_days + 1
), ems AS (
SELECT emp_id
  FROM attendance
SELECT employee_number
  FROM leave
), edy AS (
  FROM dys
), ldy AS (
  FROM edy
  JOIN leave                lve
    ON edy.day              BETWEEN lve.date_start AND lve.date_end
   AND lve.employee_number  = edy.emp_id
), uni AS (
  FROM attendance
  FROM ldy
  FROM edy
    ON uni.att_date     = edy.day
   AND uni.emp_id       = edy.emp_id
 ORDER BY 1, 2{code}


Counting Flight Statistics Query

The problem here is that the poster has three tables with data on events for frequent fliers and wants to show aggregate counts by year, but wants all years within a range to be included in the output, including years with no events. The key to this is realising that you cannot drive from the event tables but must generate a continuous set of years to drive from (assuming you don’t have them in a separate reference table). Read more here: Multiple Count aggregates from different sources grouped by Year


Note that a table was not provided for Year in the OTN post, but it is useful to include it as an entity nonetheless.


WITH yrs AS (
SELECT Add_Months (To_Date ('01011989', 'ddmmyyyy'), 12*LEVEL) YEAR
), ffe AS (
SELECT Trunc (start_date, 'YEAR') year, Count(*) n_enr
  FROM freq_flyer_enrollment
 GROUP BY Trunc (start_date, 'YEAR')
), ffs AS (
SELECT Trunc (survey_date, 'YEAR') year, Count(*) n_sur
  FROM freq_flyer_survey
 GROUP BY Trunc (survey_date, 'YEAR')
), fff AS (
SELECT Trunc (flt_date, 'YEAR') year, Count(*) n_fly
  FROM freq_flyer_flights
 GROUP BY Trunc (flt_date, 'YEAR')
SELECT  yrs.year,
        Nvl (ffe.n_enr, 0) n_enr,
        Nvl (ffs.n_sur, 0) n_sur,
        Nvl (fff.n_fly, 0) n_fly
  FROM yrs
    ON ffe.year = yrs.year
    ON ffs.year = yrs.year
    ON fff.year = yrs.year


Data Modelling of XML SOAP Documents

I have been involved in a number of projects where web services are used for interfacing, and have generally found a high level of complexity in their use compared with traditional interfacing methods. One of the areas of complexity lies in converting between the XML messages and the fields and arrays used in conventional programming languages such as Oracle PL/SQL. Often this is handled in an unmodular way with request messages being manually built, and the response message being parsed by individual xpath searches for specific strings.

My approach is to handle these conversions in a generic layer that lies between the client applications and the low-level APIs provided by the programming language for HTTP calls and XML processing. This post deals with a data model and the data structures used in the PL/SQL package that I wrote for calling web services from PL/SQL. I have posted on the program itself here: A Layered Approach To Processing XML Web Services and expect to post on examples of use at a later date.

Web Services
XML web services have become a standard mechanism for interfacing data between applications across the internet. The advantage that they have is that a standard transfer mechanism (HTTP, Hypertext Transfer Protocol) is used, and the data are formatted according to a standard specification, regardless of the technologies in which the applications are implemented. The data formats are described in a Web Services Description Language (WSDL) file, and interfacing is effected using SOAP (Simple Object Access Protocol) messages, as specified by the World Wide Web Consortium (W3C).

Web services form the cornerstone of Service Oriented Architecture (SOA), which Oracle uses in its Application Integration Architecture (AIA). A good acronym dictionary is vital for working in these areas 🙂

Interfacing by web services consists of sending input data as a text string in XML format by an HTTP request, and receiving an HTTP response, also as a text string in XML format.

My Web Service Interface Program
The layer package is intended to handle any data structures that can be represented in XML. On the request side, the client application will call layer APIs to add records and elements to build the request structure without having to write any XML directly, and the layer will construct the XML SOAP message and call the web service. The response side relies on a generic data structure comprising two hierarchical arrays: a structure array that specifies the group structure of the response XML message (or a subset of it), and a data array that holds the data with pointers to the structure array. The structure array forms an input, and is used by the layer to call standard XML APIs (Oracle XML APIs in my implementation) to retrieve the data from the response. The data modelling and conceptual framework are not language-specific, while my implementation is in Oracle PL/SQL.

SOAP Data Model
Both input and output of a web service call include an XML SOAP message, which is a text string consisting of the data in the form of an XML hierarchy. The elements in the hierarchy contain a mandatory name field, plus optional namespace and value, optional list of child elements, and an optional list of attributes. The hierarchy must contain certain standard elements and attributes, within a structure shown in this skeleton SOAP message (modified from an example here SOAP Tutorial/SOAP Syntax, by W3Schools.com ):

In addition to the standard elements and attributes, there may be application specific elements, attributes and values as indicated by the ellipses.The hierarchy of elements can be represented as below, where the group entity on the left represents the fact that a number of elements at a given level may be implicitly grouped, although without this grouping being explicit in the SOAP document. We use this grouping in the data structures on the response side but not on the request side:

Each element in an XML document has a name, an optional value, optional attribute name/value pairs, with the names being optionally name space-qualified, and the element may contain child elements. Please refer to widely available documentation on the SOAP protocol for further information; here’s a link I found useful: A Busy Developer’s Guide to SOAP 1.1

Request Side Data Structures

The diagram shows the main data structures that we use for building the request. Boxes with double borders represent arrays, and a solid arrow represents a pointer from a field to an array element.

The XML Tree List structure is derived from the general model above by treating the list of attributes as a string to be included in the element entity, and corresponds to the array specified by data type xml_tree_list_type in the following table. This array is built from procedure calls by the client application, and is processed internally by a recursive procedure to construct the SOAP request message. Note that as well as the XML tree types, we have defined two additional list structures for convenience of parameter passing:

  • Name Space List for passing name spaces
  • XML Field List as it is often convenient to treat a group of fields as a record, consisting of a parent element and a list of child fields

Name Space List Data Types

Type Name Element Category Description
name_space_type Object Name space
ns_label Character Name space label
ns_address Character Name space address
name_space_list_type Array List of name spaces
(unnamed element) name_space_type* Name space

Field List Data Types

Type Name Element Category Description
field_type Object XML field
field_name Character Field name
field_value Character Field value (optional)
field_list_type Array List of XML fields
(unnamed element) field_type* XML field

XML Tree List Data Types

Type Name Element Category Description
xml_tree_type Object Record in the XML tree holding the data for an XML element
parent_id Integer Index to parent record in the XML tree
ns_label Character Name space label (optional)
field_name Character Field name
field_value Character Field value (optional)
attr_string Character Attribute string, including name and value (optional)
xml_tree_list_type Array XML tree
(unnamed element) xml_tree_type* Record in the XML tree

Response Side Data Structures

The diagram shows the main data structures that we use for processing the response. The broken arrow between the two arrays signifies that each nested child list in the data tree corresponds to a child group record in the structure tree.

The Group Structure Tree List array defines the group structure of the output data structure. To simplify input, this is first set up in an unnested structure, where a field points to its parent, by procedure calls from the client application. The program then converts this into the nested structure shown below it, where the children are included in the parent record, which is more suitable for the later processing. Note that the hierarchy may be, and usually is, a subset of the actual SOAP response, since typically layers are present in a SOAP response message that are not useful for the client application. Also note that in the event of a standard error response being returned from the web service, the group structure is replaced by that for the error response by the program.

  • Each of the arrays (at the top level) has a root element without a parent, and these records have null values other than for their respective child lists
  • The model can represent any number of hierarchy levels

Group Structure Data Types

Type Name Element Category Description
int_list_type Array List of integers
(unnamed element) Integer Integer
structure_tree_type Object Structure tree record
group_name Character The group tag, used as a search string
ns_prefix Character Name space prefix
attr_string Character An attribute string that has to be included in searches where the group name is ambiguous (Oracle JDeveloper uses array as a group tag for arrays, with an attribute to differentiate)
child_list int_list_type*
structure_tree_list_type Array Structure tree list
(unnamed element) structure_tree_type* Structure tree record

Data Structure Data Types

Type Name Element Category Description
child_group_list_type Array List of indexes in the data tree list of child groups of current data tree record
(unnamed element) int_list_type* List of indexes in the data tree list of child records of current group
data_tree_type Object Data tree record
field_list field_list_type* Field list (specified in input side)
child_group_list child_group_list_type* List of indexes in the data tree list of child groups of current data tree record
data_tree_list_type Array Data tree list
(unnamed element) data_tree_type* Data tree record

Generic Data Models
In database design it is well known that overly generic data models lead to poor performance and extra application complexity. The web service interfacing model is of course highly generic, and it should be noted that the same problems may indeed offset the acknowledged standardisation advantages. The data model and structures described here necessarily reflect the genericity of the underlying architecture, while the approach taken is intended to reduce application complexity by moving much of it into a callable module.