Skip to main content
3 of 25
added 9 characters in body
User1974
  • 1.5k
  • 25
  • 54

Loop through tables and check fields against validation table

Introduction:

I have an object-relational Oracle database (ESRI geodatabase) that has validation tables (a.k.a. lookup tables). In the Geographic Information Systems (GIS) world, the validation tables are called domains. Unlike typical validation tables, domains aren't stored in individual tables. They're stored in an XML column in a single system table called GDB_Items.

The domains are used in a cartography application called ArcMap. When a user is editing data, he/she is forced to choose a domain item from a drop-down list. A domain code is stored in the database, but a domain description is displayed in the application. Using domains is a method of enforcing data integrity at the application level.

Unfortunately, there are easy ways for users to unintentionally enter values into the fields that do not match the domains. An example would be the Field Calculator tool, which is often used to batch-populate a field (multiple rows at a time). The field calculator is essentially an update query, except it runs VBScript or Python, not SQL. Using tools like the field calculator to subvert domains is a common source of error in the data.

Presently, to check for erroneous values, I need to manually run a select query on each field with a domain, in each table in the database. There are multiple fields with domains in each table, and there are approximately 100 tables in the database, so this task is far too labor-intensive to be even remotely practical (hundreds of queries, run manually each quarter).

As far as I know, this problem is largely ignored by the GIS community. But I can't ignore it; I run critical joins and calculations on these fields to produce corporate reports, and it's disastrous when there are erroneous values.

The Data:

Tables:

TABLE_1
+--------------+--------------+
| FIELD_1      | FIELD_2      |
| {DOMAIN_123} | {DOMAIN_ABC} |
+--------------+--------------+
|       1      | A            |
|       2      | B            |
|       3      | BLACK_SHEEP  |
|      -99     |              |
+--------------+--------------+

TABLE_2
+--------------+---------+
| FIELD_1      | FIELD_2 |
| {DOMAIN_ABC} |         |
+--------------+---------+
| A            |         |
| B            |         |
| b*           |         |
| "nuLL"**     |         |
+--------------+---------+
*Oracle joins seem to be case sensitive, so this lowercase b would cause unexpected results in a join/query.
**Believe it or not, users enter the word "null" as text, instead of setting the state of the cell to <null>.

Domains:

DOMAIN_123
+------+-------------+
| CODE | DESCRIPTION |
+------+-------------+
|    1 | ONE         |
|    2 | TWO         |
|    3 | THREE       |
+------+-------------+

DOMAIN_ABC
+------+-------------+
| CODE | DESCRIPTION |
+------+-------------+
| A    | EH          |
| B    | BEE         |
| C    | SEE         |
+------+-------------+

List of all domain values:

--ALL_DOMAIN_VALUES_VW
SELECT
    i.NAME Domain
    ,SUBSTR(EXTRACTVALUE(CodedValues.COLUMN_VALUE, 'CodedValue/Code'),1,255) AS Code 
FROM 
    SDE.GDB_ITEMS_VW i 
JOIN SDE.GDB_ITEMTYPES it ON i.Type = it.UUID, TABLE(XMLSEQUENCE(XMLType(Definition).Extract('/GPCodedValueDomain2/CodedValues/CodedValue'))) CodedValues 


Result set:
+------------+------+
|   DOMAIN   | CODE |
+------------+------+
| DOMAIN_123 | 1    |
| DOMAIN_123 | 2    |
| DOMAIN_123 | 3    |
+------------+------+
| DOMAIN_ABC | A    |
| DOMAIN_ABC | B    |
| DOMAIN_ABC | C    |
+------------+------+

List of fields with domains (extracted from an XML column in the GDB_ITEMS system table):

SELECT       
    i.NAME AS table_name
    ,EXTRACTVALUE(coded_values.COLUMN_VALUE, 'GPFieldInfoEx/Name') AS field_name
    ,EXTRACTVALUE(coded_values.COLUMN_VALUE, 'GPFieldInfoEx/DomainName') AS domain_name 
FROM        
     SDE.GDB_ITEMS_VW i 
     JOIN SDE.GDB_ITEMTYPES it ON i.Type = it.UUID
     CROSS JOIN TABLE(XMLSEQUENCE(XMLType(definition).Extract('/DETableInfo/GPFieldInfoExs/GPFieldInfoEx'))) coded_values
WHERE        
      EXTRACTVALUE(coded_values.COLUMN_VALUE, 'GPFieldInfoEx/DomainName') IS NOT NULL 


Result set:
+------------+------------+-------------+
| TABLE_NAME | FIELD_NAME | DOMAIN_NAME |
+------------+------------+-------------+
| TABLE_1    | FIELD_1    | DOMAIN_123  |
| TABLE_1    | FIELD_2    | DOMAIN_ABC  |
| TABLE_2    | FIELD_1    | DOMAIN_ABC  |
+------------+------------+-------------+

The Question:

I can identify errors in a single field, in a single table, by running this query:

SELECT     
    'TABLE_1' AS TABLE_NAME
    ,'FIELD_1' AS FIELD_NAME
    ,FIELD_1 AS ERROR
FROM
   ENG.TABLE_1
LEFT JOIN
    (
    SELECT CODE
    FROM  ENG.ALL_DOMAIN_VALUES_VW
    WHERE DOMAIN = 'DOMAIN_123'
    ) ON FIELD_1 = CODE
WHERE
    FIELD_1 IS NOT NULL
    AND CODE IS NULL


Result set:
+------------+------------+-------+
| TABLE_NAME | FIELD_NAME | ERROR |
+------------+------------+-------+
| TABLE_1    | FIELD_1    |   -99 |
+------------+------------+-------+

However, I want to run this query on all fields with domains, in a single batch process.

Can I loop through the 'List of fields with domains', and run the query on each field with a domain? Can I dynamically generate a query for each field, by subbing in the TABLE_NAME, FIELD_NAME, and DOMAIN_NAME values as variables?

I'm not picky about the output. It could be a unioned select query, or inserted values into an ERRORS table. Whatever works.

User1974
  • 1.5k
  • 25
  • 54