The key requirements for this utility are:
- Connect to an Oracle database as a specified user
- Execute the given SQL query, unmodified, so the execution plan used should match that when the same SQL is submitted by the real application
- Consume all the rows of data produced, for a true time to complete measurement
There are three parts to achieving this really
- Connect to the Oracle database using the supplied credentials
- Read in the SQL query text from a file
- Execute against an Oracle database connection and consume the results noting the start and end time of this
The code I will now present is not "perfect" in an absolute sense - there is very limited error handling for instance - but it is complete and does work. I've used it many times to execute a SQL query pulled out from an application that is performing poorly, to verify its execution and the impact of any changes made to try and improve performance.
Python Code
Connecting to an Oracle databaseI use cx_Oracle as the database access module for Oracle, and connection is relatively simple. This assumes that the database credentials are in variables named "
user_name
", "user_password
" and "database_name
".import cx_Oracle # Other code here, such as parsing command line arguments to get values into necessary variables db_conn = cx_Oracle.connect (user_name, user_password, database_name)
Reading in the SQL query from a file
Assume that the name of the file containing the query is in the string variable "
SQL_File_Name
".sql_query = open (SQL_File_Name, "r").read ().rstrip (' ;\n') # Strip any semi-colon off from the endThis does several things in one combined statement:
- Opens the file for reading
- Reads in the content of the file returning it as a single string
- Strips off certain trailing characters at the end of the string - space, semi-colon or new line character.
sql_query
"
variable the text string of the SQL query with no trailing semi-colon
(if there was one). This is now ready for use to execute against an
Oracle database.Executing the query and timing it
For now assume that a database connection has been made and is referenced by the object named "
db_conn
".To execute the SQL query we first need to get a cursor from the database connection, then execute the SQL using it, retrieve all the rows of data, and report the elapsed time:
import time try : curs = db_conn.cursor () start_time = time.time () curs.execute (sql_query) # Now retrieve the rows of data produced by the query row_count = 0 rows = curs.fetchmany (1000) # Empty list will be False so can test it directly while rows : row_count += len (rows) rows = curs.fetchmany (1000) end_time = time.time () print ("Query Complete - Elapsed {0:4.1f}s for {1} rows".format ((end_time - start_time), row_count)) except Exception as ex : logging.critical ("ERROR - Exception raised during processing") logging.critical ("Exception - %s" % (ex))I've included exception handling in case there is something wrong with the SQL query or its execution, but there is not a lot we can do about it so it just prints out some information about the exception it caught.
More details
I actually allow all the necessary values to be passed in from the command line to this utility program, and I use the Python "argparse" module to process these and map them to corresponding variables. So you can either assign the results from "
argparse
" to the discrete variables I have used in the code examples, or instead replace those references by the results produced by "argparse
". The results will be the same either way.The following code can be placed before the code given so far in the final Python program:
import argparse import sys # Parse command line arguments parser = argparse.ArgumentParser (description="Time execution of a SQL query") # Add command line argument definitions one by one into the parser parser.add_argument ("-u", "--user", help="User name", required=False, default=None) parser.add_argument ("-p", "--password", help="Password", required=False, default=None) parser.add_argument ("-d", "--database", help="Database name", required=False, default=None) parser.add_argument ("SQL_File", help="Name of SQL File to execute", default=None) args = parser.parse_args () # First check whether we did get a user name, password etc on the command line # If not, prompt the user for what we are missing if args.database is None : print ("Enter database", end=": ", flush=True) database_name = sys.stdin.readline ().rstrip () else : database_name = args.database if args.user is None : print ("Enter username", end=": ", flush=True) user_name = sys.stdin.readline ().rstrip () else : user_name = args.user if args.password is None : print ("Enter password", end=": ", flush=True) user_password = sys.stdin.readline ().rstrip () else : user_password = args.password SQL_File_Name = args.SQL_File # Get Database Connection ... # Execute SQL and consume results ...