Excel VBA Macro to copy a macro - excel
SOLVED. Solution at bottom!
Hopefully you brainiacs can help me out as I've apparently reached the limit of my programming capabilities.
I am looking for a way to write a VBA sub which duplicates another VBA Sub, but replace the name and another input. The case in details:
I am trying to build an Excel template for the organization, which will allow the users to inport/export data to/from Access databases (.accdb), as the end-users reluctance towards using real databases (as opposed to excel lists) apparently lies in their inability to extract/submit the data to/from Excel, where they are comfortable working with the data.
The challenge is, that users who don't know how to link to Access, for sure don't know anything about VBA code. Hence, I have created a worksheet from which the users selects a database using a file-path, table, password, set filters, define where to copy/insert datasets, fields to import etc. A Macro then handles the rest.
However, I want to create a macro which allows the user to create additional database links. As it is right now, this would require the user to open VBE and copy two macros and change one line of code... but that is a recipe for disaster. So how can I add a button to the sheet that copies the code I have written and rename the macro?
... I was considering if using a function, but cannot get my head around how that should Work.
Does it make sense? Any ideas/ experiences? Is there a completely different way around it that I haven't considered?
I'd really appreciate your inputs - even if this turns out to be impossible.
Edit:
Macro Man, you asked for the code - it's rather long due to all the user input fields, so I was trying to save you Guys for it since the code in and of itself is working fine...
Sub GetData1()
' Click on Tools, References and select
' the Microsoft ActiveX Data Objects 2.0 Library
Dim DBFullName As String
Dim Connect As String, Source As String
Dim Connection As ADODB.Connection
Dim Recordset As ADODB.Recordset
Dim Col As Integer
Dim DBInfoLocation As Range
Dim PW As String
Dim WSforData As String
Dim CellforData As String
Dim FieldList As Integer
'******************************
'Enter location for Database conectivity details below:
'******************************
Set DBInfoLocation = ActiveWorkbook.Sheets("DBLinks").Range("C15:I21")
FieldList = ActiveWorkbook.Sheets("DBLinks").Range("P1").Value
'******************************
' Define data location
WSforData = DBInfoLocation.Rows(4).Columns(1).Value
CellforData = DBInfoLocation.Rows(5).Columns(1).Value
'Set filters
Dim FilField1, FilField2, FilFieldA, FilFieldB, FilFieldC, FilFieldD, FilFieldE, FilOperator1, FilOperator2, FilOperatorA, FilOperatorB, FilOperatorC, FilOperatorD, FilOperatorE, FilAdMth1, FilAdMthA, FilAdMthB, FilAdMthC, FilAdMthD As String
Dim Filtxt1, Filtxt2, FiltxtA, FiltxtB, FiltxtC, FiltxtD, FiltxtE As String
Dim ExtFld1, ExtFld2, ExtFld3, ExtFld4, ExtFld5, ExtFld6, ExtFld7, ExtFld As String
Dim FilCnt, FilCntA As Integer
Dim FilVar1 As String
'Set DB field names
FilField1 = DBInfoLocation.Rows(1).Columns(5).Value
FilField2 = DBInfoLocation.Rows(2).Columns(5).Value
FilFieldA = DBInfoLocation.Rows(3).Columns(5).Value
FilFieldB = DBInfoLocation.Rows(4).Columns(5).Value
FilFieldC = DBInfoLocation.Rows(5).Columns(5).Value
FilFieldD = DBInfoLocation.Rows(6).Columns(5).Value
FilFieldE = DBInfoLocation.Rows(7).Columns(5).Value
'Set filter operators
FilOperator1 = DBInfoLocation.Rows(1).Columns(6).Value
FilOperator2 = DBInfoLocation.Rows(2).Columns(6).Value
FilOperatorA = DBInfoLocation.Rows(3).Columns(6).Value
FilOperatorB = DBInfoLocation.Rows(4).Columns(6).Value
FilOperatorC = DBInfoLocation.Rows(5).Columns(6).Value
FilOperatorD = DBInfoLocation.Rows(6).Columns(6).Value
FilOperatorE = DBInfoLocation.Rows(7).Columns(6).Value
'Run through criteria to find VarType(FilCrit1) (the Dimension data type) for the criteria field and set the appropriate data type for the filter
currentLoad = IIf(IsNumeric(DBInfoLocation.Rows(1).Columns(7).Value), CDbl(FilCrit1), IIf((DBInfoLocation.Rows(1).Columns(7).Value = "True" Or DBInfoLocation.Rows(1).Columns(7).Value = "False"), CBool(FilCrit1), IIf(IsDate(DBInfoLocation.Rows(1).Columns(7).Value), CDate(FilCrit1), CStr(FilCrit1))))
currentLoad = IIf(IsNumeric(DBInfoLocation.Rows(2).Columns(7).Value), CDbl(FilCrit2), IIf((DBInfoLocation.Rows(2).Columns(7).Value = "True" Or DBInfoLocation.Rows(2).Columns(7).Value = "False"), CBool(FilCrit2), IIf(IsDate(DBInfoLocation.Rows(2).Columns(7).Value), CDate(FilCrit2), CStr(FilCrit2))))
currentLoad = IIf(IsNumeric(DBInfoLocation.Rows(3).Columns(7).Value), CDbl(FilCrit3), IIf((DBInfoLocation.Rows(3).Columns(7).Value = "True" Or DBInfoLocation.Rows(3).Columns(7).Value = "False"), CBool(FilCrit3), IIf(IsDate(DBInfoLocation.Rows(3).Columns(7).Value), CDate(FilCrit3), CStr(FilCrit3))))
currentLoad = IIf(IsNumeric(DBInfoLocation.Rows(4).Columns(7).Value), CDbl(FilCrit4), IIf((DBInfoLocation.Rows(4).Columns(7).Value = "True" Or DBInfoLocation.Rows(4).Columns(7).Value = "False"), CBool(FilCrit4), IIf(IsDate(DBInfoLocation.Rows(4).Columns(7).Value), CDate(FilCrit4), CStr(FilCrit4))))
currentLoad = IIf(IsNumeric(DBInfoLocation.Rows(5).Columns(7).Value), CDbl(FilCrit5), IIf((DBInfoLocation.Rows(5).Columns(7).Value = "True" Or DBInfoLocation.Rows(5).Columns(7).Value = "False"), CBool(FilCrit5), IIf(IsDate(DBInfoLocation.Rows(5).Columns(7).Value), CDate(FilCrit5), CStr(FilCrit5))))
currentLoad = IIf(IsNumeric(DBInfoLocation.Rows(6).Columns(7).Value), CDbl(FilCrit6), IIf((DBInfoLocation.Rows(6).Columns(7).Value = "True" Or DBInfoLocation.Rows(6).Columns(7).Value = "False"), CBool(FilCrit6), IIf(IsDate(DBInfoLocation.Rows(6).Columns(7).Value), CDate(FilCrit6), CStr(FilCrit6))))
currentLoad = IIf(IsNumeric(DBInfoLocation.Rows(7).Columns(7).Value), CDbl(FilCrit7), IIf((DBInfoLocation.Rows(7).Columns(7).Value = "True" Or DBInfoLocation.Rows(7).Columns(7).Value = "False"), CBool(FilCrit7), IIf(IsDate(DBInfoLocation.Rows(7).Columns(7).Value), CDate(FilCrit7), CStr(FilCrit7))))
'Set Filter criteria
FilCrit1 = DBInfoLocation.Rows(1).Columns(7).Value
FilCrit2 = DBInfoLocation.Rows(2).Columns(7).Value
FilCrit3 = DBInfoLocation.Rows(3).Columns(7).Value
FilCrit4 = DBInfoLocation.Rows(4).Columns(7).Value
FilCrit5 = DBInfoLocation.Rows(5).Columns(7).Value
FilCrit6 = DBInfoLocation.Rows(6).Columns(7).Value
FilCrit7 = DBInfoLocation.Rows(7).Columns(7).Value
'Set additional filter-method
FilAdMth1 = DBInfoLocation.Rows(1).Columns(8).Value
FilAdMthA = DBInfoLocation.Rows(3).Columns(8).Value
FilAdMthB = DBInfoLocation.Rows(4).Columns(8).Value
FilAdMthC = DBInfoLocation.Rows(5).Columns(8).Value
FilAdMthD = DBInfoLocation.Rows(6).Columns(8).Value
'Set which fields to extract
ExtFld1 = DBInfoLocation.Rows(1).Columns(9).Value
ExtFld2 = DBInfoLocation.Rows(2).Columns(9).Value
ExtFld3 = DBInfoLocation.Rows(3).Columns(9).Value
ExtFld4 = DBInfoLocation.Rows(4).Columns(9).Value
ExtFld5 = DBInfoLocation.Rows(5).Columns(9).Value
ExtFld6 = DBInfoLocation.Rows(6).Columns(9).Value
ExtFld7 = DBInfoLocation.Rows(7).Columns(9).Value
'Filter on query
'Only criteria of value type string should have single quotation marks around them
FilCnt = 0
If FilField1 <> "" Then
If VarType(FilCrit1) = vbString Then
Filtxt1 = " WHERE [" & FilField1 & "] " & FilOperator1 & " '" & FilCrit1 & "'"
Else
Filtxt1 = " WHERE [" & FilField1 & "] " & FilOperator1 & " " & FilCrit1
End If
FilCnt = 1
End If
If FilField2 <> "" And FilCnt = 1 Then
If VarType(FilCrit2) = vbString Then
Filtxt2 = " " & FilAdMth1 & " [" & FilField2 & "] " & FilOperator2 & " '" & FilCrit2 & "'"
Else
Filtxt2 = " " & FilAdMth1 & " [" & FilField2 & "] " & FilOperator2 & " " & FilCrit2
End If
FilCnt = 2
End If
'Filter on Dataset
FilCntA = 0
If FilFieldA <> "" Then
If VarType(FilCrit3) = vbString Then
FiltxtA = FilFieldA & " " & FilOperatorA & " '" & FilCrit3 & "'"
Else
FiltxtA = FilFieldA & " " & FilOperatorA & " " & FilCrit3
End If
FilCntA = 1
End If
If FilFieldB <> "" And FilCntA = 1 Then
If VarType(FilCrit4) = vbString Then
FiltxtB = " " & FilAdMthA & " " & FilFieldB & " " & FilOperatorB & " '" & FilCrit4 & "'"
Else
FiltxtB = " " & FilAdMthA & " " & FilFieldB & " " & FilOperatorB & " " & FilCrit4
End If
FilCntA = 2
End If
If FilFieldC <> "" And FilCntA = 2 Then
If VarType(FilCrit5) = vbString Then
FiltxtC = " " & FilAdMthB & " " & FilFieldC & " " & FilOperatorC & " '" & FilCrit5 & "'"
Else
FiltxtC = " " & FilAdMthB & " " & FilFieldC & " " & FilOperatorC & " " & FilCrit5
End If
FilCntA = 3
End If
If FilFieldD <> "" And FilCntA = 3 Then
If VarType(FilCrit6) = vbString Then
FiltxtD = " " & FilAdMthC & " " & FilFieldD & " " & FilOperatorD & " '" & FilCrit6 & "'"
Else
FiltxtD = " " & FilAdMthC & " " & FilFieldD & " " & FilOperatorD & " " & FilCrit6
End If
FilCntA = 4
End If
If FilFieldE <> "" And FilCntA = 4 Then
If VarType(FilCrit7) = vbString Then
FiltxtE = " " & FilAdMthD & " " & FilFieldE & " " & FilOperatorE & " '" & FilCrit7 & "'"
Else
FiltxtE = " " & FilAdMthD & " " & FilFieldE & " " & FilOperatorE & " " & FilCrit7
End If
FilCntA = 5
End If
' Select Fields to Extract
ExtFld = "*"
If ExtFld1 <> "" Then
ExtFld = "[" & ExtFld1 & "]"
End If
If ExtFld2 <> "" Then
ExtFld = "[" & ExtFld1 & "],[" & ExtFld2 & "]"
End If
If ExtFld3 <> "" Then
ExtFld = "[" & ExtFld1 & "],[" & ExtFld2 & "],[" & ExtFld3 & "]"
End If
If ExtFld4 <> "" Then
ExtFld = "[" & ExtFld1 & "],[" & ExtFld2 & "],[" & ExtFld3 & "],[" & ExtFld4 & "]"
End If
If ExtFld5 <> "" Then
ExtFld = "[" & ExtFld1 & "],[" & ExtFld2 & "],[" & ExtFld3 & "],[" & ExtFld4 & "],[" & ExtFld5 & "]"
End If
If ExtFld6 <> "" Then
ExtFld = "[" & ExtFld1 & "],[" & ExtFld2 & "],[" & ExtFld3 & "],[" & ExtFld4 & "],[" & ExtFld5 & "],[" & ExtFld6 & "]"
End If
If ExtFld7 <> "" Then
ExtFld = "[" & ExtFld1 & "],[" & ExtFld2 & "],[" & ExtFld3 & "],[" & ExtFld4 & "],[" & ExtFld5 & "],[" & ExtFld6 & "],[" & ExtFld7 & "]"
End If
' Database path info
PW = DBInfoLocation.Rows(3).Columns(1).Value
' Your path will be different
DBFullName = DBInfoLocation.Rows(1).Columns(1).Value
DBTable = DBInfoLocation.Rows(2).Columns(1).Value
' Open the connection
Set Connection = New ADODB.Connection
Connect = "Provider=Microsoft.ACE.OLEDB.12.0;"
Connect = Connect & "Data Source=" & DBFullName & ";Jet OLEDB:Database Password=" & PW & ";"
Connection.Open ConnectionString:=Connect
' Create RecordSet & Define data to extract
Set Recordset = New ADODB.Recordset
With Recordset
'Get All Field Names by opening the DB, extracting a recordset, entering the field names and closing the dataset
Source = DBTable
.Open Source:=Source, ActiveConnection:=Connection
For ColH = 0 To Recordset.Fields.Count - 1
ActiveWorkbook.Worksheets("DBLinks").Range("A1").Offset(ColH + 3, FieldList - 1).Cells.Clear
ActiveWorkbook.Worksheets("DBLinks").Range("A1").Offset(ColH + 3, FieldList - 1).Value = Recordset.Fields(ColH).Name
ActiveWorkbook.Worksheets("RangeNames").Range("A1").Offset(ColH + 2, (DBInfoLocation.Rows(1).Columns(2).Value) - 1).Cells.Clear
ActiveWorkbook.Worksheets("RangeNames").Range("A1").Offset(ColH + 2, (DBInfoLocation.Rows(1).Columns(2).Value) - 1).Value = Recordset.Fields(ColH).Name
Next
Set Recordset = Nothing
End With
' Get the recordset, but only extract the field names of those defined in the spreadsheet.
' If no fields have been selected, all fields will be extracted.
Set Connection = New ADODB.Connection
Connect = "Provider=Microsoft.ACE.OLEDB.12.0;"
Connect = Connect & "Data Source=" & DBFullName & ";Jet OLEDB:Database Password=" & PW & ";"
Connection.Open ConnectionString:=Connect
Set Recordset = New ADODB.Recordset
With Recordset
If FilCnt = 0 Then 'No filter
Source = "SELECT " & ExtFld & " FROM " & DBTable
End If
' Filter Data if selected
If FilCnt = 1 Then
Source = "SELECT " & ExtFld & " FROM " & DBTable & Filtxt1
End If
If FilCnt = 2 Then
Source = "SELECT " & ExtFld & " FROM " & DBTable & Filtxt1 & Filtxt2
End If
.Open Source:=Source, ActiveConnection:=Connection
If FilCntA = 1 Then
Recordset.Filter = FiltxtA
End If
If FilCntA = 2 Then
Recordset.Filter = FiltxtA & FiltxtB
End If
If FilCntA = 3 Then
Recordset.Filter = FiltxtA & FiltxtB & FiltxtC
End If
If FilCntA = 4 Then
Recordset.Filter = FiltxtA & FiltxtB & FiltxtC & FiltxtD
End If
If FilCntA = 5 Then
Recordset.Filter = FiltxtA & FiltxtB & FiltxtC & FiltxtD & FiltxtE
End If
'Debug.Print Recordset.Filter
' Clear data
For Col = 0 To Recordset.Fields.Count - 1
If WSforData <> "" Then
ActiveWorkbook.Worksheets(WSforData).Range(CellforData).Offset(0, Col).EntireColumn.Clear
End If
'ActiveWorkbook.Worksheets("DBLinks").Range("A1").Offset(Col + 3, FieldList - 1).Cells.Clear
Next
' Write field names
For Col = 0 To Recordset.Fields.Count - 1
If WSforData <> "" Then
ActiveWorkbook.Worksheets(WSforData).Range(CellforData).Offset(0, Col).Value = Recordset.Fields(Col).Name
End If
Next
' Write recordset
If WSforData <> "" Then
ActiveWorkbook.Worksheets(WSforData).Range(CellforData).Offset(1, 0).CopyFromRecordset Recordset
ActiveWorkbook.Worksheets(WSforData).Columns.AutoFit
End If
End With
' Clear recordset and close connection
Set Recordset = Nothing
Connection.Close
Set Connection = Nothing
End Sub
This piece of the "DBLinks" worksheet is probably also needed for full understanding of the code:
DBLinks user input area for database connectivity
SOLUTION:
I followed the advice to look into VBProject.VBComponents which copied the macro. I created a simple form which asked for the name to use for the macro and the rest of the inputs comes from the relative reference. I will spare you for a full copy of my long and less than graceful code, but the essential of the code are:
In case someone else could benefit from my experience: In the Click-action of the command button on the form:
Private Sub cmdCreateDB_Click()
'Go to Tools, References and add: Microsoft Visual Basic for Applications Extensibility 5.3
Dim VBProj As VBIDE.VBProject
Dim VBComp As VBIDE.VBComponent
Dim CodeMod As VBIDE.CodeModule
Dim LineNum As Long
Const DQUOTE = """" ' one " character
Set VBProj = ActiveWorkbook.VBProject
Set VBComp = VBProj.VBComponents("Module1")
Set CodeMod = VBComp.CodeModule
Dim txtDBLinkName As String
txtDBLinkName = Me.txtDBName
With CodeMod
LineNum = .CountOfLines + 1
.InsertLines LineNum, " Sub " & txtDBLinkName & "()"
LineNum = LineNum + 1
.InsertLines LineNum, " ' Click on Tools, References and select"
LineNum = LineNum + 1
.InsertLines LineNum, " ' the Microsoft ActiveX Data Objects 2.0 Library"
' And then it goes on forever through all the lines of the original code...
' just remember to replace all double quotations with(Without Square brackets):
' [" & DQUOTE & "]
'And it ends up with:
LineNum = LineNum + 1
.InsertLines LineNum, " Set Recordset = Nothing"
LineNum = LineNum + 1
.InsertLines LineNum, " Connection.Close"
LineNum = LineNum + 1
.InsertLines LineNum, " Set Connection = Nothing"
LineNum = LineNum + 1
.InsertLines LineNum, " End Sub"
End With
Unload Me
End Sub
Thank you everyone for your help. - Especially you #findwindow for coming up with the path to a solution.
For the sake of completion, here's how this could be dealt with without metaprogramming.
Problems that boil down to "do the same thing - but..." can often be solved by making the program as generic as possible. All data specific to a single use-case should be passed down from above in a clear manner, allowing the program to be reused.
Let's look at an example of how this could be implemented in order to generate query strings from one or many ranges of varying sizes.
The first step is to group all data that belongs to the concept of a Filter. Since VBA doesn't have object literals, we can use an Array, a Collection or a Type to represent a Filter instead.
Generating the query strings requires distinction between QueryFilters and RecordFilters. Looking at the code, the two variants are similar enough to be handled by a simple Boolean within a single Type.
Option Explicit
Private Type Filter
Field As String
Operator As String
Criteria As Variant
AdditionalMethod As String
ExtractedFields As String
IsQueryFilter As Boolean
FilterString As String
End Type
Now we can use a single variable instead of keeping track of multiple variables to represent a single concept.
One way a Filter can be generated is by using a Range.
' Generates a Filter from a given Range of input data.
Private Function GenerateFilter(ByRef source As Range) As Filter
With GenerateFilter
.Field = CStr(source)
.Operator = CStr(source.Offset(0, 1))
.Criteria = source.Offset(0, 2)
.AdditionalMethod = CStr(source.Offset(0, 3))
.ExtractedFields = CStr(source.Offset(0, 4))
.IsQueryFilter = CBool(source.Offset(0, 5))
.FilterString = GenerateFilterString(GenerateFilter)
End With
End Function
Just as a single concept can be declared as a Type, a group of things can be declared as an Array (or a Collection, Dictionary, ...). This is useful, as it lets us decouple the logic from a specific Range.
' Generates a Filter for each row of a given Range of input data.
Private Function GenerateFilters(ByRef source As Range) As Filter()
Dim filters() As Filter
Dim filterRow As Range
Dim i As Long
ReDim filters(0 To source.Rows.Count)
i = 0
For Each filterRow In source.Rows
filters(i) = GenerateFilter(filterRow)
i = i + 1
Next
GenerateFilters = filters()
End Function
We now have a function that can return an Array of Filters from a given Range - and, as long as the columns are laid down in the right order, the code will work just fine with any Range.
With all of the data in a convenient package, it's easy enough to assemble the FilterString.
' Generates a FilterString for a given Filter.
Private Function GenerateFilterString(ByRef aFilter As Filter) As String
Dim temp As String
temp = " "
With aFilter
If .AdditionalMethod <> "" Then temp = temp & .AdditionalMethod & " "
If .IsQueryFilter Then
temp = temp & "[" & .Field & "]"
Else
temp = temp & .Field
End If
temp = temp & " " & .Operator & " "
If VarType(.Criteria) = vbString Then
temp = temp & "'" & .Criteria & "'"
Else
temp = temp & .Criteria
End If
End With
GenerateFilterString = temp
End Function
The data can then be merged to strings that can be used in queries regardless of how many Filters of either type are present in the specified Range.
' Merges the FilterStrings of Filters that have IsQueryString set as True.
Private Function MergeQueryFilterStrings(ByRef filters() As Filter) As String
Dim temp As String
Dim i As Long
temp = " WHERE"
For i = 0 To UBound(filters)
If filters(i).IsQueryFilter Then temp = temp & filters(i).FilterString
Next
MergeQueryFilterStrings = temp
End Function
' Merges the FilterStrings of Filters that have IsQueryString set as False.
Private Function MergeRecordFilterStrings(ByRef filters() As Filter) As String
Dim temp As String
Dim i As Long
For i = 0 To UBound(filters)
If Not filters(i).IsQueryFilter Then _
temp = temp & filters(i).FilterString
Next
MergeRecordFilterStrings = temp
End Function
' Merges the ExtractedFields of all Filters.
Private Function MergeExtractedFields(ByRef filters() As Filter) As String
Dim temp As String
Dim i As Long
temp = ""
For i = 0 To UBound(filters)
If filters(i).ExtractedFields <> "" Then _
temp = temp & "[" & filters(i).ExtractedFields & "],"
Next
If temp = "" Then
temp = "*"
Else
temp = Left(temp, Len(temp) - 1) ' Remove dangling comma.
End If
MergeExtractedFields = temp
End Function
With all of that done, we can finally plug a single Range in and get the generated strings out. It would be trivial to change filterRange or generate Filters from multiple Ranges.
Public Sub TestStringGeneration()
Dim filters() As Filter
Dim filterRange As Range
Set filterRange = Range("A1:A10")
filters = GenerateFilters(filterRange)
Debug.Print MergeQueryFilterStrings(filters)
Debug.Print MergeRecordFilterStrings(filters)
Debug.Print MergeExtractedFields(filters)
End Sub
TL;DR
Split code to reusable Functions & Subs
Favor sending data as arguments
Avoid hard-coding
Group data that represent a single concept
Use Arrays or other data structures over multiple variables
Related
Excel VBA Redshift Query Performance Improvements
I have an excel macro enabled workbook which offers the user the option of entering some parameters to use in the query as filter (WHERE) clauses. This in turn is supplied to the queries. I have about 3 queries which do not use the filters and 4 OR 5 depending on which filters are chosen that run using filters. The query complexity varies. The queries are run against a Redshift Cluster. (All of the data is confidential and the RS is internal connection only, so I can't give the entire query or anything, just examples) The 3 small queries are 1-2 lines. 3 or 4 of the remaining 5 are about 40 lines 5th is about 100. When run directly on the cluster with no filters: returns ~42400 rows and 23 Columns 3 small queries run and load to the excel file in less than 3 seconds or so each Medium query 1: On Cluster - ~1 Seconds Medium Query 2: On Cluster ~5 Seconds Medium Query 3: On Cluster - ~9 Seconds Large Query 1: On Cluster - ~24 seconds Now here in lies the issue, when I run these queries in vba using the following for each query to update a listboject (example code) it takes 980.59 (~16.4 Minutes) Seconds CS = "ODBC;Driver={Amazon Redshift (x64)};SERVER={RS1.us-east-1.redshift.amazonaws.com};PORT=8192;DATABASE=db1;UID=user;PASSWORD=fakepasswrod;sslmode=require" With Sheet2.ListObjects.Add(SourceType:=0, Source:=CS, Destination:=Sheet2.Range("$A$1")).QueryTable .CommandText = Sql .RefreshStyle = xlInsertDeleteCells .AdjustColumnWidth = True .ListObject.DisplayName = "Name_of_LO_1" .Refresh BackgroundQuery:=False End With In addiiton, I have to give the users the ability to do Wildcards, Comma Separated Lists, and single entries to filters. That part doesn't take long to build from the cell values. I have to build the filters with large if statements similar to the one as follows 'Filter Fields C_List = UCase(ThisWorkbook.Sheets(Sheet1.Name).Range("D1").Value) S_List = UCase(ThisWorkbook.Sheets(Sheet1.Name).Range("D2").Value) F_List = UCase(ThisWorkbook.Sheets(Sheet1.Name).Range("D3").Value) s_year = ThisWorkbook.Sheets(Sheet1.Name).Range("D4").Value Scen = "'" & ThisWorkbook.Sheets(Sheet1.Name).Range("D5").Value & "'" prior_s_year_1 = "'" & ThisWorkbook.Sheets(Sheet1.Name).Range("D6").Value & "'" prior_Scen_1 = "'" & ThisWorkbook.Sheets(Sheet1.Name).Range("D7").Value & "'" prior_s_year_2 = "'" & ThisWorkbook.Sheets(Sheet1.Name).Range("D8").Value & "'" prior_Scen_2 = "'" & ThisWorkbook.Sheets(Sheet1.Name).Range("D9").Value & "'" cat = UCase(ThisWorkbook.Sheets(Sheet1.Name).Range("D10").Value) subcat = UCase(ThisWorkbook.Sheets(Sheet1.Name).Range("D11").Value) If Site_List = "" And Cluster_List = "" And FBN_List = "" Then response = MsgBox("You have chosen no Site, Cluster or FBN filters, this will pull all data and may take some time" & vbNewLine & "Do you wish to continue?", vbYesNo) If response = vbNo Then Call MsgBox("Exiting data retrieval, please enter Site, Cluster or FBN filters and restart", vbOKOnly) Call DeleteConnections Exit Sub End If ElseIf C_List = "ALL" Then UserDefinedFilters = " bd.reg IN ( SELECT DISTINCT c FROM att_1 ) " ElseIf S_List <> "" And C_List <> "" And F_List <> "" Then S_List = Replace(S_List, ", ", ",") C_List = Replace(C_List, ", ", ",") F_List = Replace(F_List, ", ", ",") UserDefinedFilters = UserDefinedFilters & " UPPER(s) in ('" & Replace(S_List, ",", "','") & "')" & _ vbNewLine & " AND UPPER(reg) in ('" & Replace(C_List, ",", "','") & "')" & _ vbNewLine & " AND UPPER(f) in ('" & Replace(F_List, ",", "','") & "')" ElseIf S_List <> "" And C_List <> "" And F_List = "" Then S_List = Replace(S_List, ", ", ",") Cluster_List = Replace(C_List, ", ", ",") UserDefinedFilters = UserDefinedFilters & " UPPER(s) in ('" & Replace(S_List, ",", "','") & "')" & _ vbNewLine & " AND UPPER(reg) in ('" & Replace(C_List, ",", "','") & "')" ElseIf S_List <> "" And C_List = "" And F_List = "" Then S_List = Replace(S_List, ", ", ",") UserDefinedFilters = UserDefinedFilters & " UPPER(s) in ('" & Replace(S_List, ",", "','") & "')" ElseIf S_List = "" And C_List <> "" And F_List = "" Then C_List = Replace(C_List, ", ", ",") UserDefinedFilters = UserDefinedFilters & " UPPER(reg) in ('" & Replace(C_List, ",", "','") & "')" ElseIf S_List = "" And C_List = "" And F_List <> "" Then If InStr(1, F_List, ",") > 0 Then F_List = Replace(F_List, ", ", ",") UserDefinedFilters = UserDefinedFilters & " UPPER(bd.f) in ('" & Replace(UCase(F_List), ",", "','") & "')" ElseIf InStr(1, F_List, "*") > 0 Then UserDefinedFilters = UserDefinedFilters & " UPPER(bd.f) LIKE '%" & Replace(UCase(F_List), "*", "") & "%'" ElseIf InStr(1, F_List, "ABC") > 0 Then UserDefinedFilters = UserDefinedFilters & " UPPER(bd.f) LIKE '%" & UCase(Left(F_List, 12)) & "%'" Else UserDefinedFilters = UserDefinedFilters & " UPPER(bd.f) in ('" & UCase(F_List) & "')" End If ElseIf S_List = "" And C_List <> "" And F_List <> "" Then If InStr(1, F_List, ",") > 0 Then F_List = Replace(F_List, ", ", ",") UserDefinedFilters = UserDefinedFilters & " UPPER(bd.f) in ('" & Replace(UCase(F_List), ",", "','") & "')" ElseIf InStr(1, F_List, "*") > 0 Then UserDefinedFilters = UserDefinedFilters & " UPPER(bd.f) LIKE '%" & Replace(UCase(F_List), "*", "") & "%'" Else UserDefinedFilters = UserDefinedFilters & " UPPER(bd.f) in ('" & UCase(F_List) & "')" End If End If 'Cat and SubCat Filters If cat <> "" And subcat <> "" Then cat = Replace(cat, ",", "','") subcat = Replace(subcat, ",", "','") BCSFilters = BCSFilters & " AND UPPER(sca.cat) IN ('" & cat & "')" & _ vbNewLine & "AND UPPER(sca.subcat) in ('" & subcat & "')" ElseIf cat <> "" And subcat = "" Then cat = Replace(cat, ",", "','") BCSFilters = BCSFilters & " AND UPPER(sca.cat) IN ('" & cat & "')" ElseIf cat = "" And subcat <> "" Then subcat = Replace(subcat, ",", "','") BCSFilters = BCSFilters & " AND UPPER(sca.subcat) IN ('" & subcat & "')" End If The above is only two sets, but it should give you the idea of what I am having to do for building the where clause. I cannot find a way to get recordsets working using ADODB and I am not sure if that would be faster or not. I need to do this DSNless if at all possible because the file is used across a wide swath of users. Anything that anyone can think of that might help reduce this huge time in the queries? EDIT: Adding the code I attempted for records sets: Dim conn As Object Dim rs As Object Set conn = CreateObject("ADODB.Connection") Set rs = CreateObject("ADODB.Recordset") CS = "Driver={Amazon Redshift (x64)};DATA SOURCE={RS1.us-east-1.redshift.amazonaws.com};PORT=8192;DATABASE=db1;UID=user;PWD=fakepasswrod;sslmode=require" conn.Open CS Set RegAtt = ThisWorkbook.Sheets(Sheet6.Name) RegAtt.Cells.Clear RegSql = "SELECT cl,reg,curr FROM schema.table1" rs.Open RegSql With RegAtt.ListObjects.Add(xlSrcQuery, rs, Destination:=RegAtt.Range("$A$1")).QueryTable '.CommandText = RegSql .RefreshStyle = xlInsertDeleteCells .AdjustColumnWidth = True .ListObject.DisplayName = "LO_2" .Refresh BackgroundQuery:=False End With That connection string I get a driver not found error. This CS = "Driver={Amazon Redshift (x64)};SERVER={RS1.us-east-1.redshift.amazonaws.com};PORT=8192;DATABASE=db1;ID=user;PASSWORD=fakepasswrod;sslmode=require" I get 3709 - The connection cannot be used to perform this operation. It is either closed or invalid in this context.
This won't change the performance but you may find benefit in adopting a more object-orientated approach to building the queries. For example if you define a class module to hold the parameters and logic, then the build script becomes something like this ; Sub BuildFilters() Dim wb As Workbook, ws As Worksheet, response Set wb = ThisWorkbook Set ws = wb.Sheets(1) Const msg1 = "You have chosen no Site, Cluster or FBN filters," & _ "this will pull all data and may take some time" & vbNewLine & _ "Do you wish to continue?" Const msg2 = "Exiting data retrieval, please enter Site, Cluster or FBN filters and restart" Dim Qb As New QueryBuilder Qb.Init ws ' get parameters If Qb.hasNone Then response = MsgBox(msg1, vbYesNo) If response = vbNo Then Call MsgBox(msg2, vbOKOnly) 'Call DeleteConnections End If Else ' build SQL Qb.BuildUDFilter Qb.BuildBCSFilters ' dump to sheet to check result ws.Range("D13") = Qb.UDFilter ws.Range("D15") = Qb.BCSFilters End If End Sub Class Module QueryBuilder Option Explicit Public BCSFilters As String Public UDFilter As String Dim C_List As String, hasC As Boolean Dim S_List As String, hasS As Boolean Dim F_List As String, hasF As Boolean Dim s_year As String Dim Scen As String Dim prior_s_year_1 As String Dim prior_Scen_1 As String Dim prior_s_year_2 As String Dim prior_Scen_2 As String Dim cat As String, hasCat As Boolean Dim subcat As String, hasSubcat As Boolean Dim count As Integer, hasAny As Boolean ' Initialise Object from Sheet Sub Init(ws As Worksheet) With ws C_List = .Cells(1, 4) ' D1 S_List = .Cells(2, 4) F_List = .Cells(3, 4) s_year = Cells(4, 4) Scen = quoted(.Cells(5, 4)) prior_s_year_1 = quoted(.Cells(6, 4)) prior_Scen_1 = quoted(.Cells(7, 4)) prior_s_year_2 = quoted(.Cells(8, 4)) prior_Scen_2 = quoted(.Cells(9, 4)) cat = .Cells(10, 4) subcat = .Cells(11, 4) End With hasC = CBool(Len(C_List)) hasS = CBool(Len(S_List)) hasF = CBool(Len(F_List)) hasCat = CBool(Len(cat)) hasSubcat = CBool(Len(subcat)) End Sub Function hasNone() As Boolean hasNone = Not (hasC Or hasS Or hasF) End Function Sub BuildUDFilter() Dim sql As String count = 0 If UCase(C_List) = "ALL" Then sql = " bd.reg IN ( SELECT DISTINCT c FROM att_1 )" Else If hasC Then sql = BuildSelect("reg", C_List) If hasS Then sql = sql & BuildSelect("s", S_List) If hasF Then sql = sql & BuildSelect("f", F_List) End If UDFilter = sql End Sub Sub BuildBCSFilters() Dim sql As String count = 0 If hasCat Then sql = BuildSelect("sca.cat", cat) If hasSubcat Then sql = sql & BuildSelect("sca.subcat", subcat) BCSFilters = sql End Sub Private Function BuildSelect(v As String, s As String) Dim ar As Variant, i As Integer, sql As String s = UCase(s) If CBool(InStr(s, "*")) Then s = Replace(s, "*", "") sql = " LIKE '%" & s & "%'" ElseIf CBool(InStr(1, s, "ABC")) Then s = Left(s, 12) sql = " LIKE '%" & s & "%'" Else ar = Split(s, ",") For i = 0 To UBound(ar) ar(i) = Trim(ar(i)) Next If UBound(ar) = 0 Then sql = " = '" & ar(0) & "'" Else sql = " IN ('" & Join(ar, "','") & "')" End If End If sql = " UPPER(" & v & ")" & sql If count > 0 Then sql = vbNewLine & "AND" & sql End If count = count + 1 BuildSelect = sql End Function Private Function quoted(s) As String quoted = "'" & s & "'" End Function
It could be that the line .AdjustColumnWidth = True is contributing to the performance drop? (as it has to load the data to determine auto widths). Have you considered performing the majority of the code with Application.ScreenUpdating set to False and Application.Calculation set to xlCalculationManual? For details, see https://www.microsoft.com/en-us/microsoft-365/blog/2009/03/12/excel-vba-performance-coding-best-practices/ This may be worth a try to see if it improves performance. If it does: You could them put some appropriate user display messages in places for the duration of time that the screen updating is disabled. Good practice is to store and then restore values for ScreenUpdating and Calculation, so that the environment is left as it was found at the beginning of your subroutine
trying to update or insert records into an external excel worksheet using SQL query
I am trying to insert or update an external worksheet using SQL with VBA. Below the full function code. The error happen when I try to open the recordset within the loop passing the sql select query. Any help would be appreciated. I know that the connection string works as I am using it on other code. The SQL string looks like this: select top 1 VALUENAME FROM [TradingTotals$] WHERE LOWER(VALUENAME)= LOWER('Trading') AND VALUEDATE =29/05/2020 AND CLOSINGMONTH = 'May' ORDER BY VALUEDATE DESC) Full Function code Public Function InsertClosingMonthTotals(ByVal CollOfTradeLogTotObj As Collection) As Boolean Dim IsSuccess As Boolean Dim Item As TradeLogTotalsObj Dim Sql As String Dim SqlSelect As String Dim ConnDbString As String Dim TotalRecords As Long Dim Name As String Dim Value As Variant Dim Trading As Variant Dim LongShort As Variant Dim Therms As Variant Dim Valdate As Date Dim ClosingMonth As String ClosingMonth = Helper.FormatValue(Date, formatTypes.AsMonthlongOnly) Set Glob_Conn = New ADODB.Connection Set Glob_RecSet = New ADODB.Recordset ' Client-side cursor for correct return of record count Glob_RecSet.CursorLocation = adUseClient 'Get Connection string according to the database server type ConnDbString = Helper.GetConnectionString(ServerTypes.Excel, Glob_FilePathForDataInput) 'if the connection is closed then open it If (Glob_Conn.State = 0) Then Glob_Conn.Open (ConnDbString) End If For Each Item In CollOfTradeLogTotObj Name = Item.Name Value = Helper.FormatValue(Item.Value, AsNumber) LongShort = Helper.FormatValue(Item.LongShort, AsDecimalThreeDigits) Therms = Helper.FormatValue(Item.Therms, AsDecimalThreeDigits) Valdate = Helper.FormatValue(Item.dateTime, AsDateDisplay) SqlSelect = "select top 1 VALUENAME FROM [" & Glob_SheetNameTotalBooks & "$]" & _ " WHERE LOWER(VALUENAME)= LOWER(" & "'" & Name & "'" & ")" & " AND VALUEDATE =" & Valdate & _ " AND CLOSINGMONTH = " & "'" & ClosingMonth & "'" & _ " ORDER BY VALUEDATE DESC)" Debug.Print ("SQL SELECT " & SqlSelect) Debug.Print ("*************************************************************") 'open the record set If (Glob_RecSet.State <> 1) Then ' ******* ERROR ON LINE BELOW ON OPEN ********** Glob_RecSet.Open SqlSelect, Glob_Conn, adOpenForwardOnly, adLockOptimistic End If TotalRecords = Glob_RecSet.RecordCount If (TotalRecords > 0) Then Sql = "UPDATE TradingTotals SET VALUENAME =" & "'" & Name & "'" & _ ",VALUEDATE =" & Valdate & _ ",VALUE =" & Value & _ ",LONGSHORT =" & LongShort & _ ",THERMS =" & Therms & _ ",CLOSINGMONTH =" & "'" & ClosingMonth & "'" & _ " WHERE LOWER(VALUENAME) = LOWER(" & Name & ") AND VALUEDATE = " & Valdate & " AND CLOSINGMONTH =" & ClosingMonth Else Sql = "INSERT INTO " & Glob_SheetNameTotalBooks & " (VALUENAME,VALUEDATE,VALUE, LONGSHORT, THERMS, CLOSINGMONTH )" & _ " VALUES (" & "'" & Name & "'" & "," & Valdate & "," & Value & "," & LongShort & "," & Therms & "," & ClosingMonth & ")" End If Debug.Print ("SQL INSERT " & Sql) Debug.Print ("*************************************************************") Glob_Conn.Execute Sql Next Item 'cleanup Helper.CloseConnectionObjects Glob_RecSet, Glob_Conn InsertClosingMonthTotals = IsSuccess End Function EDIT this query works SQL SELECT select TOP 1 VALUENAME FROM [TradingTotals$] WHERE LOWER(VALUENAME) ='Trading' and seems that the issue is with LOWER() ; does anyone know if LOWER() cannot be used in excel queries? EDIT 2 it seems that is just a matter of "properly" writing the query in Excel; this one below works. In the previous one tehre also was an extra ")" at the end of teh query SQL SELECT select top 1 VALUENAME FROM [TradingTotals$] WHERE VALUENAME= 'Trading' AND VALUEDATE ='29/05/2020' AND CLOSINGMONTH = 'May' ORDER BY VALUEDATE DESC I am trying now to use LOWER() again EDIT 3 LOWER() dos not work, at least in the way I am using it in VBA; SQL SELECT select top 1 VALUENAME FROM [TradingTotals$] WHERE LOWER(VALUENAME)='Trading' And ValueDate = '29/05/2020' AND CLOSINGMONTH = 'May' ORDER BY VALUEDATE DESC I also tried double quotes for literals
So at the end there were different issues but mostly due with incorrect query syntax as I am writing SQL as when querying SQL server; not yet up to speed with excel query syntax. Thanks to all who added useful comments, I am unaware on how to accept an answer from a comment so I am posting this as an answer but no credit to me at all; all credit to the great folks that added comments here. I am now facing other issues with the insert.. basically even if I pass numbers as long and dates it get stored in excel as general. Even if I format the columns in the spreadsheet as date and number, it get reverted again to general?? but I am opening a new question for this issue
Domain controller and user based on email VBA
I'm trying to resolve domain controller name and user(dc\user) using email address. I have below code(borrowed) but it only solves user name for default domain. Any suggestions much appreciated. Function Get_LDAP_User_Properties(strObjectType, strSearchField, strObjectToGet, strCommaDelimProps) If InStr(strObjectToGet, "\") > 0 Then arrGroupBits = Split(strObjectToGet, "\") strDC = arrGroupBits(0) strDNSDomain = strDC & "/" & "DC=" & Replace(Mid(strDC, InStr(strDC, ".") + 1), ".", ",DC=") strObjectToGet = arrGroupBits(1) Else ' Otherwise we just connect to the default domain Set objRootDSE = GetObject("LDAP://RootDSE") strDNSDomain = objRootDSE.Get("defaultNamingContext") End If strBase = "<LDAP://" & strDNSDomain & ">" ' Setup ADO objects. Set adoCommand = CreateObject("ADODB.Command") Set ADOConnection = CreateObject("ADODB.Connection") ADOConnection.Provider = "ADsDSOObject" ADOConnection.Open "Active Directory Provider" adoCommand.ActiveConnection = ADOConnection ' Filter on user objects. 'strFilter = "(&(objectCategory=person)(objectClass=user))" strFilter = "(&(objectClass=" & strObjectType & ")(" & strSearchField & "=" & strObjectToGet & "))" ' Comma delimited list of attribute values to retrieve. strAttributes = strCommaDelimProps arrProperties = Split(strCommaDelimProps, ",") ' Construct the LDAP syntax query. strQuery = strBase & ";" & strFilter & ";" & strAttributes & ";subtree" adoCommand.CommandText = strQuery ' Define the maximum records to return adoCommand.Properties("Page Size") = 100 adoCommand.Properties("Timeout") = 30 adoCommand.Properties("Cache Results") = False ' Run the query. End Function Thanks in advance Michal
This should be the code you need to get what you want. It takes an email address and returns the attributes listed in strAttributes of the requested user. Example: Input: LdapUserByMailAddress("ad.user#ad.example.com") Output: sn: Doe; givenName: John; mail: ad.user#ad.example.com; Value of strBaseDn: <LDAP://dc=ad,dc=example,dc=com> Public Function LdapUserByMailAddress(strMailAddress As String) As String Dim arrMailParts() As String Dim strUsername As String Dim strDomain As String Dim strBaseDn As String Dim strFilter As String Dim strQuery As String Dim strAttributes As String Dim arrAttributes() As String Dim i As Integer Dim j As Integer strAttributes = "mail,sn,givenName" arrAttributes = Split(strAttributes, ",") arrMailParts = Split(strMailAddress, "#") If 1 <> UBound(arrMailParts) Then LdapUserByMailAddress = "Not a valid email address" Exit Function End If strUsername = arrMailParts(0) strDomain = arrMailParts(1) strBaseDn = "<LDAP://dc=" & Replace(strDomain, ".", ",dc=") & ">" strFilter = "(sAMAccountName=" & strUsername & ")" ' Construct the LDAP syntax query. strQuery = strBaseDn & ";" & strFilter & ";" & strAttributes & ";subtree" Set adoCommand = CreateObject("ADODB.Command") Set ADOConnection = CreateObject("ADODB.Connection") ADOConnection.Provider = "ADsDSOObject" ADOConnection.Open "Active Directory Provider" adoCommand.ActiveConnection = ADOConnection adoCommand.CommandText = strQuery adoCommand.Properties("Page Size") = 100 adoCommand.Properties("Timeout") = 30 adoCommand.Properties("Cache Results") = False Set resultSet = adoCommand.Execute LdapUserByMailAddress = "" For i = 0 To resultSet.Fields.Count - 1 LdapUserByMailAddress = LdapUserByMailAddress & resultSet.Fields(i).Name & ": " If resultSet.Fields(i).Type = adVariant And Not (IsNull(resultSet.Fields(i).Value)) Then ' For Multi Value attribute. LdapUserByMailAddress = LdapUserByMailAddress & "[MultiValue]" For j = LBound(resultSet.Fields(i).Value) To UBound(resultSet.Fields(i).Value) LdapUserByMailAddress = LdapUserByMailAddress & resultSet.Fields(i).Value(j) & " # " Next j Else ' For Single Value attribute. LdapUserByMailAddress = LdapUserByMailAddress & resultSet.Fields(i).Value End If LdapUserByMailAddress = LdapUserByMailAddress & ";" Next i End Function
Please elaborate about the given email addresses and add examples about the parameters strObjectType, strSearchField, strObjectToGet and strCommaDelimProps when calling the function Get_LDAP_User_Properties(). What is the expected result? What is the actual result? My interpretation of your question: Your input is an email address like myuser#dc.example.com and you want to get following result: dc.example.com\myuser Is this correct? In that case this could be the solution: Public Function LdapUserByMailAddress(strMailAddress As String) As String Dim arrParts() As String arrParts = Split(strMailAddress, "#") If 1 <> UBound(arrParts) Then LdapUserByMailAddress = "Not a valid email address" Exit Function End If LdapUserByMailAddress = arrParts(1) & "\" & arrParts(0) End Function If you call this function in your worksheet with following code: =LdapUserByMailAddress("user#dc.example.com") you get this result: dc.example.com\user
Hyperlinks.add changes hyperlink unwanted
I have used hyperlinks.add several times now and never had any problems with it. Now I added a line of code: SourceBook.Sheets(ESN & "_SV" & SV).Hyperlinks.Add Anchor:=Range("A" & i), _ Address:=ToPath & NewName to my base code (which you can find under here). This should add a link to the newly created document. The problem is that excel always says it cannot open the file. The link I enter via code is right, as I copied it with debug.print and it opened the file without a problem. It came to my attention that the hyperlink I added was modified by excel when I hold my mouse over the hyperlink. I wonder how this is possible. A second problem I encounterd is that when I enter the hyperlink manually and navigate manually to the file to make sure it takes the right file, excel still modifies my link and says "cannot open specified file". Anyone an idea what might go wrong here? Thanks! Code: `Application.ScreenUpdating = False Dim i, j, FSO As Object, SV, ESN, PartName, ToPath, FromPath, NewName, MsgBoxAnswer, TargetBook As Workbook, SourceBook As Workbook Dim OS, PN, SN, ProjectNumber, Customer, StartDate, EndDate, LastRowCMM ESN = ActiveWorkbook.ActiveSheet.Range("G2").Value SV = ActiveWorkbook.ActiveSheet.Range("K2").Value ProjectNumber = ActiveWorkbook.ActiveSheet.Range("A3").Value Customer = ActiveWorkbook.ActiveSheet.Range("G3").Value Set FSO = CreateObject("scripting.filesystemobject") PGB.Min = 0 PGB.Value = 0 PGB.Max = 22 'Create main folder If SV <> 1 Then SV = "(SV " & SV & ")" ToPath = "U:\tmo\vanmolle\fiches constat\Fiches constats #" & ESN & " " & SV Else ToPath = "U:\tmo\vanmolle\fiches constat\Fiches constats #" & ESN End If If FSO.folderexists(ToPath) = True Then MsgBoxAnswer = MsgBox("Folder already created.", vbExclamation, "Folder exists.") Exit Sub End If FSO.createfolder (ToPath) 'Create all Excel files & fill them in For i = 6 To 27 FromPath = "U:\tmo\VANMOLLE\Fiches constat\Template fiches constat LEAP.xlsm" If SV <> 1 Then ToPath = "U:\tmo\vanmolle\fiches constat\Fiches constats #" & ESN & " " & SV & "\" Else ToPath = "U:\tmo\vanmolle\fiches constat\Fiches constats #" & ESN & "\" End If FSO.copyfile Source:=FromPath, Destination:=ToPath NewName = "#" & ESN & "_" & ActiveWorkbook.ActiveSheet.Range("A" & i) & ".xlsm" If SV <> 1 Then FromPath = "U:\tmo\vanmolle\fiches constat\Fiches constats #" & ESN & " " & SV & "\Template fiches constat LEAP.xlsm" Else FromPath = "U:\tmo\vanmolle\fiches constat\Fiches constats #" & ESN & "\Template fiches constat LEAP.xlsm" End If Name FromPath As ToPath & NewName Set SourceBook = ThisWorkbook Set TargetBook = Workbooks.Open(ToPath & NewName) TargetBook.Sheets("Sheet1").Activate PartName = SourceBook.ActiveSheet.Range("A" & i).Value OS = SourceBook.ActiveSheet.Range("D" & i).Value PN = SourceBook.ActiveSheet.Range("B" & i).Value SN = SourceBook.ActiveSheet.Range("C" & i).Value If SN = "" Then SN = "N/A" StartDate = SourceBook.ActiveSheet.Range("G" & i).Value EndDate = SourceBook.ActiveSheet.Range("H" & i).Value 'check for right CMM 'LastRowCMM = TargetBook.Sheets("Révision CMM").Range("B6").End(xlDown).Row 'For j = 1 To LastRowCMM 'If PartName = TargetBook.Sheets("Révision CMM").Range("A" & j).Value Then ActiveWorkbook.ActiveSheet.Range("A23").Value = ActiveWorkbook.Sheets("Révision CMM").Range("B" & j).Value 'Next j TargetBook.ActiveSheet.Range("B9").Value = PartName TargetBook.ActiveSheet.Range("B10").Value = OS TargetBook.ActiveSheet.Range("B11").Value = "# " & ESN TargetBook.ActiveSheet.Range("B12").Value = PN TargetBook.ActiveSheet.Range("B13").Value = SN TargetBook.ActiveSheet.Range("E9").Value = StartDate TargetBook.ActiveSheet.Range("E10").Value = EndDate TargetBook.ActiveSheet.Range("B14").Value = ProjectNumber TargetBook.ActiveSheet.Range("B15").Value = Customer TargetBook.ActiveSheet.PageSetup.PrintArea = "$A$1:$E$39" TargetBook.Close True 'Add hyperlink SourceBook.Sheets(ESN & "_SV" & SV).Hyperlinks.Add Anchor:=Range("A" & i), _ Address:=ToPath & NewName Application.Wait (Now + TimeValue("00:00:01")) Progress.PGB.Value = i - 5 Progress.Lbl.Caption = "File " & i - 5 & " of 22 copied." Next i Application.ScreenUpdating = True`
First thing first - declare each variable explicitly. E.g.: Dim i as Long, j as Long, FSO As Object, SV as String, ESN as String and etc. The way in your code - Dim i, j, SV, ESN, PartName, ToPath they are declared as variant. Second thing second - try something really very small to debug further. E.g. write this small piece: Sub TestMe() With Worksheets(1) .Hyperlinks.Add anchor:=.Range("A1"), Address:="C:\Users\UserName\Desktop\test.docx" End With End Sub and check whether it works. If it doesn't, debug further, check whether cells are locked or anything similar.
Why does my script seem to randomly truncate strings?
The script below basically takes a source table and a target table and copies all the records. It works fairly well, but one recurring issue I'm having is that it seems to truncate some strings. Here is one of the error outputs: Error Number: -2147217900 Description: [Microsoft][ODBC SQL Server Driver][SQL S erver]Unclosed quotation mark after the character string 'STRINGSEGMENT^urn:uuid:e9 e91fe151-5w4c-12e1-bac5-25b3a0'. INSERT INTO TableName VALUES ('23189','23189','','','1^^','','12/5/2013 3:37:2 2 PM','fieldvalue','','somethinghere','somethinghere','12/5/2013 9:37:22 AM','123456','1234568798','STRINGSEGMENT^urn:uuid: e91fe151-5w4c-12e1-bac5-25b3a0 Query is 584 characters long If you look at the source data, the string that is truncated looks something like this: STRINGSEGMENT^urn:uuid:e91fe151-5w4c-12e1-bac5-25b3a0004b00^STRINGSEGMENT So it's cutting it off after the 53rd character (highlighted). The entire length of tSQL is only 584 characters long. Why is this happening? WScript.Echo "Setting Vars..." Dim sConnect, tConnect, resultSet, r Dim sDSN, sUserName, sPassWord Dim tDSN, tUserName, tPassWord Dim value sDSN = "mydsn" sUsername = "myusername" sPassword = "mypassword" tDSN = "LOCAL" tUsername = "myusername" tPassword = "mypassword" sTable = "sourceTable" tTable = "targetTable" sSQL = "" 'see below sDSN = "DSN=" & sDSN & ";UID=" & sUsername & ";PWD=" & sPassword & ";" tSQL = "Select TOP 1 ID FROM " & tTable & " ORDER BY ID Desc" tDSN = "DSN=" & tDSN & ";UID=" & sUsername & ";PWD=" & sPassword & ";" Set sConnect = CreateObject("ADODB.Connection") WScript.Echo "Opening connection to source..." sConnect.Open sDSN Set tConnect = CreateObject("ADODB.Connection") WScript.Echo "Opening connection to target..." tConnect.Open tDSN WScript.Echo "Finding Current Record..." Set r = tConnect.Execute(tSQL) On Error Resume Next r.MoveFirst if r.eof Then currentRecord = 1 Err.Clear Do While Not r.eof currentRecord = r("ID") + 1 r.MoveNext Loop r.Close sSQL ="Select * FROM " & sTable & " WHERE ID >= " & currentRecord WScript.Echo "Beginning shadow at record " & currentRecord & "..." Set resultSet = sConnect.Execute(sSQL) resultSet.MoveFirst Do While Not resultSet.eof On Error Resume Next tSQL = "INSERT INTO " & tTable & " VALUES ('" For i = 0 To resultSet.fields.Count - 1 if NOT IsNull(resultSet(i)) Then value = replace(resultSet(i),"'","") 'somewhere around here else value = "" End If tSQL = tSQL & value if i < resultSet.fields.Count - 1 Then tSQL = tSQL & "','" end if Next tSQL = tSQL & "')" 'when the error occurs, the line above doesn't seem to be processed but the line below obviously is... tConnect.Execute(tSQL) If (Err.Number <> 0) Then WScript.Echo "Error Number: " & Err.Number & " Description: " & Err.Description WScript.Echo tSQL WScript.Echo "Query is " & Len(tSQL) & " characters long" WScript.StdIn.ReadLine Err.Clear End If tSQL = "" resultSet.MoveNext Loop resultSet.Close sConnect.Close Set sConnect = Nothing tConnect.Close Set tConnect = Nothing WScript.Quit(0)
I don't know why this is happening, but here is my workaround. I will not accept this as an answer, just wanted to document. Function allowed(n) allowed = true if n = 13 Then allowed = false if n = 14 Then allowed = false End Function Function sanitize(v,i) 'v=value i=index mystr = "" if allowed(i) Then if Not IsNull(v) Then mystr = replace(v,"'","") End If end if sanitize = mystr End Function Basically I'm just manually excluding the columns that have a problem. Notice that I identified a second one. What's really curious is that columns 12 and 13 have identical data in the source database, but column 12 copies fine.