Delete Unused Domains

15217
30
06-08-2015 11:50 AM
BlakeTerhune
MVP Regular Contributor

I created this script to help with some geodatabase house cleaning tasks. Maybe someone else will find it useful or have an idea to improve it.

import arcpy
import os

# Set workspace
myGDB = r"C:\temp\working.gdb"

# Get domains that are assigned to a field
domains_used = []
for dirpath, dirnames, filenames in arcpy.da.Walk(myGDB, datatype=["FeatureClass", "Table"]):
    for filename in filenames:
        print "Checking {}".format(os.path.join(dirpath, filename))
        try:
            ## Check for normal field domains
            for field in arcpy.ListFields(os.path.join(dirpath, filename)):
                if field.domain:
                    domains_used.append(field.domain)
            ## Check for domains used in a subtype field
            subtypes = arcpy.da.ListSubtypes(os.path.join(dirpath, filename))
            for stcode, stdict in subtypes.iteritems():
                if stdict["SubtypeField"] != u'':
                    for field, fieldvals in stdict["FieldValues"].iteritems():
                        if not fieldvals[1] is None:
                            domains_used.append(fieldvals[1].name)
        except Exception, err:
            print "Error:", err

# Get domains that exist in the geodatabase
domains_existing = [dom.name for dom in arcpy.da.ListDomains(myGDB)]

# Find existing domains that are not assigned to a field
domains_unused = set(domains_existing) ^ set(domains_used)
print "{} unused domains in {}".format(len(domains_unused), myGDB)
for domain in domains_unused:
    arcpy.DeleteDomain_management(myGDB, domain)
    print "{} deleted".format(domain)

python snippets

30 Replies
RuchiraWelikala
Occasional Contributor

Hi Blake,

Thanks for the great script. A colleague recommended it to perform some long awaited maintenance on our SDE instances and this will be very handy.

While running it, however, the script through the following exception:

Traceback (most recent call last):
  File "P:\Scratch Workspace\CleanUnusedDomains.py", line 34, in <module>
    arcpy.DeleteDomain_management(myGDB, domain)
  File "C:\Program Files (x86)\ArcGIS\Desktop10.1\arcpy\arcpy\management.py", line 1357, in DeleteDomain
    raise e
ExecuteError: ERROR 999999: Error executing function.
Must be the owner to perform this operation.
Failed to execute (DeleteDomain).

Is there a way to execute this code when the connection being used isn't the owner? Perhaps a way to skip over the domains that don't belong to the current owner.

Thanks again,

Ruch

BlakeTerhune
MVP Regular Contributor

That's a great point, Ruchira Welikala​. I had only tested this with a file geodatabase, but the issue you are describing is absolutely correct with SDE; you need to be the owner. Conveniently, I've also been working on an SDE maintenance script that will reconcile versions, compress, rebuild indexes, and analyze datasets (source inspiration). For those last two tools, you need to run them as the data owner. My solution (for SDE in Oracle 11g):

  1. Get all the distinct owner names from the domain objects
  2. For each owner, create a temporary sde connection file​.
  3. Then use that temporary connection file to run whatever task you need (delete domain in this case).

Here is my final code that will remove unused domains from both local and remote (SDE) geodatabases. You will need to figure out a way to generate the password if it is different for each owner. Please test it out and let me know how it works for you.

import arcpy
from contextlib import contextmanager
import os
import shutil
import tempfile

def main():
    try:
        # Connection path to geodatabse (as administrator if SDE)
        myGDB = r"C:\GISConnections\SDE@GTEST.sde"

        # Get domains that are assigned to a field
        domainsUsed_names = []
        for dirpath, dirnames, filenames in arcpy.da.Walk(myGDB, datatype=["FeatureClass", "Table"]):
            for filename in filenames:
                print "Checking {}".format(os.path.join(dirpath, filename))
                ## Check for normal field domains
                for field in arcpy.ListFields(os.path.join(dirpath, filename)):
                    if field.domain:
                        domainsUsed_names.append(field.domain)
                ## Check for domains used in a subtype field
                subtypes = arcpy.da.ListSubtypes(os.path.join(dirpath, filename))
                for stcode, stdict in subtypes.iteritems():
                    if stdict["SubtypeField"] != u'':
                        for field, fieldvals in stdict["FieldValues"].iteritems():
                            if not fieldvals[1] is None:
                                domainsUsed_names.append(fieldvals[1].name)
                ## end for subtypes
            ## end for filenames
        ## end for geodatabase Walk

        # List of all existing domains (as domain objects)
        domainsExisting = arcpy.da.ListDomains(myGDB)

        # Find existing domain names that are not in use (using set difference)
        domainsUnused_names = (
            set([dom.name for dom in domainsExisting]) - set(domainsUsed_names)
        )

        # Get domain objects for unused domain names
        domainsUnused = [
            dom for dom in domainsExisting
            if dom.name in domainsUnused_names
        ]
        print "{} unused domains in {}".format(len(domainsUnused), myGDB)

        # Cleanup
        del domainsExisting
        del domainsUnused_names

        # Delete unused domains by owner
        ## For local geodatabses, owner is an empty string ('')
        with makeTempDir() as temp_dir:
            descGDB = arcpy.Describe(myGDB)
            for owner in set([dom.owner for dom in domainsUnused]):
                if descGDB.workspaceType == "RemoteDatabase":
                    ## Use temporary SDE connection as owner
                    myGDB = arcpy.CreateDatabaseConnection_management(
                        temp_dir,  ## out_folder_path
                        owner+".sde",  ## out_name
                        "ORACLE",  ## database_platform
                        "GISTEST.WORLD",  ## instance
                        "DATABASE_AUTH",  ## account_authentication
                        owner,  ## username
                        "myuserpass",  ## password
                    )
                    print arcpy.GetMessages()
                    ## Format result object as string for path to connection file
                    myGDB = str(myGDB)
                # Get unused domains for current owner
                domainsUnused_currentOwner = [
                    dom.name for dom in domainsUnused
                    if dom.owner == owner
                ]
                for domain in domainsUnused_currentOwner:
                    arcpy.DeleteDomain_management(myGDB, domain)
                    print "\t{} deleted".format(domain)
            ## end for domainsExisting_owners
        ## end with temp_dir

    finally:
        # Cleanup
        arcpy.ClearWorkspaceCache_management()


@contextmanager
def makeTempDir():
    """Creates a temporary folder and returns the full path name.
    Use in with statement to delete the folder and all contents on exit.
    Requires contextlib contextmanager, shutil, and tempfile modules.
    """
    temp_dir = tempfile.mkdtemp()
    try:
        yield temp_dir
    finally:
        shutil.rmtree(temp_dir)


if __name__ == '__main__':
    main()
RuchiraWelikala
Occasional Contributor

Wow, this is great stuff! Thanks, Blake. I will try out your script as well as your SDE maintenance scripts. The main problem with our SDE environment is that we have one production instance for both edits and map services. Hence, hundreds of locks that can't be broken to perform compressions and index rebuilds. I'm in the process of creating a dB replication workflow that'll likely remove some of the load off the instance and free up the database to allow for regular maintenance. Part of that is removing dozens of unused domains. Thanks again and I'll keep you posted. Cheers, Ruch

0 Kudos
BlakeTerhune
MVP Regular Contributor

I think you can still delete unused domains regardless of the geodatabase locks that may exist. Worth a shot.

RuchiraWelikala
Occasional Contributor

Oh yes, you can definitely delete with existing locks, however, deleting the unused domains is just one of the ways I'm trying to declutter our production environment. We're conducting an audit of all the items in the GDB. Deleting unused or archived feature classes/datasets/tables. Then I will deploy your script to remove the domains. My initial plan was to write a script that lists all of the unused domains and then go in manually and delete them myself, but this will save me hours of work! You're a life saver!

LeonS
by
Occasional Contributor III

Thanks for sharing!  I've had this script on my "to-do" list for longer than I can recall. May I suggest putting it up on GitHub for others to download and perhaps contribute?

Thanks again,

Leon

0 Kudos
BlakeTerhune
MVP Regular Contributor

I've never tried using GitHub, but you're welcome to spread the love there if you like. Be sure to post a link if you do!

LeonS
by
Occasional Contributor III

Here is the link to GitHub...   Script on GitHub

I added an output file to list the unused domains and made delete an option. I was little nervous to just let it run, so I wanted to just see a list first.

Thanks again

Leon

nizarsalih
New Contributor III

Great tool, Thank you.