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)
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
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):
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()
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
I think you can still delete unused domains regardless of the geodatabase locks that may exist. Worth a shot.
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!
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
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!
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
Great tool, Thank you.