First let me apologize for the spaghetti code as I am not sure of the best way to handle errors like this.
The situation I run into is that our data gets moved between two different data centers throughout the year, so I need to test the connection based on the error number -2147467259, which happens on cfRS.Open CIFstr, adoConn in the top section of the code if there is no connection to that server.
I have tried using IF statements to look at the above error number, but those were not succesful, so below is the wonderful spaghetti code I have written.
Error Number 3021 happens on cfRS.MoveFirst within the Branson: section of this code when I delete the value from Sheet1.Range("B103") and I am not sure why the error is not being handled with the On Error GoTo Err3021. The ErrHandler: in the top section of the code works just fine. Any help is greatly appreciated.
CODE:
Option Explicit
Sub CIFIncoming()
Dim adoConn As New ADODB.Connection
Dim cfRS As New ADODB.Recordset
Dim Name As String, Address1 As String, Address2 As String
Dim City As String, State As String, Zip As String
Dim HomePhone As String, CellPhone As String
Dim BSA As String
Dim strConn As String
Dim CIFstr As String, CIF As String
On Error GoTo ErrHandler
'\\\\BEGIN DATABASE INFORMATION GRAB////
' 1. Sets the Connection String to the Data Base
' 2. Opens the connection to the database
' 3. Sets the SQL String to get the fields from the Data Base
' 4. Defines the CIF Number to use in the SQL String
' 5. Opens the Recordset
' 6. Moves the cursor in the DataBase to first position
strConn = [REDACTED]
adoConn.Open strConn
CIF = UCase(Sheet1.Range("B103").Text)
CIFstr = "SELECT " & _
"cfna1, cfna2, cfna3, cfcity, cfstat, LEFT(cfzip, 5), cfhpho, cfcel1, cfudsc6 " & _
"FROM cncttp08.jhadat842.cfmast cfmast " & _
"WHERE cfcif# = '" & CIF & "'"
cfRS.Open CIFstr, adoConn
cfRS.MoveFirst
'\\\\END DATABASE INFORMATION GRAB////
'\\\\BEGIN WORKSHEET INFORMATION PLACEMENT////
' 1. Assigns each field from the Database to a variable
' 2. Moves data from Database to specific cells
Name = cfRS.Fields(0) 'cfna1
Address1 = cfRS(1) 'cfna2
Address2 = cfRS(2) 'cfna3
City = Trim(cfRS.Fields(3)) 'cfcity
State = Trim(cfRS.Fields(4)) 'cfstat
Zip = cfRS.Fields(5) 'cfzip
HomePhone = cfRS.Fields(6) 'cfhpho
CellPhone = cfRS.Fields(7) 'cfcel1
BSA = cfRS.Fields(8) 'cfudsc6
With Sheet1
.Range("B104") = Name
.Range("B105") = Address1
.Range("B106") = Address2
.Range("B107") = City & ", " & State & " " & Zip
End With
'\\\\END WORKSHEET INFORMATION PLACEMENT////
'\\\\BEGIN FINAL DATABASE OPERATIONS////
' 1. Closes connection to Database
' 2. Sets the Recordset from the Database to Nothing
' 3. Exits sub when there are no errors
cfRS.Close
Set cfRS = Nothing
Exit Sub
'\\\\END FINAL DATABASE OPERATIONS
ErrHandler:
'THIS HANDLES ERROR 3021
If Err.Number = 3021 Then
With Sheet1
.Range("B104") = vbNullString
.Range("B105") = vbNullString
.Range("B106") = vbNullString
.Range("B107") = ""
End With
End If
If Err.Number = -2147467259 Then GoTo Branson
Branson:
On Error GoTo Err3021
CIF = UCase(Sheet1.Range("B103").Text)
CIFstr = "SELECT " & _
"cfna1, cfna2, cfna3, cfcity, cfstat, LEFT(cfzip, 5), cfhpho, cfcel1, cfudsc6 " & _
"FROM bhschlp8.jhadat842.cfmast cfmast " & _
"WHERE cfcif# = '" & CIF & "'"
cfRS.Open CIFstr, adoConn
cfRS.MoveFirst
'\\\\END DATABASE INFORMATION GRAB////
'\\\\BEGIN WORKSHEET INFORMATION PLACEMENT////
' 1. Assigns each field from the Database to a variable
' 2. Moves data from Database to specific cells
Name = cfRS.Fields(0) 'cfna1
Address1 = cfRS(1) 'cfna2
Address2 = cfRS(2) 'cfna3
City = Trim(cfRS.Fields(3)) 'cfcity
State = Trim(cfRS.Fields(4)) 'cfstat
Zip = cfRS.Fields(5) 'cfzip
HomePhone = cfRS.Fields(6) 'cfhpho
CellPhone = cfRS.Fields(7) 'cfcel1
BSA = cfRS.Fields(8) 'cfudsc6
With Sheet1
.Range("B104") = Name
.Range("B105") = Address1
.Range("B106") = Address2
.Range("B107") = City & ", " & State & " " & Zip
End With
'\\\\END WORKSHEET INFORMATION PLACEMENT////
'\\\\BEGIN FINAL DATABASE OPERATIONS////
' 1. Closes connection to Database
' 2. Sets the Recordset from the Database to Nothing
' 3. Exits sub when there are no errors
cfRS.Close
Set cfRS = Nothing
Exit Sub
'\\\\END FINAL DATABASE OPERATIONS
Err3021:
'THIS HANDLES ERROR 3021
If Err.Number = 3021 Then
With Sheet1
.Range("B104") = vbNullString
.Range("B105") = vbNullString
.Range("B106") = vbNullString
.Range("B107") = ""
End With
End If
End Sub
Before you do cfRS.MoveFirst, do If not(cfRS.bof and cfRS.eof) then.
When your query's Where clause is WHERE cfcif# = '' then you have no rows. When you try to execute a cfRS.MoveFirst when you have no records, you get your error 3012. so you will want to ensure that you HAVE records before you try to navigate the recordset and then manipulate it.
Related
I am trying to export six recordsets generated by a Do-Loop to six specific tabs in a single MS Excel workbook using VBA. Instead of updating the single tabs, however, the code creates six open iterations of the workbook with only the first being editable, the remainder read-only. The recordsets are successfully exported into the correct tab in the desired format.
Function ExportRecordset2XLS2(ByVal rs As DAO.Recordset, strSheetName)
Dim xls As Object
Dim xlwb As Object
Dim xlws As Object
Dim fld As DAO.Field
Dim strPath As String07
Dim strTitleRange,strHeaderRange, strBodyRange as String
On Error GoTo err_handler
strPath = "C:\Database\Roster.xlsx"
Set xls = CreateObject("Excel.Application")
Set xlwb = xls.Workbooks.Open(strPath)
xls.Visible = False
xls.ScreenUpdating = False
Set xlws = xlwb.Worksheets(strSheetName)
xlws.Activate
'Define ranges for formatting
intFields = rs.Fields.Count
intRows = rs.RecordCount
strTitleRange = "A1:" & Chr(64 + intFields) & "1"
strHeaderRange = "A2:" & Chr(64 + intFields) & "2"
strBodyRange = "A3:" & Chr(64 + intFields) & (intRows + 2)
'Build TITLE Row
xlws.Range("A1").Select
xls.ActiveCell = Format(Now(), "YYYY") & " Roster (" & strSheetName & ")"
'Build HEADER Row
xlws.Range("A2").Select
For Each fld In rs.Fields
xls.ActiveCell = fld.Name
xls.ActiveCell.Offset(0, 1).Select
Next
rs.MoveFirst
'Paste Recordset into Worksheet(strSheetName) starting in A3
xlws.Range("A3").CopyFromRecordset rs
On Error Resume Next
xls.Visible = True 'Make excel visible to the user
Set rs = Nothing
Set xlws = Nothing
Set xlwb = Nothing
xls.ScreenUpdating = True
Set xls = Nothing
xls.Quit
Exit Function
err_handler:
DoCmd.SetWarnings True
MsgBox Err.Description, vbExclamation, Err.Number
Exit Function
End Function
I suspect the problem revolves around how the function opens the .xlsx file for editing; I have tried programmatically closing the active worksheet and/or workbook in various ways and sequences to no effect. I could presumably insert a break into the code that generates the recordset to allow MS Excel to open then close, before repeating the process with the next tab, but there must be a more elegant way.
Image of multiple iterations in Excel
** As a side note, I did post this question also to answers.microsoft.com before finding this forum. Sorry. **
Thanks in advance, Erik
For each workbook opened you can check the security and reset it so it can be edited:
If Application.ProtectedViewWindows.Count > 0 Then
Application.ActiveProtectedViewWindow.Edit
End If
As expected, this turned out to be series of small issues that resulted in MS Excel holding the workbook file in read-only status after the function would error out. SOlved after scrutinizing each line of code to find individual lines that were failing.
Try this methodology and feedback.
Dim qdf As DAO.QueryDef
Dim dbs As DAO.Database
Dim rstMgr As DAO.Recordset
Dim strSQL As String, strTemp As String, strMgr As String
' Replace PutEXCELFileNameHereWithoutdotxls with actual EXCEL
' filename without the .xls extension
' (for example, MyEXCELFileName, BUT NOT MyEXCELFileName.xls)
Const strFileName As String = "PutEXCELFileNameHereWithoutdotxls"
Const strQName As String = "zExportQuery"
Set dbs = CurrentDb
' Create temporary query that will be used for exporting data;
' we give it a dummy SQL statement initially (this name will
' be changed by the code to conform to each manager's identification)
strTemp = dbs.TableDefs(0).Name
strSQL = "SELECT * FROM [" & strTemp & "] WHERE 1=0;"
Set qdf = dbs.CreateQueryDef(strQName, strSQL)
qdf.Close
strTemp = strQName
' *** code to set strSQL needs to be changed to conform to your
' *** database design -- ManagerID and EmployeesTable need to
' *** be changed to your table and field names
' Get list of ManagerID values -- note: replace my generic table and field names
' with the real names of the EmployeesTable table and the ManagerID field
strSQL = "SELECT DISTINCT ManagerID FROM EmployeesTable;"
Set rstMgr = dbs.OpenRecordset(strSQL, dbOpenDynaset, dbReadOnly)
' Now loop through list of ManagerID values and create a query for each ManagerID
' so that the data can be exported -- the code assumes that the actual names
' of the managers are in a lookup table -- again, replace generic names with
' real names of tables and fields
If rstMgr.EOF = False And rstMgr.BOF = False Then
rstMgr.MoveFirst
Do While rstMgr.EOF = False
' *** code to set strMgr needs to be changed to conform to your
' *** database design -- ManagerNameField, ManagersTable, and
' *** ManagerID need to be changed to your table and field names
' *** be changed to your table and field names
strMgr = DLookup("ManagerNameField", "ManagersTable", _
"ManagerID = " & rstMgr!ManagerID.Value)
' *** code to set strSQL needs to be changed to conform to your
' *** database design -- ManagerID, EmployeesTable need to
' *** be changed to your table and field names
strSQL = "SELECT * FROM EmployeesTable WHERE " & _
"ManagerID = " & rstMgr!ManagerID.Value & ";"
Set qdf = dbs.QueryDefs(strTemp)
qdf.Name = "q_" & strMgr
strTemp = qdf.Name
qdf.SQL = strSQL
qdf.Close
Set qdf = Nothing
' Replace C:\FolderName\ with actual path
DoCmd.TransferSpreadsheet acExport, acSpreadsheetTypeExcel9, _
strTemp, "C:\FolderName\" & strFileName & ".xls"
rstMgr.MoveNext
Loop
End If
rstMgr.Close
Set rstMgr = Nothing
dbs.QueryDefs.Delete strTemp
dbs.Close
Set dbs = Nothing
I'm trying to import relevant information from Excel report which is not specifically designed to import data. Basically it is formatted report with other information. Please see the attached image to get an idea. This is huge report and contains hundreds of rows.
I'm thinking to import data by reading Excel file reading line by line, based on the information on that particular row and then inserting that row into Access table.
I've attached simplified version of report to give you an idea about the report layout and also Access table structure, the information I want to store in table DailyTranaction.
Example Report Image here:
Access Table Structure Image here:
I'm not sure the best way to do the above task using Access VBA, a working simple example will be highly appreciated.
Insert new code module then copy and paste below code:
Option Compare Database
Option Explicit
Public Function GetDataFromReport(ByVal sRepFileName As String) As Integer
Dim xlApp As Object, xlWbk As Object, xlWsh As Object
Dim retVal As Integer, sRepDate As String, r As Integer, sBranch As String, sQry As String, rs As Integer
On Error GoTo Err_GetDataFromReport
DoCmd.SetWarnings False
Set xlApp = CreateObject("Excel.Application")
Set xlWbk = xlApp.Workbooks.Open(sRepFileName)
Set xlWsh = xlWbk.Worksheets(1) 'or pass the name, ex: "Sheet1"
sRepDate = xlWsh.Range("A1")
r = InStr(1, sRepDate, "th")
sRepDate = Replace(sRepDate, Left(sRepDate, InStr(r - 3, sRepDate, " ")), "")
sRepDate = Replace(sRepDate, "th", "")
'find the last row;
rs = xlWsh.Range("A" & xlWsh.Rows.Count).End(-4162).Row
r = 3
Do While r <= rs
Select Case UCase(Trim(xlWsh.Range("A" & r)))
Case "", UCase("CustId")
'skip empty row and header of data
GoTo SkipRow
Case UCase("Branch:")
sBranch = xlWsh.Range("B" & r)
Case Else
'proceed if the value is numeric
If Not IsNumeric(xlWsh.Range("A" & r)) Then GoTo SkipRow
sQry = "INSERT INTO Reports([ReportDate],[BranchCode],[CustId],[AccountNo],[Transaction])" & vbCr & _
"VALUES(#" & sRepDate & "#," & sBranch & ", " & xlWsh.Range("A" & r) & _
", " & xlWsh.Range("B" & r) & ", " & xlWsh.Range("C" & r) & ")"
'Debug.Print sQry
DoCmd.RunSQL sQry
'get the number of rows affected ;)
retVal = retVal +1
End Select
SkipRow:
r = r + 1
Loop
Exit_GetDataFromReport:
On Error Resume Next
DoCmd.SetWarnings True
Set xlWsh = Nothing
xlWbk.Close SaveChanges:=False
Set xlWbk = Nothing
xlApp.Quit
Set xlApp = Nothing
'return value
GetDataFromReport = retVal
Exit Function
Err_GetDataFromReport:
MsgBox Err.Description, vbExclamation, Err.Number
Resume Exit_GetDataFromReport
End Function
To use this, you need to create macro, which action should refer to above function:
GetDataFromReport ("C:\report.xls")
As you can see, you need to define full path to the source workbook.
Alternativelly, you can run above code by creating procedure:
Sub Test()
MsgBox GetDataFromReport("D:\Report Daily Transaction.xls") & " records have been imported!", vbInformation, "Message..."
End Sub
Alternativelly, you can create macro which open form. Sample database and report
Good luck!
I have a table in an Excel file with some data, and I want to export these data to my database on Access (in a concrete table on my database called Water Quality) with a VBA code to avoid to open my Database every time that I want to introduce more data on it.
At the moment I have this code but it's not working...
Sub Button14_Click()
' Macro purpose: To add record to Access database using ADO and SQL
' NOTE: Reference to Microsoft ActiveX Data Objects Libary required
' Exports data from the active worksheet to a table in an Access database
'Dim cnt As New ADODB.Connection
'Dim rst As New ADODB.Recordset
Dim cnt As DAO.Database
Dim rst As Recordset
Dim dbPath As String
Dim tblName As String
Dim rngColHeads As Range
Dim rngTblRcds As Range
Dim colHead As String
Dim rcdDetail As String
Dim ch As Integer
Dim cl As Integer
Dim notNull As Boolean
Dim sConnect As String
'Set the string to the path of your database as defined on the worksheet
dbPath = "C:\Documents and Settings\Administrador\Mis documentos\MonEAU\modelEAU Database V.2.accdb"
tblName = "Water Quality"
Set rngColHeads = ActiveSheet.Range("tblHeadings")
Set rngTblRcds = ActiveSheet.Range("tblRecords")
'Set the database connection string here
sConnect = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source='" & dbPath & "';" 'For use with *.accdb files
' sConnect = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & dbPath & ";" 'For use with *.mdb files
'Concatenate a string with the names of the column headings
colHead = " ("
For ch = 1 To rngColHeads.Count
colHead = colHead & rngColHeads.Columns(ch).Value
Select Case ch
Case Is = rngColHeads.Count
colHead = colHead & ")"
Case Else
colHead = colHead & ","
End Select
Next ch
'Open connection to the database
cnt.Open sConnect
'Begin transaction processing
On Error GoTo EndUpdate
cnt.BeginTrans
'Insert records into database from worksheet table
For cl = 1 To rngTblRcds.Rows.Count
'Assume record is completely Null, and open record string for concatenation
notNull = False
rcdDetail = "('"
'Evaluate field in the record
For ch = 1 To rngColHeads.Count
Select Case rngTblRcds.Rows(cl).Columns(ch).Value
'if empty, append value of null to string
Case Is = Empty
Select Case ch
Case Is = rngColHeads.Count
rcdDetail = Left(rcdDetail, Len(rcdDetail) - 1) & "NULL)"
Case Else
rcdDetail = Left(rcdDetail, Len(rcdDetail) - 1) & "NULL,'"
End Select
'if not empty, set notNull to true, and append value to string
Case Else
notNull = True
Select Case ch
Case Is = rngColHeads.Count
rcdDetail = rcdDetail & rngTblRcds.Rows(cl).Columns(ch).Value & "')"
Case Else
rcdDetail = rcdDetail & rngTblRcds.Rows(cl).Columns(ch).Value & "','"
End Select
End Select
Next ch
'If record consists of only Null values, do not insert it to table, otherwise
'insert the record
Select Case notNull
Case Is = True
rst.Open "INSERT INTO " & tblName & colHead & " VALUES " & rcdDetail, cnt
Case Is = False
'do not insert record
End Select
Next cl
EndUpdate:
'Check if error was encounted
If Err.Number <> 0 Then
'Error encountered. Rollback transaction and inform user
On Error Resume Next
cnt.RollbackTrans
MsgBox "There was an error. Update was not succesful!", vbCritical, "Error!"
Else
On Error Resume Next
cnt.CommitTrans
End If
'Close the ADO objects
cnt.Close
Set rst = Nothing
Set cnt = Nothing
On Error GoTo 0
End Sub
At the moment, the problem is when I debug the code, there appears the compiling error: "Method or data member not found" on the function "cnt.Open sConnect".
If this is possible, any help would be greatly appreciated.
Note: I'm using Office 2010.
Your compile error is due to these 2 lines:
Dim cnt As DAO.Database
cnt.Open sConnect
A DAO.Database object does not have an .Open method, which explains "Method or data member not found". Too often error messages can be somewhat vague and just not very helpful. However, in this case, I can't think how the error message could be any more clear.
There is something more which I don't understand. sConnect looks like an ADO connection string. But cnt is a DAO (database) object. You can't mashup the two object models like that in one statement.
You have this just before your active variable declarations:
'Dim cnt As New ADODB.Connection
Then later in your procedure, you have:
'Close the ADO objects
cnt.Close
So perhaps you originally intended cnt to be an ADO.Connection object and didn't adapt the rest of your code after you switched it to a DAO.Database object.
I suggest you revise your code to sort out the DAO vs. ADO confusion, then show us the new code if you have any remaining problems. And please show us only the minimum tested code required to reproduce the problem you're hoping to solve. TIA for your consideration.
I only have Access databases that open the excel file (instead of the other way around) but from looking through my code I think you should go straight to this:
`Set cnt = OpenDatabase_
(dbPath, False, True, "Access 8.0;")
Found this on http://support.microsoft.com/kb/190195 too.
Does this help?
I have a workbook that contains a pivot table which is updated by a macro. Before the data is refreshed, though, the connection string gets changed:
With ThisWorkbook.Connections("Data").ODBCConnection
.Connection = [Redacted]
.CommandText = "EXEC ExtractCases " & Client
.BackgroundQuery = False
.Refresh
End With
This seems to cause the pivot table to create a new connection (called either Connection or Data1, and I can't seem to figure out what it does to choose between them) and point itself to that. So I then have to add lines like these:
Sheets("Pivot").PivotTables("Pivot").ChangeConnection ThisWorkbook.Connections("Data")
Sheets("Pivot").PivotTables("Pivot").PivotCache.Refresh
Which seems to work (except when it doesn't), but leaves a lot of dead connections knocking around the workbook causing confusion.
I've tried manually deleting the Connection connection, but then it suddenly names itself Data1 itself for no apparent reason and the system gets upset because a non-existent Connection can't be deleted.
Is there something obvious I'm doing wrong? Is there some magic way to fix this so it doesn't create the second one in the first place to cause these kinds of headaches?
Note: I am running this code in Excel 2010, but the workbook has to be openable by 2003; however, I remove the VB module before distribution, so 2010 macro stuff is fine, it's just things in the workbook proper that might get tripped up by this...
I have experienced the same problem in Excel 2010 (might be the same for earlier versions, I dunno).
I have tried the same approach as you i.e. changing the connection of the Pivot Table in the VBA-code AFTER I have edited the commandText of the connection string. As you, I noted sometimes success and other times failure.
I haven't been able to find out why the problem arises and in which cases the above mentioned approach results in success or failure.
I have, however, found a working solution:
In your VBA code, you need to perform the following steps in the said order:
Change the commandText (which as you know results in the creation of a new
connection now in use by the Pivot Table).
Delete the old connection string.
Rename the connection string from step 1 to the name of the connection string deleted in step 2.
Refresh the Pivot Table.
NB: This only works if there is only one pivot table using the connection. If you have created extra Pivot Tables by copying the first one (i.e. they share the same Pivot Cache), the above mentioned procedure won't work (and I don't know why).
However, if you use only one Pivot Table with the connection string the approach will work.
I do not believe that it is the update of the connection string that is causing your problem. There is a bug when updating the CommandText property of an ODBC connection that causes an extra connection to be created. If you temporarily switch to an OLEDB connection, update your CommandText property and then switch back to ODBC it does not create the new connection. Don't ask me why... this just works for me.
I created a module that allows you to update the CommandText and/or Connection string. Insert this code into a new module:
Option Explicit
Sub UpdateWorkbookConnection(WorkbookConnectionObject As WorkbookConnection, Optional ByVal CommandText As String = "", Optional ByVal ConnectionString As String = "")
With WorkbookConnectionObject
If .Type = xlConnectionTypeODBC Then
If CommandText = "" Then CommandText = .ODBCConnection.CommandText
If ConnectionString = "" Then ConnectionString = .ODBCConnection.Connection
.ODBCConnection.Connection = Replace(.ODBCConnection.Connection, "ODBC;", "OLEDB;", 1, 1, vbTextCompare)
ElseIf .Type = xlConnectionTypeOLEDB Then
If CommandText = "" Then CommandText = .OLEDBConnection.CommandText
If ConnectionString = "" Then ConnectionString = .OLEDBConnection.Connection
Else
MsgBox "Invalid connection object sent to UpdateWorkbookConnection function!", vbCritical, "Update Error"
Exit Sub
End If
If StrComp(.OLEDBConnection.CommandText, CommandText, vbTextCompare) <> 0 Then
.OLEDBConnection.CommandText = CommandText
End If
If StrComp(.OLEDBConnection.Connection, ConnectionString, vbTextCompare) <> 0 Then
.OLEDBConnection.Connection = ConnectionString
End If
.Refresh
End With
End Sub
This UpdateWorkbookConnection subroutine only works on updating OLEDB or ODBC connections. The connection does not necessarily have to be linked to a pivot table. It also fixes another problem and allows you to update the connection even if there are multiple pivot tables based on the same connection.
To initiate the update just call the function with the connection object and command text parameters like this:
UpdateWorkbookConnection ActiveWorkbook.Connections("Connection"), "exec sp_MyAwesomeProcedure", "ODBC;..."
you may add this code, after you refresh connection.
With ThisWorkbook
.RefreshAll
End With
Had the same problem. Have a start date and end date field on the worksheet that is used to modify the period for the data in a pivot table. Added the following code for the worksheet:
Private Sub Worksheet_Change(ByVal Target As Range)
'Update the query when the date range has been changed.
If (Target.Row = Worksheets("Revenue").Range("StartDate").Row Or _
Target.Row = Worksheets("Revenue").Range("EndDate").Row) And _
Target.Column = Worksheets("Revenue").Range("StartDate").Column Then
FilterTableData
End If
End Sub
Sub FilterTableData()
'Declare variables
Dim noOfConnections As Integer
Dim loopCount As Integer
Dim conn As WorkbookConnection
Dim connectionName As String
Dim startDate As Date
Dim endDate As Date
Dim strMonth As String
Dim strDay As String
Dim startDateString As String
Dim endDateString As String
'Remove current connections
'Note: Excel creates a new connection with a new name as soon as you change the query for the connection. To avoid
' ending up with multiple connections delete all connections and start afresh.
'First delete all fake connections
noOfConnections = ActiveWorkbook.Connections.Count
For loopCount = noOfConnections To 1 Step -1
Set conn = ActiveWorkbook.Connections.Item(loopCount)
If conn Is Nothing Then
conn.Delete
End If
Next loopCount
'Then delete all extra connections
noOfConnections = ActiveWorkbook.Connections.Count
For loopCount = noOfConnections To 1 Step -1
If loopCount = 1 Then
Set conn = ActiveWorkbook.Connections.Item(loopCount)
conn.Name = "Connection1"
Else
Set conn = ActiveWorkbook.Connections.Item(loopCount)
conn.Delete
End If
Next loopCount
'Create date strings for use in query.
startDate = Worksheets("Revenue").Range("B1")
strDay = Day(startDate)
If Len(strDay) = 1 Then
strDay = "0" & strDay
End If
strMonth = Month(startDate)
If Len(strMonth) = 1 Then
strMonth = "0" & strMonth
End If
startDateString = Year(startDate) & "-" & strMonth & "-" & strDay & " 00:00:00"
endDate = Worksheets("Revenue").Range("B2")
strDay = Day(endDate)
If Len(strDay) = 1 Then
strDay = "0" & strDay
End If
strMonth = Month(endDate)
If Len(strMonth) = 1 Then
strMonth = "0" & strMonth
End If
endDateString = Year(endDate) & "-" & strMonth & "-" & strDay & " 00:00:00"
'Modify the query in accordance with the new date range
With conn.ODBCConnection
.CommandText = Array( _
"SELECT INVOICE.ACCOUNT_PERIOD, INVOICE.INVOICE_NUMBER, INVOICE_ITEM.LAB, INVOICE_ITEM.TOTAL_PRICE, ", _
"INVOICE.INVOICED_ON" & Chr(13) & "" & Chr(10) & _
"FROM Lab.dbo.INVOICE INVOICE, Lab.dbo.INVOICE_ITEM INVOICE_ITEM" & Chr(13) & "" & Chr(10) & _
"WHERE INVOICE.INVOICE_NUMBER = INVOICE_ITEM.INVOICE_NUMBER AND ", _
"INVOICE.INVOICED_ON > {ts '" & startDateString & "'} AND INVOICE.INVOICED_ON < {ts '" & endDateString & "'} ")
End With
'Refresh the data and delete any surplus connections
noOfConnections = ActiveWorkbook.Connections.Count
If noOfConnections = 1 Then
'Rename connection
ActiveWorkbook.Connections.Item(1).Name = "Connection"
'Refresh the data
ActiveWorkbook.Connections("Connection").Refresh
Else
'Refresh the data
ActiveWorkbook.Connections("Connection").Refresh
'Delete the old connection
ActiveWorkbook.Connections("Connection1").Delete
End If
'Refresh the table
ActiveSheet.PivotTables("Revenue").Update
End Sub
I'm looking for a macro/vbs to update all the external data query connections to point at a different server or database. This is a pain to do manually and in versions of Excel before 2007 it sometimes seems impossible to do manually.
Anyone have a sample? I see there are different types of connections 'OLEDB' and 'ODBC', so I guess I need to deal with different formats of connection strings?
I ended up writing the following, which prompts for the connection details, creates a connection string, then updates all external data queries to use that connection string.
'''' Prompts for connection details and updates all the external data connections in the workbook accordingly.
'''' Changes all connections to use ODBC connections instead of OLEDB connections.
'''' Could be modified to use OLEDB if there's a need for that.
Sub PromptAndUpdateAllConnections()
Dim Server As String, Database As String, IntegratedSecurity As Boolean, UserId As String, Password As String, ApplicationName As String
Dim ConnectionString As String
Dim MsgTitle As String
MsgTitle = "Connection Update"
If vbOK = MsgBox("You will be asked for information to connect to the database, and this spreadsheet will be updated to connect using those details.", vbOKCancel, MsgTitle) Then
Server = InputBox("Database server or alias and instance name, e.g. 'LONDB01' or 'LONDB01\INST2'", MsgTitle)
If Server = "" Then GoTo Cancelled
Database = InputBox("Database name", MsgTitle, "a default value")
If Database = "" Then GoTo Cancelled
IntegratedSecurity = (vbYes = MsgBox("Integrated Security? (i.e. has your windows account been given access to connect to the database)", vbYesNo, MsgTitle))
If Not IntegratedSecurity Then
UserId = InputBox("User Id", MsgTitle)
If UserId = "" Then GoTo Cancelled
Password = InputBox("Password", MsgTitle)
If Password = "" Then GoTo Cancelled
End If
ApplicationName = "Excel Reporting"
ConnectionString = GetConnectionString(Server, Database, IntegratedSecurity, UserId, Password, ApplicationName)
UpdateAllQueryTableConnections ConnectionString
MsgBox "Spreadsheet Updated", vbOKOnly, MsgTitle
End If
Exit Sub
Cancelled:
MsgBox "Spreadsheet not updated", vbOKOnly, MsgTitle
End Sub
'''' Generates an ODBC connection string from the given details.
Function GetConnectionString(Server As String, Database As String, IntegratedSecurity As Boolean, _
UserId As String, Password As String, ApplicationName As String)
Dim result As String
If IntegratedSecurity Then
result = "ODBC;DRIVER=SQL Server;SERVER=" & Server & ";DATABASE=" & Database _
& ";Trusted_Connection=Yes;APP=" & ApplicationName & ";"
Else
result = "ODBC;DRIVER=SQL Server;SERVER=" & Server & ";DATABASE=" & Database _
& ";UID=" & UserId & ";PWD=" & Password & ";APP=" & ApplicationName & ";"
End If
RM_GetConnectionString = result
End Function
'''' Sets all external data connection strings to the given value (regardless of whether they're
'''' currently ODBC or OLEDB connections. Appears to change type successfully.
Sub UpdateAllQueryTableConnections(ConnectionString As String)
Dim w As Worksheet, qt As QueryTable
Dim cn As WorkbookConnection
Dim odbcCn As ODBCConnection, oledbCn As OLEDBConnection
For Each cn In ThisWorkbook.Connections
If cn.Type = xlConnectionTypeODBC Then
Set odbcCn = cn.ODBCConnection
odbcCn.SavePassword = True
odbcCn.Connection = ConnectionString
ElseIf cn.Type = xlConnectionTypeOLEDB Then
Set oledbCn = cn.OLEDBConnection
oledbCn.SavePassword = True
oledbCn.Connection = ConnectionString
End If
Next
End Sub
Connection string format is largely irrelevant as Excel will pass it to data providers.
Update one querytable manually, then do something like this:
dim w as worksheet, q as querytable
for each w in thisworkbook.worksheets
for each q in w.querytables
q.connection = SampleSheet.querytables("PreparedQueryTable").connection
next
next
Even we can refresh particular connection and in turn it will refresh all the pivots linked to it.
For this code I have created slicer from table present in Excel:
This code is for Slicer from DB:
Sub UpdateConnection()
Dim ServerName As String
Dim ConnectionString As String
Dim DatabaseNameCount As Integer
DatabaseNameCount = ActiveWorkbook.SlicerCaches("Slicer_Name").VisibleSlicerItems.Count
If DatabaseNameCount = 1 Then
ServerName = ActiveWorkbook.SlicerCaches("Slicer_Name").VisibleSlicerItems.Item(1).Name
ConnectionString = GetConnectionString(ServerName)
UpdateAllQueryTableConnections ConnectionString
Else
MsgBox "Please Select One Value", vbOKOnly, "Slicer Info"
End If
End Sub
This code is for Slicer created from Excel table present in same workbook:
Sub UpdateConnection()
Dim ServerName As String
Dim ServerNameRaw As String
Dim CubeName As String
Dim CubeNameRaw As String
Dim ConnectionString As String
ServerNameRaw = ActiveWorkbook.SlicerCaches("Slicer_ServerName").VisibleSlicerItemsList(1)
ServerName = Replace(Split(ServerNameRaw, "[")(3), "]", "")
CubeNameRaw = ActiveWorkbook.SlicerCaches("Slicer_CubeName").VisibleSlicerItemsList(1)
CubeName = Replace(Split(CubeNameRaw, "[")(3), "]", "")
If CubeName = "All" Or ServerName = "All" Then
MsgBox "Please Select One Cube and Server Name", vbOKOnly, "Slicer Info"
Else
ConnectionString = GetConnectionString(ServerName, CubeName)
UpdateAllQueryTableConnections ConnectionString, CubeName
End If
End Sub
Common code to create connection and update connection for desired Initial Catalog:
Function GetConnectionString(ServerName As String, CubeName As String)
Dim result As String
result = "OLEDB;Provider=MSOLAP.5;Integrated Security=SSPI;Persist Security Info=True;Initial Catalog=" & CubeName & ";Data Source=" & ServerName & ";MDX Compatibility=1;Safety Options=2;MDX Missing Member Mode=Error;Update Isolation Level=2"
'"OLEDB;Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=True;Initial Catalog=" & CubeName & ";Data Source=" & ServerName & ";Use Procedure for Prepare=1;Auto Translate=True;Packet Size=4096;Use Encryption for Data=False;Tag with column collation when possible=False"
GetConnectionString = result
End Function
Sub UpdateAllQueryTableConnections(ConnectionString As String, CubeName As String)
Dim cn As WorkbookConnection
Dim oledbCn As OLEDBConnection
Dim Count As Integer, i As Integer
Dim DBName As String
DBName = "Initial Catalog=" + CubeName
Count = 0
For Each cn In ThisWorkbook.Connections
If cn.Name = "ThisWorkbookDataModel" Then
Exit For
End If
oTmp = Split(cn.OLEDBConnection.Connection, ";")
For i = 0 To UBound(oTmp) - 1
If InStr(1, oTmp(i), DBName, vbTextCompare) = 1 Then
Set oledbCn = cn.OLEDBConnection
oledbCn.SavePassword = True
oledbCn.Connection = ConnectionString
Count = Count + 1
End If
Next
Next
If Count = 0 Then
MsgBox "Nothing to update", vbOKOnly, "Update Connection"
ElseIf Count > 0 Then
MsgBox "Connection Updated Successfully", vbOKOnly, "Update Connection"
End If
End Sub