2

(CrossPosted from Reddit) I'm trying to use VBA to automate a data link to an Access Query. Unfortunately the query is a query of a query, so I'm not able to substitute a nice descriptive SQL statement for the source, like I normally would :/ I recorded a Macro of linking a different query to see what it would look like, then cleaned up the source and changed the command text to reference the query I'm wanting, but I've been metaphorically smashing my keyboard trying to understand how to add parameters. All I need is a date. I've been googling for 2 days now, and the syntax has me all kinds of confused.

Here's what I've got, with my most recent attempt commented out and the file path replaced with brackets

With ActiveSheet.ListObjects.Add(SourceType:=0, Source:=Array( _
        "OLEDB;Provider=Microsoft.ACE.OLEDB.12.0;Data Source=[]"), _
        Destination:=Range("$A$1")).QueryTable
        .CommandType = xlCmdTable
        .CommandText = Array("PeramIndvCallScore")
        '.Parameters(1).SetParam xlConstant, "8/1/2022"
        .RowNumbers = False
        .FillAdjacentFormulas = False
        .PreserveFormatting = True
        .RefreshOnFileOpen = False
        .BackgroundQuery = True
        .RefreshStyle = xlInsertDeleteCells
        .SavePassword = False
        .SaveData = True
        .AdjustColumnWidth = True
        .RefreshPeriod = 0
        .PreserveColumnInfo = True
        .SourceDataFile = []
        .Refresh BackgroundQuery:=False
    End With

I'll end up with different Run-time errors between "the query did not run," "application/object-defined error," "Object doesn't support this property or method," (when I remove the (1) from parameters)

Ideally I want to be able to use a cell range for parameters so as to make a dynamic data link.

Does anyone know if I can set an ADO Recordset to auto refresh in the same way that a data link does? Also, are all the properties here necessary, or can I omit a few of them? (In that, if I don't specify, do they have a default they're set to?)

Here's the SQL:

PeramIndvCallScore:

SELECT CallDetails.CallDate, IIf(([CallTypes].[ID]=1) Or ([calltypes].[ID]=4) Or ([calltypes].[ID]=6),'CSB/VES/Govt',[CallTypes].[Type]) AS CallType, CalculatedScores.TotalScore
FROM CallTypes INNER JOIN (CalculatedScores INNER JOIN CallDetails ON CalculatedScores.KeyID = CallDetails.KeyID) ON CallTypes.ID = CallDetails.CallType
WHERE (((CallDetails.CallDate)=[Date]) AND ((IIf(([CallTypes].[ID]=1) Or ([calltypes].[ID]=4) Or ([calltypes].[ID]=6),'CSB/VES/Govt',[CallTypes].[Type]))='CSB/VES/Govt') AND ((CallDetails.Spanish)=False) AND ((CallDetails.Omit)=False))
UNION ALL SELECT CallDetails.CallDate, IIf(([Calldetails].[Spanish]),'Spanish','') AS CallType, CalculatedScores.TotalScore
FROM CalculatedScores INNER JOIN CallDetails ON CalculatedScores.KeyID = CallDetails.KeyID
WHERE (((CallDetails.CallDate)=[Date]) AND ((IIf(([Calldetails].[Spanish]),'Spanish',''))="CSB/VES/Govt") AND ((CallDetails.Spanish)=True) AND ((CallDetails.Omit)=False));

And I've just taken a second look and saw that there is a spot that I can input the date as a variable in the SQL. Regardless, I will need to know how to add parameters to a QueryTable in the future for other parts of this project. Any help is appreciated :)

Ace_Q
  • 41
  • 6
  • 1
    What is `PeramIndvCallScore`? If an Access query can you show the underlying SQL and how to use parameters which sounds like a `WHERE` clause on date? How is this date determined? – Parfait Oct 14 '22 at 15:10
  • Be sure to qualify all `Range` objects as context can change depending on active status. See [How to avoid using Select in Excel VBA](https://stackoverflow.com/q/10714251/1422451). – Parfait Oct 14 '22 at 15:23
  • @Parfait I've edited the post to include the SQL from Access. The range in this case is just an empty cell to serve as the destination – Ace_Q Oct 14 '22 at 16:08
  • Review https://stackoverflow.com/questions/5122582/run-access-query-from-excel-and-pass-parameters-to-the-query. Apparently, it shows how to pass parameters to action SQL in Access. I don't really see how this is useful for a SELECT query. What do you want Excel to do with this query data? I have never used dynamic parameters in query objects. I use VBA to build filter criteria and apply to form or report. – June7 Oct 15 '22 at 03:10
  • @June7 I can't say I fully understand your question, but I'll try to provide an answer. I'm working on an automated system for my organization's QA team. They run daily, weekly, and monthly reports. Right now they're doing it all by hand. My goal is to create a system that is so automated that I'll only need to touch it for maintenance, so I need to figure out how to code a new monthly sheet to automatically populate daily numbers, which is why data links are attractive to me. I hope that sums it up, feel free to email me at AceQuantumBit@gmail.com – Ace_Q Oct 17 '22 at 16:55
  • Why is Excel involved? Why not just Access reports? – June7 Oct 18 '22 at 02:21
  • The team doesn't want to use anything other than Excel unfortunately. My boss also doesn't want to pay for everyone to use Access. – Ace_Q Oct 18 '22 at 20:36

1 Answers1

2

Several issues regarding your implementation:

  • SetParam is a property of QueryTable.Parameter object. Therefore, consider nesting another With block to initialize QueryTable.Parameter and call SetParam.

  • Though not visibly documented, parameters appear to be supported with ODBC Driver and not OLEDB Provider connections. Therefore, adjust to use the MS Access ODBC driver.

  • To use parameters, qmarks, ?, must be used for parameter placeholders and not named identifiers (which prompts user to enter value inside the MS Access GUI).

  • The XlCmdType does not support saved queries or views of backend database. Therefore, use the xlCmdSql type and pass an SQL string either from a cell range or .sql file. Below shows .sql file approach.

SQL

Save below as .sql file. Query uses table aliases and IN to tighten up code.

SELECT cd.CallDate, 
       IIf((ct.[ID] IN (1, 4 6), 'CSB/VES/Govt', ct.[Type]) AS CallType, 
       cs.TotalScore
FROM CallTypes ct
INNER JOIN (CalculatedScores cs
INNER JOIN CallDetails cd ON cs.KeyID = cd.KeyID) 
  ON ct.ID = cd.CallType
WHERE (((cd.CallDate) = ?) 
  AND ((IIf((ct.[ID] IN (1, 4 6), 'CSB/VES/Govt', ct.[Type])) = 'CSB/VES/Govt') 
  AND ((cd.Spanish)=False) AND ((cd.Omit)=False))

UNION ALL

SELECT cd.CallDate, 
       IIf(([Calldetails].[Spanish]), 'Spanish', '') AS CallType, 
       cs.TotalScore
FROM CalculatedScores cs
INNER JOIN CallDetails cd ON cs.KeyID = cd.KeyID
WHERE (((cd.CallDate) = ?) 
  AND ((IIf(([Calldetails].[Spanish]), 'Spanish', '')) = 'CSB/VES/Govt') 
  AND ((cd.Spanish)=True) AND ((cd.Omit)=False));

VBA

Below reads in .sql file, explicitly qualify workbook objects without Active calls, and incorporates error handling. Please adjust file paths and worksheet numbers accordingly.

Sub BuildQueryTable()
On Error GoTo ErrHandle
    Dim strSQL As String
    
    ' READ .SQL INTO A STRING VARIABLE
    With CreateObject("Scripting.FileSystemObject")
          strSQL = .OpenTextFile("C:\Path\To\My\Query.sql", 1).readall
    End With

    ' CREATE LIST OBJECT
    ThisWorkbook.Worksheets(1).Columns("A:Z").Delete
    
    With ThisWorkbook.Worksheets(1).ListObjects.Add(SourceType:=0, Source:=Array( _
            "ODBC;Driver={Microsoft Access Driver (*.mdb, *.accdb)};" _
              & "DBQ=C:\Path\To\My\Database.accdb"), _
            Destination:=ThisWorkbook.Worksheets(1).Range("A1")).QueryTable
        .CommandType = xlCmdSql
        .CommandText = strSQL
        .RowNumbers = False
        .FillAdjacentFormulas = False
        .PreserveFormatting = True
        .RefreshOnFileOpen = False
        .BackgroundQuery = True
        .RefreshStyle = xlInsertDeleteCells
        .SavePassword = False
        .SaveData = True
        .AdjustColumnWidth = True
        .RefreshPeriod = 0
        .PreserveColumnInfo = True

        ' ADD 1ST PARAMETER AND SET VALUE
        With .Parameters.Add("prmDate1", xlParamTypeDate)
           .SetParam xlConstant, CDate("8/1/2021")
        End With

        ' ADD 2ND PARAMETER AND SET VALUE
        With .Parameters.Add("prmDate2", xlParamTypeDate)
           .SetParam xlConstant, CDate("8/1/2021")
        End With

        'REFRESH QUERY TABLE
        .Refresh BackgroundQuery:=False
    End With
    
ExitHandle:
    Exit Sub
    
ErrHandle:
    MsgBox Err.Number & " - " & Err.Description, vbCritical
    Resume ExitHandle
End Sub
Parfait
  • 104,375
  • 17
  • 94
  • 125
  • Okay gotcha, I think I understand enough to google more efficiently! I'll have to give this a go on Monday and let you know how it goes. Thank you so so much for taking the time to lay this out for me, and adding some error handling. Have a good weekend, thank you again for your help! – Ace_Q Oct 14 '22 at 20:21
  • Hey @Parfait , I've been running into some errors. I'm using your code as is and I'm getting 1004 - The worksheet data for a table needs to be on the same sheet as the table. – Ace_Q Oct 17 '22 at 20:08
  • Adjust the ranges to fit your needs. This solution explicitly uses `ThisWorkbook.Worksheets(1)`. – Parfait Oct 17 '22 at 20:12
  • It's giving me that error regardless of where I se the range :/ – Ace_Q Oct 17 '22 at 20:15
  • Okay, I've got both the ranges and the file path hammered out it seems, but I've now been getting hit with "general errors" – Ace_Q Oct 17 '22 at 20:26
  • Hmmm...no issue on my end after re-checking. And thanks for question as I never parameterized a query table before! Some suggestions: make sure the delete columns, `A:Z`, line fits to your data. Otherwise, re-try in a blank new workbook and run code in the ThisWorkbook or standard module. Add `Option Explicit` at very top and set it under Tools \ Options \ Require Variable Declaration (best practice in VBA). And always run Debug\Compile after code changes. – Parfait Oct 17 '22 at 20:27