简体   繁体   中英

How to retrieve all saved queries from MS Access database using python?

Basically, i'm comparing 2 access databases using python.

i do not have access to manually open any access files, it must be done entirely within python!

I need to retrieve the full list of

  • Query names
  • Associated query code

I will not know what the names of the queries are ahead of time.

I've tried a number of solutions that have nearly worked, i've outlined the 3 closed below.


Partial Solution 1

i nearly had it working using win32com & the CurrentDb.QueryDefs method to retrieve each query's code.

However, it appears that the order of the joins is not stored deterministicaly between 2 databases. (it appears to be dependent on the order of the entry in MSysQueries)

ie in one database, the text for the join could be

on Table1.ColumnA = Table2.ColumnA & Table1.ColumnB = Table2.ColumnB

and in another

on Table1.ColumnB = Table2.ColumnB & Table1.ColumnA = Table2.ColumnA

obviously these will result in the same type of join, but not the exact same query text.

If i compared the text directly they do not match. Processing the text before comparing seems like a bad idea with lots of corner cases.

Sample Code

objAccess = Dispatch("Access.Application")
objAccess.Visible = False

counter = 0
query_dicts = {}
for database_path in (new_database_path, old_database_path):

    # Open New DB and pull stored queries into dict
    objAccess.OpenCurrentDatabase(database_path)
    objDB = objAccess.CurrentDb()

    db_query_dict = {}
    for stored_query in objDB.QueryDefs:
        db_query_dict[stored_query.name] = stored_query.sql

    query_dicts[("New" if counter == 0 else 'Old')] = db_query_dict

    objAccess.CloseCurrentDatabase()
    counter += 1

Partial Solution 2

After the first solution failed, i tried to write a query on MSysQueries and force an ordering. However, pyodbc does not have read access to the table!

It appears you cannot grant read access from python itself, which is an issue, could be wrong here.

Query:

SELECT MSysObjects.Name
          , MSysQueries.Attribute
          , MSysQueries.Expression
          , MSysQueries.Flag
          , MSysQueries.Name1
          , MSysQueries.Name2
FROM MSysObjects INNER JOIN MSysQueries ON MSysObjects.Id = MSysQueries.ObjectId
order by MSysObjects.Name
          , MSysQueries.Attribute
          , MSysQueries.Expression
          , MSysQueries.Flag
          , MSysQueries.Name1
          , MSysQueries.Name2

Partial Solution 3

Another thing i tried was to get python to store a VBA module into the database, that will write the meta info to a table and then read that table out via pyodbc .

i could add the module, but the access database kept prompting for a name for the module. I couldnt find the documentation on how to name the module with a method call

Sample Code:

import win32com.client as win32

import comtypes, comtypes.client
import win32api, time
from win32com.client import Dispatch

strDbName = r'C:\Users\Username\SampleDatabase.mdb'
objAccess = Dispatch("Access.Application")
# objAccess.Visible = False
objAccess.OpenCurrentDatabase(strDbName)
objDB = objAccess.CurrentDb()


xlmodule = objAccess.VBE.VbProjects(1).VBComponents.Add(1)  # vbext_ct_StdModule

xlmodule.CodeModule.AddFromString(Constants.ACCESS_QUERY_META_INFO_MACRO)

objAccess.Run("CreateQueryMetaInfoTable")

objAccess.CloseCurrentDatabase()


objAccess.Quit()

Macro i was attempting to add.

Sub CreateQueryMetaInfoTable()
    Dim sql_string As String

    # Create empty table
    CurrentDb.Execute ("Create Table QueryMetaInfoTable (QueryName text, SqlCode text)")

    Dim qd As QueryDef

    For Each qd In CurrentDb.QueryDefs

        # insert values
        sql_string = "Insert into QueryMetaInfoTable (QueryName, SqlCode) values ('" & qd.Name & "', '" & qd.SQL & "')"

        CurrentDb.Execute sql_string

    Next


End Sub

With the help of @Gord Thompson, i have a working solution now.

I needed to connect with OLEDB to grant the read access 1st, generated a non-system table with the info needed, then read the table back with ODBC via pandas.

CONNECTION_STRING_OLEDB = "PROVIDER=Microsoft.Jet.OLEDB.4.0;DATA SOURCE={};Jet OLEDB:System Database={};" 

ACCESS_QUERY_META_INFO_CREATE = """SELECT MSysObjects.Name
          , MSysQueries.Attribute
          , MSysQueries.Expression
          , MSysQueries.Flag
          , MSysQueries.Name1
          , MSysQueries.Name2
INTO QueryMetaInfo       
FROM MSysObjects INNER JOIN MSysQueries ON MSysObjects.Id = MSysQueries.ObjectId
order by MSysObjects.Name
          , MSysQueries.Attribute
          , MSysQueries.Expression
          , MSysQueries.Flag
          , MSysQueries.Name1
          , MSysQueries.Name2"""


ACCESS_QUERY_META_INFO_READ = """select * from QueryMetaInfo
order by Name
      , Attribute
      , Expression
      , Flag
      , Name1
      , Name2;"""

ACCESS_QUERY_META_INFO_DROP = "DROP TABLE QueryMetaInfo"

connection = win32com.client.Dispatch(r'ADODB.Connection')
DSN = CONNECTION_STRING_OLEDB.format(database_path, r"C:\Users\C218\AppData\Roaming\Microsoft\Access\System.mdw")
connection.Open(DSN)
cmd = win32com.client.Dispatch(r'ADODB.Command')
cmd.ActiveConnection = connection
cmd.CommandText = "GRANT SELECT ON MSysObjects TO Admin;"
cmd.Execute()
connection.Execute(ACCESS_QUERY_META_INFO_CREATE)
connection.Close()

# connect with odbc to read the query meta info into pandas
connection_string = Constants.CONNECTION_STRING_ACCESS.format(database_path)
access_con = pyodbc.connect(connection_string)
access_cursor = access_con.cursor()

df = pd.read_sql(ACCESS_QUERY_META_INFO_READ, access_con)

# drop table after read
access_cursor.execute(ACCESS_QUERY_META_INFO_DROP)
access_cursor.commit

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM