I am using VBA to refresh a Data Connection in an Excel workbook with a string variable being used for the Query to run.
When using a SELECT query this all works as it should; if it is an INSERT query, it throws an application-defined or object-defined error on Connections(ConnectionName).Refresh
The error does not repeat itself if I run the code to the point where it adds the Query string into the Data Connection and manually click the Refresh button in Excel.
Sub UploadData()
Dim wb As Workbook
Dim UploadSheetNum As Integer
Dim QueryCol As String
Dim QueryString As String
Dim CurRowString As String
Dim ConnName As String
Set wb = ThisWorkbook
UploadSheetNum = 3
QueryCol = "H"
ConnName = "DataConn"
Call VBAModule_v1.SwitchtoSheet(UploadSheetNum, wb)
For i = 1 To VBAModule_v1.GetLastRow(QueryCol)
CurRowString = wb.Sheets(UploadSheetNum).Range(QueryCol & i)
QueryString = QueryString & CurRowString & Chr(10)
Next i
Call VBAModule.RefreshConnection(ConnName, QueryString, wb)
End Sub
Sub RefreshConnection(ConnectionName As String, Query As String, wb As Workbook)
wb.Activate
On Error GoTo ExitProc
With wb.Connections(ConnectionName).ODBCConnection
.BackgroundQuery = False
.CommandText = Query
End With
wb.Connections(ConnectionName).Refresh
DoEvents
Exit Sub
ExitProc:
MsgBox ("Error Sub RefreshConnection: Issue with ConnectionName '" & _
ConnectionName & "' or Query - " & Err.Description)
End Sub
Actually just tripped over the answer. So, WorkbookConnection Object works for queries that return a result e.g Select queries, but not so much for queries that don't e.g. Insert, Update, Deletes.
The below subroutine will run an Insert query without error:
Sub RunInsertQuery(InsertTable As String, InsertValues As String)
On Error GoTo ExitProc
Dim Dataconn As Object
Set Dataconn = CreateObject("ADODB.Connection")
connstr = "DRIVER={'SQL DRIVER To Use'};" & _
"SERVER='SERVERNAME to Connect to';" & _
"PORT='PortNumber'" & _
"DATABASE='Database Name';" & _
"UID='User Id to use';" & _
"PWD='User Password';"
Dataconn.Open connstr
Dataconn.Execute "INSERT INTO " & InsertTable & " VALUES " & InsertValues
Dataconn.Close
Set Dataconn = Nothing
Exit Sub
ExitProc:
MsgBox ("Error Sub RunInsertQuery: Issue with Tablename for Insert '" & InsertTable & "' or Values being Inserted - " & Err.Description)
End Sub
Related
I have a templated Excel file that will be is used to save the cell values to SQL. There will be about a thousand of these all with different names that perform the same function. For this reason I wanted to remove my code from the template into another file allowing for global changes if needed.
The User works from File A and hits the save button executing the following code to run the Macro contained in File B.
Sub Save_Inspection()
Dim wb As Workbook
On Error Resume Next
Set wb = Workbooks("SaveInspectionData.xlsm")
On Error GoTo 0
If wb Is Nothing Then Set wb = Workbooks.Open("\\SERVER\FOLDER\Files\XDomainDocs\SaveInspectionData.xlsm")
Dim FileName As String
FileName = ThisWorkbook.Name
Run "SaveInspectionData.xlsm!sheet1.Save_Inspection", FileName
wb.Close False
Set wb = Nothing
End Sub
Below is the code used to save the data residing on File B. Note - it is undermentioned at this time how many rows or columns there will, so I am looping through to create the SQL query and qty of rows, columns etc. This all works fine.
My problem is when I try to close the workbooks. I want them both to close and regardless or what I try only one of the two will close. The code below reflects the simplest close method, but I have tried several other techniques.
After some searching it may be something to do with my use of "With" statements to reference File A, but I am not sure.
Thanks in advance!
Sub Save_Inspection(FileName As String)
On Error GoTo errH
Dim strUserDomain As String
Dim cn As ADODB.Connection
Dim Server_Name As String
Dim Database_Name As String
Dim User_ID As String
Dim Password As String
Dim InspectionId As Integer 'Will use this Id to associate all results to this Inspection Instance
Dim Query As String
Dim rs As ADODB.Recordset
Set rs = New ADODB.Recordset
'Find proper connection string
strUserDomain = Environ$("UserDomain")
`If strUserDomain = "A" Then
Server_Name = "ServerA"
Database_Name = "Inspection"
User_ID = "xxx"
Password = "xxx"
ElseIf strUserDomain = "B" Then
Server_Name = "ServerB"
Database_Name = "Inspection"
User_ID = "xxx"
Password = "xxx"
ElseIf strUserDomain = "C" Then
Server_Name = "ServerC"
Database_Name = "Inspection"
User_ID = "xxx"
Password = "xxx"
Else
'Something must be wrong
Exit Sub
End If
Workbooks(FileName).Activate
Dim wb As Workbook
Dim ws As Worksheet
Set wb = Workbooks(FileName)
Set ws = wb.Sheets(1)
'Let's Save this stuff!
Dim DateInspected, PartNumber, LotNumber, Revision As String
'Set values
With ws
'DateInspected = .Range("Q5").Value
PartNumber = .Range("K4").Value
LotNumber = .Range("G3").Value
Revision = .Range("Q4").Value
End With
Query = "INSERT INTO InspectionCatalog (DateInspected, PartNumber, LotNumber, Revision) VALUES (GETDATE(), '" & PartNumber & "', '" & LotNumber & "', '" & Revision & "')"
Set cn = New ADODB.Connection
cn.Open "Provider=SQLOLEDB;Server=" & Server_Name & ";Initial Catalog=" & Database_Name & ";Uid=" & User_ID & ";Pwd=" & Password & ";"
cn.Execute (Query)
rs.Open "SELECT ##identity AS InspectionId", cn
InspectionId = rs.Fields("InspectionId")
'MsgBox (InspectionId)'For testing
'Loop through all cells on sheet and save results
Call LoopThroughResults(InspectionId, FileName, strUserDomain)
Exit Sub
errH:
MsgBox Err.Description
End Sub
Sub LoopThroughResults(InspectionId As Integer, FileName As String, strUserDomain As String)
On Error GoTo errH
'Declare Variables
Dim RowCount As Integer
Dim CollCount As Integer
Dim Coll_Count As Integer
Dim wb As Workbook
Dim ws As Worksheet
Set wb = Workbooks(FileName)
Set ws = wb.Sheets(1)
'Find the number of rows in the sheet based on a value in Col U
With ws
RowCount = .Cells(.Rows.Count, "G").End(xlUp).Row
'MsgBox RowCount
End With
'Go through each row and find the number of columns that are filled
'Set CollCount to the longest row - ignoring 1-9 these are header fields
For i = 10 To RowCount
With ws
Coll_Count = .Cells(i, .Columns.Count).End(xlToLeft).Column
If Coll_Count > CollCount Then
'Find the length of the longest row
CollCount = Coll_Count
End If
'MsgBox "Row " & i & " Has " & Coll_Count & " Columns!"
End With
Next i
'MsgBox "The Row with the Most data has " & CollCount & " Columns!"
'Save Col Count to be used for retrieving the data later
Dim Query As String
Query = "UPDATE InspectionCatalog SET CollCount='" & CollCount & "', [RowCount]='" & RowCount & "' WHERE InspectionId='" & InspectionId & "' "
Call SaveResults(Query, strUserDomain)
Dim QueryStart As String
Dim QueryEnd As String
'Loop through each row starting at 2 (Not 10, this time we want to capture all data
For i = 2 To RowCount
'Reset Query String befor hitting next row
QueryStart = "INSERT INTO InspectionResults ("
QueryEnd = " VALUES ("
'Loop through each column to create insert query
For n = 1 To CollCount
QueryStart = QueryStart & "Col" & n & ","
QueryEnd = QueryEnd & "N'" & Workbooks(FileName).Worksheets("Inspection Report").Cells(i, n).Value & "',"
Next n
QueryStart = QueryStart & "InspectionId)"
QueryEnd = QueryEnd & "'" & InspectionId & "');"
'MsgBox QueryStart & QueryEnd
Call SaveResults(QueryStart & QueryEnd, strUserDomain)
Next i
MsgBox "Inspection Data Has Been Saved"
Call CloseWorkBooks(FileName)
Exit Sub
errH:
MsgBox Err.Description
End Sub
Sub SaveResults(Query As String, strUserDomain As String)
On Error GoTo errH
Dim cn As ADODB.Connection
Dim Server_Name As String
Dim Database_Name As String
Dim User_ID As String
Dim Password As String
Dim rs As ADODB.Recordset
Set rs = New ADODB.Recordset
'Find proper connection string
strUserDomain = Environ$("UserDomain")
If strUserDomain = "A" Then
Server_Name = "ServerA"
Database_Name = "Inspection"
User_ID = "xxx"
Password = "xxx"
ElseIf strUserDomain = "B" Then
Server_Name = "ServerB"
Database_Name = "Inspection"
User_ID = "xxx"
Password = "xxx"
ElseIf strUserDomain = "C" Then
Server_Name = "ServerC"
Database_Name = "Inspection"
User_ID = "xxx"
Password = "xxx"
Else
'Something must be wrong
Exit Sub
End If
Set cn = New ADODB.Connection
cn.Open "Provider=SQLOLEDB;Server=" & Server_Name & ";Initial Catalog=" & Database_Name & ";Uid=" & User_ID & ";Pwd=" & Password & ";"
cn.Execute (Query)
Exit Sub
errH:
MsgBox Err.Description
End Sub
Sub CloseWorkBooks(FileName As String)
Workbooks(FileName).Close SaveChanges:=False
Workbooks("SaveInspectionData.xlsm").Close SaveChanges:=False
Exit Sub
End Sub
Note that Application.Run executes the code in the same 'environment' as the current workbook. Basically the workbook executing Application.Run is the one running the code, and the new workbook will be linked to the same session.
This will result in the peculiar situation that you are observing.
Closing the workbook executed by 'Run' will make any macro (sub, function, object, sheet) in this workbook go out of scope, and any code run will stop running in this workbook. In addition as the notebook was closed, the code will not 'finish' in the executed workbook and thus we will not return to the original workbook, effectively halting any execution in the original notebook.
Additionaly as the code will try to return to the original workbook, to finish the original running sub (here Save_Inspection() in the original workbook), the two workbooks are linked to the same session (or environment), and thus closing this workbook first will Halt the original code running (going to the next line in Save_Inspection becomes effectively impossible as the workbook is now closed), and this will end the session as well.
Thus closing all workbooks in a workbook opened and executed by Application.Run is not possible directly. Workarounds can be done. The simplest is closing all workbooks in the original workbook (placing wb.close false: Thisworkbook.close false after application.run). Alternatively making a sub in the second workbook that runs 'Application.Ontime' and saves the filename to a cell for use in the function run by 'ontime' should make certain that the two sessions wont be linked while running code in the second notebook. But this i am less certain is actually the case.
Below is the code in the original notebook. If the original workbook finishes this should close the workbooks in the end.
Sub Save_Inspection()
Dim wb As Workbook
On Error Resume Next
Set wb = Workbooks("SaveInspectionData.xlsm")
On Error GoTo 0
If wb Is Nothing Then
Set wb = Workbooks.Open(ThisWorkbook.Path & "SaveInspectionData.xlsm")
End If
Dim FileName As String
FileName = ThisWorkbook.Name
Run "SaveInspectionData.xlsm!sheet1.CloseBooks", FileName
wb.Close False
ThisWorkbook.Close False
Set wb = Nothing
End Sub
Above Oliver does a great job explaining why my procedures where not working properly. In order to fix this I removed the call's to close the workbooks, the code in workbook A handles that on it's own. I did make some small changes to the above code to handle closing Excel or the workbook based on the qty of instances open.
Sub Save_Inspection()
Dim wb As Workbook
Dim wb2 As Workbook
On Error Resume Next
Set wb = Workbooks("SaveInspectionData.xlsm")
On Error GoTo 0
If wb Is Nothing Then Set wb = Workbooks.Open("\\Server\Cloud9\Files\XDomainDocs\SaveInspectionData.xlsm")
Dim FileName As String
FileName = ThisWorkbook.Name
Run "SaveInspectionData.xlsm!sheet1.Save_Inspection", FileName
If Application.Workbooks.Count > 2 Then
wb.Close False
ThisWorkbook.Close False
Set wb = Nothing
Else
Application.Quit
End If
End Sub
I found a Macro to export DAX query into CSV, but I'm having trouble with one of the columns where a cell includes a comma. I'm thinking VB didn't really take into consideration for when a resulted cell includes comma.
What can I change in the VB so that I can replace comma with space before it gets wrriten into CSV file?
Putting this into powerquery isn't really an option, unfortunately...
Option Explicit
Public Sub ExportToCsv()
Dim wbTarget As Workbook
Dim ws As Worksheet
Dim rs As Object
Dim sQuery As String
'Suppress alerts and screen updates
With Application
.ScreenUpdating = False
.DisplayAlerts = False
End With
'Bind to active workbook
Set wbTarget = ActiveWorkbook
Err.Clear
On Error GoTo ErrHandler
'Make sure the model is loaded
wbTarget.Model.Initialize
'Send query to the model
sQuery = "EVALUATE CALCULATETABLE('Query 3')"
Set rs = CreateObject("ADODB.Recordset")
rs.Open sQuery,
wbTarget.Model.DataModelConnection.ModelConnection.ADOConnection
Dim CSVData As String
CSVData = RecordsetToCSV(rs, True)
'Write to file
Open "C:\abc.csv" For Binary Access Write As #1
Put #1, , CSVData
Close #1
rs.Close
Set rs = Nothing
ExitPoint:
With Application
.ScreenUpdating = True
.DisplayAlerts = True
End With
Set rs = Nothing
Exit Sub
ErrHandler:
MsgBox "An error occured - " & Err.Description, vbOKOnly
Resume ExitPoint
End Sub
Public Function RecordsetToCSV(rsData As ADODB.Recordset, _
Optional ShowColumnNames As Boolean = True, _
Optional NULLStr As String = "") As String
'Function returns a string to be saved as .CSV file
'Option: save column titles
Dim K As Long, RetStr As String
If ShowColumnNames Then
For K = 0 To rsData.Fields.Count - 1
RetStr = RetStr & ",""" & rsData.Fields(K).Name & """"
Next K
RetStr = Mid(RetStr, 2) & vbNewLine
End If
RetStr = RetStr & """" & rsData.GetString(adClipString, -1, """,""", """" & vbNewLine & """", NULLStr)
RetStr = Left(RetStr, Len(RetStr) - 3)
RecordsetToCSV = RetStr
End Function
Good Afternoon,
I have created a Macro that uploads data to a access database ( both on my desktop). The problem is it I keep getting errors when I try to expand the range.
I presumed it would be something simple but seems to be something I am overlooking.
here is the code - basically I would like to include the column or set it to a dynamic range? can you please help?
Sub AccessCode()
Application.ScreenUpdating = False
Dim db As Database
Dim rs As DAO.Recordset
Set db = OpenDatabase("C:\Users\user\Desktop\Test Copy.accdb")
Set rs = db.OpenRecordset("Fact Table", dbOpenTable)
rs.AddNew
rs.Fields("GUID") = Range("g2").Value
rs.Fields("StageID") = Range("h2").Value
rs.Fields("Sync Date") = Range("i2").Value
rs.Fields("Forecast HP") = Range("j2").Value
rs.Fields("Owner Id") = Range("k2").Value
rs.Fields("Recent Modified Flag") = Range("L2").Value
rs.Fields("Upload Date") = Range("M2").Value
rs.Update
rs.Close
db.Close
Application.ScreenUpdating = True
MsgBox " Upload To PMO Database Successful."
End Sub
You can use a query instead of iterating through a recordset:
Sub AccessCode()
Application.ScreenUpdating = False
Dim db As Database
Dim rs As DAO.Recordset
Set db = OpenDatabase("C:\Users\user\Desktop\Test Copy.accdb")
db.Execute "INSERT INTO [Fact Table] ([GUID], [StageID], etc) " & _
"SELECT * FROM [SheetName$G:M] " & _
"IN """ & ActiveWorkbook.FullName & """'Excel 12.0 Macro;HDR=No;'"
End Sub
This has numerous advantages, such as often being faster because you don't have to iterate through all the fields.
If you would trigger the import from Access instead of Excel, you wouldn't even need VBA to execute the query.
Change the rs section to this one:
With rs
.addnew
!GUID = Range("g2").Value
!StageID = Range("h2").Value
'...etc
.Update
End With
MSDN source
Use the AddNew method to create and add a new record in the Recordset object named by recordset. This method sets the fields to default values, and if no default values are specified, it sets the fields to Null (the default values specified for a table-type Recordset).
After you modify the new record, use the Update method to save the changes and add the record to the Recordset. No changes occur in the database until you use the Update method.
Edit:
This is how your code should look like, when you change the rs section with the code above:
Sub AccessCode()
Application.ScreenUpdating = False
Dim db As Database
Dim rs As DAO.Recordset
Set db = OpenDatabase("C:\Users\user\Desktop\Test Copy.accdb")
Set rs = db.OpenRecordset("Fact Table", dbOpenTable)
With rs
.addnew
!GUID = Range("g2").Value
!StageID = Range("h2").Value
'...etc
.Update
.Close
End With
Application.ScreenUpdating = True
MsgBox " Upload To PMO Database Successful."
End Sub
Just thought I'd add in an alternative to #Erik von Asmuth's excellent answer. I use something like this in a real project. It's a little more robust for importing a dynamic range.
Public Sub ImportFromWorksheet(sht As Worksheet)
Dim strFile As String, strCon As String
strFile = sht.Parent.FullName
strCon = "Excel 12.0;HDR=Yes;Database=" & strFile
Dim strSql As String, sqlTransferFromExcel As String
Dim row As Long
row = sht.Range("A3").End(xlDown).row
Dim rng As Range
sqlTransferFromExcel = " Insert into YourTable( " & _
" [GUID] " & _
" ,StageID " & _
" ,[sync Date] " & _
" ,[etc...] " & _
" ) " & _
" SELECT [GUID] " & _
" ,StageID " & _
" ,[sync Date] " & _
" ,[etc...] " & _
" FROM [{{connString}}].[{{sheetName}}$G2:M{{lastRow}}]"
sqlTransferFromExcel = Replace(sqlTransferFromExcel, "{{lastRow}}", row)
sqlTransferFromExcel = Replace(sqlTransferFromExcel, "{{connString}}", strCon)
sqlTransferFromExcel = Replace(sqlTransferFromExcel, "{{sheetName}}", sht.Name)
CurrentDb.Execute sqlTransferFromExcel
End Sub
I am having an issue where I create a connection string (Excel) and query a worksheet, I can get the results, placed into a recordset, and then transposted into a destination worksheet.
The problem is that for some reason, if I go back and edit this worksheet (without saving), the recordset is caching the OLD results. eg: I first queried 10 rows, returned 10, deleted 7 of them, execute the query again but it returns the original 10 as opposed to my expectation for the remaining 3. I've used this method thoroughly and have never had this issue and believe it to be memory related somehow...
Please help...
Public Sub sbTest()
Dim wb As Workbook
Dim wsData As Worksheet, _
wsTmp As Worksheet
Set wb = ThisWorkbook
Set wsData = wb.Sheets("Data"): wsData.Cells.ClearContents
Set wsTmp = wb.Sheets("Temporary")
sSQL = "SELECT * FROM [" & wsTmp.Name & "$]"
Call mUtilities.sbRunSQL(sConnXlsm, wb.FullName, sSQL, wsData.Cells(1, 1))
'Cleanup
Set wb = Nothing
Set wsData = Nothing
Set wsTmp = Nothing
End Sub
Public Const sConnXlsm As String = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=zzzzz;Extended Properties=""Excel 12.0 Macro;HDR=YES;IMEX=1"";"
Public Sub sbRunSQL(ByVal sConn As String, ByVal sSource As String, ByVal sSQL As String, ByVal rDest As Range, _
Optional ByVal bHeader As Boolean = True, Optional ByVal bMsg As Boolean = True)
Dim oCn As ADODB.Connection, _
oRs As ADODB.Recordset, _
oFld As ADODB.Field
Dim vArr As Variant
'Setup
On Error GoTo Cleanup
'Handle DELETE and INSERT INTO Access queries seperately from other types
If (UCase(Left(sSQL, 6)) = "DELETE" Or UCase(Left(sSQL, 11)) = "INSERT INTO") And sConn = sConnAccess Then
Set oCn = CreateObject("ADODB.Connection")
oCn.Open Replace(sConn, "zzzzz", sSource)
sSQL = Replace(sSQL, "FROM ", "FROM [Excel 8.0;HDR=YES;DATABASE=" & ThisWorkbook.FullName & "].")
oCn.Execute sSQL
'Exit if successful
oCn.Close
Set oCn = Nothing
Exit Sub
Else
Set oRs = Nothing
Set oRs = New ADODB.Recordset
oRs.Open sSQL, Replace(sConn, "zzzzz", sSource), adOpenForwardOnly, adLockReadOnly
If Not (oRs.BOF And oRs.EOF) Then
vArr = oRs.GetRows
vArr = fTranspose(vArr) 'The .GetRows process tranposes the data so we need to undo this
If bHeader = True Then
For i = 0 To oRs.Fields.Count - 1
rDest.Offset(0, i).Value = oRs.Fields(i).Name
Next i
Range(rDest.Offset(1, 0), rDest.Offset(UBound(vArr, 1) + 1, UBound(vArr, 2))) = vArr
Else
Range(rDest, rDest.Offset(UBound(vArr, 1), UBound(vArr, 2))) = vArr
End If
'Exit if successful
oRs.Close
Set oRs = Nothing
Exit Sub
End If
End If
'Cleanup
Cleanup:
If bMsg = True Then
MsgBox "Critical error!" & vbNewLine & vbNewLine & _
"Error: " & Err.Description & vbNewLine & vbNewLine & _
"SQL: " & sSQL, vbCritical + vbOKOnly
End If
Set oCn = Nothing
Set oRs = Nothing
End Sub
For what it's worth, I was able to solve this and the issue seems to be related to some kind of latency bug if multiple instances of Excel are open. I've simply forced only one book to be open in such cases.
Set oWMI = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
Set oProc = oWMI.ExecQuery("SELECT * FROM Win32_Process WHERE NAME = 'Excel.exe'")
If oProc.Count > 1 Then
MsgBox "There are " & oProc.Count & " instances of Excel open." & vbNewLine & vbNewLine & _
"Only 1 instance is allowed open in order to update database.", vbCritical + vbOKOnly
GoTo Cleanup
End If
I am trying to use a function to look up in an Access table a value from an Excel cell and to return a value from a column in the matched row in the Access table.
I am using the code below, but it just keeps returning #value even though I get an exact match in the Access db.
Dim adoCN As ADODB.Connection
Dim strSQL As String
Const DatabasePath As String = "\\aur\hobo_data\Corporate\Corporate\3DOCK2
\D_IMA\Teams\Data Architecture and Management\Projects\Payments Transformation\02 -
Documents\23 - Data Architecture Deliverables\11 - ODS & Data Mart Model\04 Fundtech
Data Provision\Payments Transformation.mdb"
'Function argument descriptions
'LookupFieldName - the field you wish to search
'LookupValue - the value in LookupFieldName you're searching for
'ReturnField - the matching field containing the value you wish to return
Public Function DBVLookUp(TableName As String, _
LookUpFieldName As Long, _
LookupValue As String, _
ReturnField As String) As Variant
Dim adoRS As ADODB.Recordset
If adoCN Is Nothing Then SetUpConnection
Set adoRS = New ADODB.Recordset
strSQL = "SELECT [" & LookUpFieldName & "], [" & ReturnField & _
"] FROM [" & TableName & _
"] WHERE [" & LookUpFieldName & "]='" & LookupValue & "';"
'If lookup value is a number, then remove the two
adoRS.Open strSQL, adoCN, adOpenForwardOnly, adLockReadOnly
If adoRS.BOF And adoRS.EOF Then
DBVLookUp = "Value not Found"
Else
DBVLookUp = adoRS.Fields(ReturnField).Value
End If
adoRS.Close
End Function
Sub SetUpConnection()
On Error GoTo ErrHandler
Set adoCN = New Connection
adoCN.Provider = "Microsoft.Jet.OLEDB.4.0" 'Change to 3.51 for Access 97
adoCN.ConnectionString = DatabasePath
adoCN.Open
Exit Sub
ErrHandler:
MsgBox Err.Description, vbExclamation, "An error occurred"
End Sub