sqlvbaadodb

SQL query works in studio manager but throws error when used by ADODB in VBA


I've been using ADODB in (Excel) VBA to pull SQL data as required. All of it has been running without an issue, including a case where I needed to pull the data into a temporary table to make some calculations in the final output simpler (all in a single SQL query).

However, I recently had to make a change to that particular query, and now it doesn't seem to return anything and so Excel throws an 3704 error stating "Operation is not allowed when the object is closed". From my research I see this often occurs when using temporary tables with ADODB in VBA, but as I've successfully done just that previously I'm extremely confused as to what's changed to cause the issue. (For reference I've used debug.print to get the query produced in Excel and it successfully executes when pasted into Server Management Studio).

I've not been using SQL long so I'm sure there's plenty I'm not doing 'right', but this is the first issue I've been unable to resolve myself (with the help of Dr. Google). I also appretiate that without the databases it's not easy for others to error-check the code, so I'm willing to try any/all thoughts/ideas/suggestions to try and get this to work! (Also, just let me know if I've missed any useful information, etc.)

SQL code as output by using debug.print on the query produced in VBA (for easier reading):

SET NOCOUNT ON

Declare @End_Date Datetime = '11-Jun-2023'
Declare @Start_Date Datetime = '01-May-2023'

Declare @Mon_Count Int = datediff( day, -7, @End_Date ) / 7 - datediff( day, -6, @Start_Date) / 7
Declare @Tue_Count Int = datediff( day, -6, @End_Date ) / 7 - datediff( day, -5, @Start_Date ) / 7
Declare @Wed_Count Int = datediff( day, -5, @End_Date ) / 7 - datediff( day, -4, @Start_Date ) / 7
Declare @Thu_Count Int = datediff( day, -4, @End_Date ) / 7 - datediff( day, -3, @Start_Date ) / 7
Declare @Fri_Count Int = datediff( day, -3, @End_Date ) / 7 - datediff( day, -2, @Start_Date ) / 7
Declare @Sat_Count Int = datediff( day, -2, @End_Date ) / 7 - datediff( day, -1, @Start_Date ) / 7
Declare @Sun_Count Int = datediff( day, -1, @End_Date ) / 7 - datediff( day, -0, @Start_Date ) / 7

Select

    CAST( tk.ref As Int ) As 'Ref Num'
    ,MIN( ls.subdt ) As 'First Log Sub of Period'
    ,SUM( Case When tl_part.wtype in ('L', 'LH') Then tl_part.lhrs Else 0 End ) as 'Standard Hours'
    ,Sum( tl_part.lhrs ) as 'Total Worked Hours'

    ,ISNULL( ( ( cth.cmonhrs *  @Mon_Count ) +
               ( cth.ctueshrs * @Tue_Count ) +
               ( cth.cwedshrs * @Wed_Count ) +
               ( cth.cthurshrs * @Thu_Count ) +
               ( cth.cfrihrs * @Fri_Count) +
               ( cth.csathrs * @Sat_Count ) +
               ( cth.csunhrs * @Sun_Count )
               ), 0 ) as 'Contract Hours'

    ,( Select
            Count (ph.phdate)
         From
            publichols As ph
         Where
            ph.area = tk.tkarea
            and ph.phdate between @Start_Date and @End_Date
        ) *
        ( ISNULL( ( cth.cmonhrs +
                    cth.ctueshrs +
                    cth.cwedshrs +
                    cth.cthurshrs +
                    cth.cfrihrs +
                    cth.csathrs +
                    cth.csunhrs
                  ), 0 ) / 5) As 'Public Holidays'

Into
    #Temp_Table_1

From
    trucks As tk
    left join timelogs As tl_full On tl_full.ref = tk.ref
                                     and tl_full.wtype in ( 'L', 'LH', 'O' )
    left join timelogs as tl_part on tl_part.tindex = tl_full.tindex
                                     and tl_part.tworkdt > @Start_Date
    left join logsubs As ls On ls.logref = tl_full.logref
    left join contract As ct On ct.ref = tk.ref
                                 and @Start_Date between ct.stadt and ct.enddt
    left join contracthours As cth On cth.ctrref = ct.ctrref

Where
    tk.tkincl = 'Y'
    and ls.subdt between @Start_Date and @End_Date

Group By
    tk.ref
    ,tk.tkarea
    ,cth.cmonhrs
    ,cth.ctueshrs
    ,cth.cwedshrs
    ,cth.cthurshrs
    ,cth.cfrihrs
    ,cth.csathrs
    ,cth.csunhrs

Order By
    Min (ls.subdt)
    ,CAST( tk.ref As Int )

Select
    [Ref Num]
    ,[Standard Hours] / Case
                               When ([Total Worked Hours] > ([Contract Hours] - [Public Holidays]))
                               Then [Total Worked Hours]
                               Else ([Contract Hours]-[Public Holidays])
                               End as 'Monitor Ratio'

From
    #Temp_Table_1

Order By
    [First Log Sub of Period]
    ,[Ref Num]

Drop Table #Temp_Table_1

What I believe should be the relevant VBA parts (the debug.print DB_RS.EOF is a stand-in for where the RS would actually be used, but it's enough to throw the error).:

Sub Demo()
    
    Dim Query As String
    Query = Issue_Query
    
    Dim RS As ADODB.Recordset
    Set RS = Run_SQL_Query(DB_Query:=Query)
    
    Debug.Print RS.EOF
    

End Sub

Function Run_SQL_Query(ByVal DB_Query As String) As ADODB.Recordset

    Dim DB_Conn As ADODB.Connection
    Set DB_Conn = New ADODB.Connection

    DB_Conn.Open "Provider=SQLNCLI11;" & _
                 "Data Source=OurDB;" & _
                 "Initial Catalog=son_db_unicode;" & _
                 "Integrated Security=SSPI;"
    
    
    Dim DB_RS As New ADODB.Recordset
    DB_RS.CursorLocation = adUseClient
    DB_RS.Open DB_Query, DB_Conn, adOpenStatic


    Set Run_SQL_Query = DB_RS
    
    
End Function

Function Issue_Query() As String

    Dim Query As String
    
    Query = "SET NOCOUNT ON" & vbNewLine _
            & vbNewLine _
            & "Declare @End_Date Datetime = '11-Jun-2023'" & vbNewLine _
            & "Declare @Start_Date Datetime = '01-May-2023'" & vbNewLine _
            & vbNewLine _
            & "Declare @Mon_Count Int = datediff( day, -7, @End_Date ) / 7 - datediff( day, -6, @Start_Date) / 7" & vbNewLine _
            & "Declare @Tue_Count Int = datediff( day, -6, @End_Date ) / 7 - datediff( day, -5, @Start_Date ) / 7" & vbNewLine _
            & "Declare @Wed_Count Int = datediff( day, -5, @End_Date ) / 7 - datediff( day, -4, @Start_Date ) / 7" & vbNewLine _
            & "Declare @Thu_Count Int = datediff( day, -4, @End_Date ) / 7 - datediff( day, -3, @Start_Date ) / 7" & vbNewLine _
            & "Declare @Fri_Count Int = datediff( day, -3, @End_Date ) / 7 - datediff( day, -2, @Start_Date ) / 7" & vbNewLine _
            & "Declare @Sat_Count Int = datediff( day, -2, @End_Date ) / 7 - datediff( day, -1, @Start_Date ) / 7" & vbNewLine _
            & "Declare @Sun_Count Int = datediff( day, -1, @End_Date ) / 7 - datediff( day, -0, @Start_Date ) / 7" & vbNewLine _
            & vbNewLine _
            & "Select" & vbNewLine _
            & vbNewLine _
            & "    CAST( tk.ref As Int ) As 'Ref Num'" & vbNewLine _
            & "    ,MIN( ls.subdt ) As 'First Log Sub of Period'" & vbNewLine _
            & "    ,SUM( Case When tl_part.wtype in ('L', 'LH') Then tl_part.lhrs Else 0 End ) as 'Standard Hours'" & vbNewLine
            
    Query = Query _
            & "    ,Sum( tl_part.lhrs ) as 'Total Worked Hours'" & vbNewLine _
            & vbNewLine _
            & "    ,ISNULL( ( ( cth.cmonhrs *  @Mon_Count ) +" & vbNewLine _
            & "               ( cth.ctueshrs * @Tue_Count ) +" & vbNewLine _
            & "               ( cth.cwedshrs * @Wed_Count ) +" & vbNewLine _
            & "               ( cth.cthurshrs * @Thu_Count ) +" & vbNewLine _
            & "               ( cth.cfrihrs * @Fri_Count) +" & vbNewLine _
            & "               ( cth.csathrs * @Sat_Count ) +" & vbNewLine _
            & "               ( cth.csunhrs * @Sun_Count )" & vbNewLine _
            & "               ), 0 ) as 'Contract Hours'" & vbNewLine _
            & vbNewLine
            
    Query = Query _
            & "    ,( Select" & vbNewLine _
            & "            Count (ph.phdate)" & vbNewLine _
            & "         From" & vbNewLine _
            & "            publichols As ph" & vbNewLine _
            & "         Where" & vbNewLine _
            & "            ph.area = tk.tkarea" & vbNewLine _
            & "            and ph.phdate between @Start_Date and @End_Date" & vbNewLine _
            & "        ) *" & vbNewLine _
            & "        ( ISNULL( ( cth.cmonhrs +" & vbNewLine _
            & "                    cth.ctueshrs +" & vbNewLine _
            & "                    cth.cwedshrs +" & vbNewLine _
            & "                    cth.cthurshrs +" & vbNewLine _
            & "                    cth.cfrihrs +" & vbNewLine _
            & "                    cth.csathrs +" & vbNewLine _
            & "                    cth.csunhrs" & vbNewLine _
            & "                  ), 0 ) / 5) As 'Public Holidays'" & vbNewLine
            
    Query = Query _
            & vbNewLine _
            & "Into" & vbNewLine _
            & "    #Temp_Table_1" & vbNewLine _
            & vbNewLine _
            & "From" & vbNewLine _
            & "    trucks As tk" & vbNewLine _
            & "    left join timelogs As tl_full On tl_full.ref = tk.ref" & vbNewLine _
            & "                                     and tl_full.wtype in ( 'L', 'LH', 'O' )" & vbNewLine _
            & "    left join timelogs as tl_part on tl_part.tindex = tl_full.tindex" & vbNewLine _
            & "                                     and tl_part.tworkdt > @Start_Date" & vbNewLine _
            & "    left join logsubs As ls On ls.logref = tl_full.logref" & vbNewLine _
            & "    left join contract As ct On ct.ref = tk.ref" & vbNewLine _
            & "                                 and @Start_Date between ct.stadt and ct.enddt" & vbNewLine _
            & "    left join contracthours As cth On cth.ctrref = ct.ctrref" & vbNewLine _
            & vbNewLine
            

            
    Query = Query _
            & "Where" & vbNewLine _
            & "    tk.tkincl = 'Y'" & vbNewLine _
            & "    and ls.subdt between @Start_Date and @End_Date" & vbNewLine _
            & vbNewLine _
            & "Group By" & vbNewLine _
            & "    tk.ref" & vbNewLine _
            & "    ,tk.tkarea" & vbNewLine _
            & "    ,cth.cmonhrs" & vbNewLine _
            & "    ,cth.ctueshrs" & vbNewLine _
            & "    ,cth.cwedshrs" & vbNewLine _
            & "    ,cth.cthurshrs" & vbNewLine _
            & "    ,cth.cfrihrs" & vbNewLine _
            & "    ,cth.csathrs" & vbNewLine _
            & "    ,cth.csunhrs" & vbNewLine _
            & vbNewLine _
            & "Order By" & vbNewLine _
            & "    Min (ls.subdt)" & vbNewLine _
            & "    ,CAST( tk.ref As Int )" & vbNewLine _
            & vbNewLine

    Query = Query _
            & "Select" & vbNewLine _
            & "    [Ref Num]" & vbNewLine _
            & "    ,[Standard Hours] / Case" & vbNewLine _
            & "                               When ([Total Worked Hours] > ([Contract Hours] - [Public Holidays]))" & vbNewLine _
            & "                               Then [Total Worked Hours]" & vbNewLine _
            & "                               Else ([Contract Hours]-[Public Holidays])" & vbNewLine _
            & "                               End as 'Monitor Ratio'" & vbNewLine _
            & vbNewLine _
            & "From" & vbNewLine _
            & "    #Temp_Table_1" & vbNewLine _
            & vbNewLine _
            & "Order By" & vbNewLine _
            & "    [First Log Sub of Period]" & vbNewLine _
            & "    ,[Ref Num]" & vbNewLine _
            & vbNewLine _
            & "Drop Table #Temp_Table_1"
            
    Issue_Query = Query

    
End Function

Edited: I've added the old queries (in both VBA and SQL formats) below.

Old Query - SQL Format:

SET NOCOUNT ON

Declare @End_Date Datetime = '14-Jun-2023'
Declare @Start_Date Datetime = '01-May-2023'

Declare @Mon_Count Int = datediff( day, -7, @End_Date ) / 7 - datediff( day, -6, @Start_Date) / 7
Declare @Tue_Count Int = datediff( day, -6, @End_Date ) / 7 - datediff( day, -5, @Start_Date ) / 7
Declare @Wed_Count Int = datediff( day, -5, @End_Date ) / 7 - datediff( day, -4, @Start_Date ) / 7
Declare @Thu_Count Int = datediff( day, -4, @End_Date ) / 7 - datediff( day, -3, @Start_Date ) / 7
Declare @Fri_Count Int = datediff( day, -3, @End_Date ) / 7 - datediff( day, -2, @Start_Date ) / 7
Declare @Sat_Count Int = datediff( day, -2, @End_Date ) / 7 - datediff( day, -1, @Start_Date ) / 7
Declare @Sun_Count Int = datediff( day, -1, @End_Date ) / 7 - datediff( day, -0, @Start_Date ) / 7

Select

    CAST( tk.ref As Int ) As 'Ref Num'
    ,MIN( ls.subdt ) As 'First Log Sub of Period'
    ,SUM( Case When tl.wtype in ('L', 'LH') Then tl.lhrs Else 0 End ) as 'Standard Hours'
    ,Sum(tl.lhrs) as 'Total Worked Hours'

    ,ISNULL( ( ( cth.cmonhrs *  @Mon_Count ) +
               ( cth.ctueshrs * @Tue_Count ) +
               ( cth.cwedshrs * @Wed_Count ) +
               ( cth.cthurshrs * @Thu_Count ) +
               ( cth.cfrihrs * @Fri_Count) +
               ( cth.csathrs * @Sat_Count ) +
               ( cth.csunhrs * @Sun_Count )
               ), 0 ) as 'Contract Hours'

    ,( Select
            Count (ph.phdate)
         From
            publichols As ph
         Where
            ph.area = tk.tkarea
            and ph.phdate between @Start_Date and @End_Date
        ) *
        ( ISNULL( ( cth.cmonhrs +
                    cth.ctueshrs +
                    cth.cwedshrs +
                    cth.cthurshrs +
                    cth.cfrihrs +
                    cth.csathrs +
                    cth.csunhrs
                  ), 0 ) / 5) As 'Public Holidays'

Into
    #Temp_Table_1

From
    trucks As tk
    left join timelogs As tl On tk.ref = tl.ref
    left join logsubs As ls On ls.logref = tl.logref
    left join contract As ct On ct.ref = tk.ref
                                 and @Start_Date between ct.stadt and ct.enddt
    left join contracthours As cth On cth.ctrref = ct.ctrref
Where
    tk.tkincl = 'Y'
    and tl.wtype in ('L','LH','O')
    and ls.subdt between @Start_Date and @End_Date

Group By
    tk.ref
    ,tk.tkarea
    ,cth.cmonhrs
    ,cth.ctueshrs
    ,cth.cwedshrs
    ,cth.cthurshrs
    ,cth.cfrihrs
    ,cth.csathrs
    ,cth.csunhrs

Order By
    Min (ls.subdt)
    ,CAST( tk.ref As Int )

Select
    [Ref Num]
    ,[Standard Hours] / Case
                               When ([Total Worked Hours] > ([Contract Hours] - [Public Holidays]))
                               Then [Total Worked Hours]
                               Else ([Contract Hours]-[Public Holidays])
                               End as 'Monitor Ratio'

From
    #Temp_Table_1

Order By
    [First Log Sub of Period]
    ,[Ref Num]

Drop Table #Temp_Table_1

Old Query - VBA Format:

Function Issue_Old_Query() As String

    Dim Query As String
    
    Query = "SET NOCOUNT ON" & vbNewLine _
            & vbNewLine _
            & "Declare @End_Date Datetime = '11-Jun-2023'" & vbNewLine _
            & "Declare @Start_Date Datetime = '01-May-2023'" & vbNewLine _
            & vbNewLine _
            & "Declare @Mon_Count Int = datediff( day, -7, @End_Date ) / 7 - datediff( day, -6, @Start_Date) / 7" & vbNewLine _
            & "Declare @Tue_Count Int = datediff( day, -6, @End_Date ) / 7 - datediff( day, -5, @Start_Date ) / 7" & vbNewLine _
            & "Declare @Wed_Count Int = datediff( day, -5, @End_Date ) / 7 - datediff( day, -4, @Start_Date ) / 7" & vbNewLine _
            & "Declare @Thu_Count Int = datediff( day, -4, @End_Date ) / 7 - datediff( day, -3, @Start_Date ) / 7" & vbNewLine _
            & "Declare @Fri_Count Int = datediff( day, -3, @End_Date ) / 7 - datediff( day, -2, @Start_Date ) / 7" & vbNewLine _
            & "Declare @Sat_Count Int = datediff( day, -2, @End_Date ) / 7 - datediff( day, -1, @Start_Date ) / 7" & vbNewLine _
            & "Declare @Sun_Count Int = datediff( day, -1, @End_Date ) / 7 - datediff( day, -0, @Start_Date ) / 7" & vbNewLine _
            & vbNewLine _
            
    Query = Query _
            & "Select" & vbNewLine _
            & vbNewLine _
            & "    CAST( tk.ref As Int ) As 'TKID'" & vbNewLine _
            & "    ,MIN( ls.subdt ) As 'First Log Sub of Period'" & vbNewLine _
            & "    ,SUM( Case When tl.wtype in ('L', 'LH') Then tl.lhrs Else 0 End ) as 'Standard Hours'" & vbNewLine _
            & "    ,Sum(tl.lhrs) as 'Total Worked Hours'" & vbNewLine _
            & vbNewLine
    
    Query = Query _
            & "    ,ISNULL( ( ( cth.cmonhrs *  @Mon_Count ) +" & vbNewLine _
            & "               ( cth.ctueshrs * @Tue_Count ) +" & vbNewLine _
            & "               ( cth.cwedshrs * @Wed_Count ) +" & vbNewLine _
            & "               ( cth.cthurshrs * @Thu_Count ) +" & vbNewLine _
            & "               ( cth.cfrihrs * @Fri_Count) +" & vbNewLine _
            & "               ( cth.csathrs * @Sat_Count ) +" & vbNewLine _
            & "               ( cth.csunhrs * @Sun_Count )" & vbNewLine _
            & "               ), 0 ) as 'Contract Hours'" & vbNewLine _
            & vbNewLine
        
    Query = Query _
            & "    ,( Select" & vbNewLine _
            & "            Count (ph.phdate)" & vbNewLine _
            & "         From" & vbNewLine _
            & "            publichols As ph" & vbNewLine _
            & "         Where" & vbNewLine _
            & "            ph.area = tk.tkarea" & vbNewLine _
            & "            and ph.phdate between @Start_Date and @End_Date" & vbNewLine _
            & "        ) *" & vbNewLine _
            & "        ( ISNULL( ( cth.cmonhrs +" & vbNewLine _
            & "                    cth.ctueshrs +" & vbNewLine _
            & "                    cth.cwedshrs +" & vbNewLine _
            & "                    cth.cthurshrs +" & vbNewLine _
            & "                    cth.cfrihrs +" & vbNewLine _
            & "                    cth.csathrs +" & vbNewLine _
            & "                    cth.csunhrs" & vbNewLine _
            & "                  ), 0 ) / 5) As 'Public Holidays'" & vbNewLine _
            & vbNewLine
            
    Query = Query _
            & "Into" & vbNewLine _
            & "    #Temp_Table_1" & vbNewLine _
            & vbNewLine _
            & "From" & vbNewLine _
            & "    trucks As tk" & vbNewLine _
            & "    left join timelogs As tl On tk.ref = tl.ref" & vbNewLine _
            & "    left join logsubs As ls On ls.logref = tl.logref" & vbNewLine _
            & "    left join contract As ct On ct.ref = tk.ref" & vbNewLine _
            & "                                 and @Start_Date between ct.stadt and ct.enddt" & vbNewLine _
            & "    left join contracthours As cth On cth.ctrref = ct.ctrref" & vbNewLine
                        
                        
    Query = Query _
            & "Where" & vbNewLine _
            & "    tk.tkincl = 'Y'" & vbNewLine _
            & "    and tl.wtype in ('L','LH','O')" & vbNewLine _
            & "    and ls.subdt between @Start_Date and @End_Date" & vbNewLine _
            & vbNewLine
            
    Query = Query _
            & "Group By" & vbNewLine _
            & "    tk.ref" & vbNewLine _
            & "    ,tk.tkarea" & vbNewLine _
            & "    ,cth.cmonhrs" & vbNewLine _
            & "    ,cth.ctueshrs" & vbNewLine _
            & "    ,cth.cwedshrs" & vbNewLine _
            & "    ,cth.cthurshrs" & vbNewLine _
            & "    ,cth.cfrihrs" & vbNewLine _
            & "    ,cth.csathrs" & vbNewLine _
            & "    ,cth.csunhrs" & vbNewLine _
            & vbNewLine
            
    Query = Query _
            & "Order By" & vbNewLine _
            & "    Min (ls.subdt)" & vbNewLine _
            & "    ,CAST( tk.ref As Int )" & vbNewLine _
            & vbNewLine _
            & "Select" & vbNewLine _
            & "    TKID" & vbNewLine _
            & "    ,[Standard Hours] / Case" & vbNewLine _
            & "                               When ([Total Worked Hours] > ([Contract Hours] - [Public Holidays]))" & vbNewLine _
            & "                               Then [Total Worked Hours]" & vbNewLine _
            & "                               Else ([Contract Hours]-[Public Holidays])" & vbNewLine _
            & "                               End as 'Monitor Ratio'" & vbNewLine _
            & vbNewLine

    Query = Query _
            & "From" & vbNewLine _
            & "    #Temp_Table_1" & vbNewLine _
            & vbNewLine _
            & "Order By" & vbNewLine _
            & "    [First Log Sub of Period]" & vbNewLine _
            & "    ,[Ref Num]" & vbNewLine _
            & vbNewLine _
            & "Drop Table #Temp_Table_1"
            
    Issue_Old_Query = Query


End Function

Solution

  • So the issue was the line Sum(tl_part.lhrs) as 'Total Worked Hours' (found by tweaking the original SQL script one line at a time until it triggered the error in VBA, and then doing further checks to confirm it was just this line and not a combination of issues).

    In Microsoft SQL Server Management Studio I then ran the script for just the first half of the script (all the bits prior to the second Select) without the results being displayed I realised there was a warning being generated; Null value is eliminated by an aggregate or other SET operation. Removing groupings, etc. confirmed there were some unexpected (or rather, overlooked) NULL values in the tl_part.lhrs data.

    It seems SSMS just treats these NULL values as zeros when summing, but for some reason that I'm not entirely sure on, when running the script via VBA it causes it to error out (my guess would be that the warning half-way through the script 'breaks' the VBA call, ultimately causing it to close the dataset; but that is just a guess).

    Simply updating the issue line to Sum( IsNull( tl_part.lhrs, 0 ) ) as 'Total Worked Hours' resolves the issue.

    TL;DR: The left join of tl_part causes there to be NULL values, these trigger a warning in SMSS when being summed but cause VBA calls to error. I was an idiot and didn't notice the warning message in SMSS as it ran successfully, and I didn't even consider the NULLs caused by the left join.