Have you ever put a batch import/update/export process in place in
Access using the Execute or RunSQL method? If so, then you probably know the pains of
going back in your code to update the SQL statement. I'll be honest, I've come close to
pulling my hair on several occasions.
Till I realized one day that we could use an Access table and some
simple logic to not only trim the possible hundreds of lines of embedded SQL and VBA code,
but also make the queries themselves self documenting. One could continue to define
queries with Access's QBE and not have to end up concatenating the entire SQL in code.
I defined a table, tblSQL, as
Field Name |
Data Type |
Description |
SQLID |
Autonumber |
Primary Key for the table |
SQLSequence |
Long Integer |
Determines which order the SQL statement gets executed in |
CalledFromProc |
Text |
Procedure name which uses this SQL statement |
SQLString |
Memo |
The actual SQL string to be used |
SQLDescription |
Memo |
Description of what the SQL statement is supposed to do |
The field description represent the actual content of each field. The idea behind this
process is to save SQL statements in the order they need to get executed, ensuring their
uniqueness via the SQLSequence number. If you have several functions
using action queries, they can still be saved in the same table provided that you use the
entry in the CalledFromProc field to categorize the SQL statements. Some
typical entries in the table would look like
SQL ID |
SQL
Seq-
uence |
Called
From
Proc |
SQL
String |
SQL
Description |
3 |
1 |
Batch
Process1 |
UPDATE DISTINCTROW Table1 INNER JOIN Table2 ON Table1.SomeID =
Table2.SomeID SET Table2.SomeField = [Table1].[AnotherField]
WHERE (((Table2.ThirdField)="Microsoft Access")); |
Gets the value for SomeID from Table2 for ThirdField="Microsoft
Access" |
4 |
2 |
Batch
Process1 |
UPDATE DISTINCTROW Table2 SET Table2.LastName =
fRemoveChar([LastName],"_"), Table2.FirstName =
fRemoveChar([FirstName],"_"); |
Removes Underscores from First and Last names for all records and replaces
it with an underscore. |
5 |
3 |
Batch
Process1 |
UPDATE DISTINCTROW Table2 INNER JOIN MasterTable ON Table2.SomeID =
MasterTable.ThisID SET Table2.ThisID = MasterTable.TheID WHERE (((Table2.SomeID) Is
Null)); |
Get the ThisID for everybody from MasterTable table |
Create a simple form bound to this table containing all the fields. You may leave out
SQLID if you wish. This form will provide a means for you to save, change, and view the
saved SQL statements.
The steps required to run these queries in sequence just as they would have run had you
used a layout like concatenating the entire SQL in code are actually very simple.
We need to basically take these steps:
- Identify the SQL statements that we need to execute for a
particular procedure.
- Find out the first SQL statement in sequence (SQLSequence)
for all the statements found for the identified procedure.
- Replace all quotes with Chr(34) in the SQL statement.
- Execute the action query.
With some error trapping to handle syntax errors etc., and a function to replace
quotes, we have everything ready to go. A sample usage of this concept is the code is as
follows:
Sub sSomeSubRoutine()
Dim lodb As Database, strSQL As String
Dim loSQLRS As Recordset, lngCurrent As Long
Dim varTmp As Variant
Const cERR_GRACEFUL_EXIT = vbObjectError + 20
On Local Error GoTo Err_handler
varTmp = SysCmd(acSysCmdSetStatus, "Starting Batch process...")
Set lodb = CurrentDb
Set loSQLRS = lodb.OpenRecordset("Select * from tblSQL where" _
& " CalledFromProc='sStartOutSiderProcess'", dbOpenSnapshot)
lngCurrent = 1
With loSQLRS
.FindFirst "SQLSequence=" & lngCurrent
If .NoMatch Then Err.Raise cERR_GRACEFUL_EXIT
Do While Not .NoMatch
strSQL = adhHandleQuotes(!SQLString)
lodb.Execute strSQL, dbFailOnError
lngCurrent = lngCurrent + 1
.FindFirst "SQLSequence=" & lngCurrent
Loop
End With
Exit_Here:
varTmp = SysCmd(acSysCmdClearStatus)
Set loSQLRS = Nothing
Set lodb = Nothing
Exit Sub
Err_handler:
Dim strXX As String
Select Case Err.Number
Case 3065:
strXX = "You can only run Action queries by the Execute method."
strXX = strXX & vbCrLf & "Please change the SQL to an action statement."
strXX = strXX & vbCrLf & vbCrLf & loSQLRS!SQLString
MsgBox strXX, vbCritical + vbOKOnly, "Error in SQL Statement"
Case 3075:
strXX = "The following SQL statement has syntax error."
strXX = strXX & vbCrLf & "Please verify the SQL string and try again."
strXX = strXX & vbCrLf & vbCrLf & loSQLRS!SQLString
MsgBox strXX, vbCritical + vbOKOnly, "Error in SQL Statement"
Case cERR_GRACEFUL_EXIT:
Case Else:
strXX = "Proc: sSomeSubRoutine"
strXX = strXX & vbCrLf & "Error #: " & Err.Number
strXX = strXX & vbCrLf & "Description: " & Err.Description
If Not (loSQLRS Is Nothing) And Not (lodb Is Nothing) Then
strXX = strXX & vbCrLf & "The last action query " & vbCrLf & vbCrLf & _
loSQLRS!SQLString & vbCrLf & vbCrLf & "affected " & _
lodb.RecordsAffected & " records"
End If
MsgBox strXX, vbExclamation + vbOKOnly, "Runtime Error"
End Select
Resume Exit_Here
End Sub
Public Function adhHandleQuotes(strValue As String) As String
Const QUOTE As String = """"
adhHandleQuotes = QUOTE & adhReplace(strValue, QUOTE, _
QUOTE & " & Chr$(34) & " & QUOTE) & QUOTE
End Function
Function adhReplace(ByVal varValue As Variant, _
ByVal strFind As String, ByVal strReplace As String) As Variant
Dim intLenFind As Integer
Dim intLenReplace As Integer
Dim intPos As Integer
If IsNull(varValue) Then
adhReplace = Null
Else
intLenFind = Len(strFind)
intLenReplace = Len(strReplace)
intPos = 1
Do
intPos = InStr(intPos, varValue, strFind)
If intPos > 0 Then
varValue = Left(varValue, intPos - 1) & _
strReplace & Mid(varValue, intPos + intLenFind)
intPos = intPos + intLenReplace
End If
Loop Until intPos = 0
End If
adhReplace = varValue
End Function
The benefits of this approach:
- The SQL is in ready condition to be pasted into the SQL view
of a query object. No longer do you have to remove underscores and concatenation
operators.
- Need to have a particular statement execute in a different
place? Simply change the SQLSequence for a particular procedure!
- Maintain the SQLDescription entries and you will find the
code a whole lot easier to understand when you revisit it down the road. You can even
create a report on the table sorted by CallingProcName and SQLSequence fields respectively
and viola! You have instant documentation.
- Need I mention the pages of codes that we just got rid of?
Everything is controlled by one single controlling procedure.
- Want to "test run" a certain number of statements?
Simply put the statements you don't want to run "out of sequence". That is, if
you want to run statements 1 through 10, but not 11 and 12; set the SQLSequence for
statements 11 and 12 to say, 20 and 21. Since the logic of the master procedure looks for
sequentially incrementing SQLSequence values, the code will stop after executing the 12th
statement.
|