need a faster way to match these data sets - vba

I have a set of Excel sheets, each set up as follows:
ID | imageName
--------------
1 abc.jpg
2 def.bmp
3 abc.jpg
4 xyz123.jpg
This sheet corresponds to a folder with contents like:
abc.pdf
ghijkl.pdf
def.pdf
def.xls
x-abc.pdf
I'm trying to generate a report that matches the instance of each imageName with the lowest ID with the PDFs that match it, and also identifies unmatched imageName in the sheet and unmatched PDFs in the folder. A filename with an "x-" prefix is equivalent to one without the prefix, so the report for this data set would be as follows:
ID imageName filename
-----------------------
1 abc.jpg abc.pdf
1 abc.jpg x-abc.pdf
2 def.bmp def.pdf
4 xyz123.jpg
ghijkl.pdf
My current solution is as follows:
'sheetObj is the imageName set, folderName is the path to the file folder
sub makeReport(sheetObj as worksheet,folderName as string)
dim fso as new FileSystemObject
dim imageDict as Dictionary
dim fileArray as variant
dim ctr as long
'initializes fileArray for storing filename/imageName pairs
redim fileArray(1,0)
'returns a Dictionary where key is imageName and value is lowest ID for that imageName
set imageDict=lowestDict(sheetObj)
'checks all files in folder and populates fileArray with their imageName matches
for each file in fso.getfolder(folderName).files
fileFound=false
'gets extension and checks if it's ".pdf"
if isPDF(file.name) then
for each key in imageDict.keys
'checks to see if base names are equal, accounting for "x-" prefix
if equalNames(file.name,key) then
'adds a record to fileArray mapping filename to imageName
addToFileArray fileArray,file.path,key
fileFound=true
end if
next
'checks to see if filename did not match any dictionary entries
if fileFound=false then
addToFileArray fileArray,file.path,""
end if
end if
next
'outputs report of imageDict entries and their matches (if any)
for each key in imageDict.keys
fileFound=false
'checks for all fileArray matches to this imageName
for ctr=0 to ubound(fileArray,2)
if fileArray(0,ctr)=key then
fileFound=true
'writes the data for this match to the worksheet
outputToExcel sheetObj,key,imageDict(key),fileArray(0,ctr)
end if
next
'checks to see if no fileArray match was found
if fileFound=false then
outputToExcel sheetObj,key,imageDict(key),""
end if
next
'outputs unmatched fileArray entries
for ctr=0 to ubound(fileArray,2)
if fileArray(1,ctr)="" then
outputToExcel sheetObj,"","",fileArray(0,ctr)
end if
next
This program outputs the report successfully, but it's very slow. Because of the nested For loops, as the number of imageName entries and files grows, the time to process them grows exponentially.
Is there a better way to check for matches in these sets? It might be faster if I make fileArray into a Dictionary, but a dictionary can't have duplicate keys, and this data structure needs to have duplicate entries in its fields, as a filename may match multiple imageNames and vice versa.

this should find the first one pretty quickly. you can do whatever you want at the inside of that last if statement. It uses an ADO recordset which should be faster than nested for loops
Sub match()
Dim sheetName As String: sheetName = "Sheet1"
Dim rst As New ADODB.Recordset
Dim cnx As New ADODB.Connection
Dim cmd As New ADODB.Command
'setup the connection
'[HDR=Yes] means the Field names are in the first row
With cnx
.Provider = "Microsoft.Jet.OLEDB.4.0"
.ConnectionString = "Data Source='" & ThisWorkbook.FullName & "'; " & "Extended Properties='Excel 8.0;HDR=Yes;IMEX=1'"
.Open
End With
'setup the command
Set cmd.ActiveConnection = cnx
cmd.CommandType = adCmdText
cmd.CommandText = "SELECT * FROM [" & sheetName & "$]"
rst.CursorLocation = adUseClient
rst.CursorType = adOpenDynamic
rst.LockType = adLockOptimistic
'open the connection
rst.Open cmd
Dim fso As FileSystemObject: Set fso = New FileSystemObject
Dim filesInFolder As files, f As File
Set filesInFolder = fso.GetFolder("C:\Users\Bradley\Downloads").files
For Each f In filesInFolder
rst.MoveFirst
rst.Find "imageName = '" & f.Name & "'", , adSearchForward
If Not rst.EOF Then
Debug.Print rst("imagename") & "::" & rst("ID") '<-- Do what you need to do here
End If
Next f
End Sub
FYI: I referenced this post

Another way.
Sub Sample()
Dim ws As Worksheet, wstemp As Worksheet
Dim FileAr() As String
Dim n As Long, wsLRow As Long
Set ws = Sheets("Sheet1") '<~~ Which has imageNames
wsLRow = ws.Range("A" & ws.Rows.Count).End(xlUp).Row
n = 0
strFile = Dir("C:\Temp\*.*")
Do While strFile <> ""
n = n + 1
ReDim Preserve FileAr(n)
If Mid(strFile, Len(strFile) - 3, 1) = "." Then
FileAr(n) = Mid(strFile, 1, Len(strFile) - 4)
ElseIf Mid(strFile, Len(strFile) - 4, 1) = "." Then
FileAr(n) = Mid(strFile, 1, Len(strFile) - 5)
Else
FileAr(n) = strFile
End If
strFile = Dir
Loop
Set wstemp = Worksheets.Add
wstemp.Range("A1").Resize(UBound(FileAr) + 1, 1).Value = Application.Transpose(FileAr)
ws.Range("B1:B" & wsLRow).Formula = "=IF(ISERROR(VLOOKUP(A1," & wstemp.Name & _
"!A:A,1,0)),"""",VLOOKUP(A1," & wstemp.Name & "!A:A,1,0))"
ws.Range("B1:B" & wsLRow).Value = ws.Range("B1:B" & wsLRow).Value
Application.DisplayAlerts = False
wstemp.Delete
Application.DisplayAlerts = True
End Sub

Thanks for the responses.
I ended up solving this by making an array of the filenames in folderName, using the WinAPI FindFirstFile and FindNextFile functions to go through the folder, because it's over a network so iterating through the collection returned by fso.getfolder(foldername).files was too slow.
I then made a filename/basename dictionary from the filename array, as:
key | value
-----------------------
abc.pdf | abc
x-lmnop.pdf | lmnop
x-abc.pdf | abc
From this dictionary I made a reverse dictionary fileConcat that concatenated keys from duplicate basenames, as:
key | value
-----------------------
abc | abc.pdf,x-abc.pdf
lmnop | lmnop.pdf
I was then able to match the basename for each imageDict key to a key in fileConcat, and then iterate through an array of the concatenated values generated by:
split(fileConcat(key))
where key is the basename of the imageDict key.
As #chrisneilsen commented, eliminating the nested For loops reduces the growth rate to O(ImageNames)+O(Files), and the function now performs at a satisfactory speed.

Related

How get Cells's value and check before open excel file use VBA in outlook

I tried to search value of cell from outlook script and check whether Does it exist in excel file, if yes, open excel file, else do nothing. I can open file and search where is that value in range. But my problem is I don't know how to search that value in range and get it's position without open excel file.
EDIT 1:
Here is my detail issue: EX:
I have a phone number at column "Phone Number". I would like to find where Column of "Phone Number" (because sometime it will change to another column). After I find position of column, I would like to search whether number "123876" is existed in that column (until this time excel file still close). Now, if number "123876" is existed, open that excel file, else do nothing.
Here is my code to search if file open
Sub test()
Dim objExcel As Object
Dim WB As Object
Dim WS As Object
'Open excel file
Set objExcel = CreateObject("Excel.Application")
objExcel.Visible = True
Set WB = objExcel.Workbooks.Open("D:\Book1.xlsm")
WB.Activate
Set WS = WB.Worksheets("Sheet1")
'Search position of column "Phone Number"
Phone_Number_Col = Chr(WS.Range("A:Z").Find("Phone Number", LookIn:=xlValues).Column + 64) 'It will return 5 and change to "E" for this column
'Search whether does my number is exist in this file
Dim range_1 As Range
Set Found_Nprod = WS.Range(Phone_Number_Col & ":" & Phone_Number_Col).Find("123876", LookIn:=xlValues)
If Not Found_Nprod Is Nothing Then 'found my number
MsgBox ("This value is existed")
Else ' not find my number
MsgBox ("This value is not existed in this file")
End If
End Sub
Above code just can find when excel file is opened. But my problem is how to find like that without open file, It just open file when that file have my number "123876"
EDIT 2:
I found a peace of code which can get value of cell without open. It's run ok But I don't know how to use find function with it.
This is my function I found
Sub ReadClosed()
'
' Credit this To Bob Umlas
'
Dim strPath As String
Dim strFile As String
Dim strInfoCell As String
strPath = "D:\"
strFile = "Book1.xlsm"
i = 3
strInfoCell = "'" & strPath & "[" & strFile & "]Sheet1'!R" & i & "C1"
MsgBox "In Cell A1 = " & ExecuteExcel4Macro(strInfoCell), vbInformation, strFile
Ok, this is how I can help you with - this is a code, that I am using, and it gives you the row of the wanted string, if you give it in which column to search. If it is not the wanted string, it returns -1. If you want the second repetitable of the string, you should give in the optional parameter l_more_values_found a value of 2. If your string is Phone and in the sheet it is Phones, you should set look_for_part as True. Prety much this is how it works. Lets imagine you have this:
If you run the MyTest Sub, you would get 4 as a result. 4 is the phone number 155, which is given as a parameter to l_locate_value_row. In your case, you can check once you know the column that it has to search, whether it returns -1.
Here comes the code:
Public Function l_locate_value_row(target As String, ByRef target_sheet As Worksheet, _
Optional l_col As Long = 2, _
Optional l_more_values_found As Long = 1, _
Optional b_look_for_part = False) As Long
Dim l_values_found As Long
Dim r_local_range As Range
Dim my_cell As Range
l_values_found = l_more_values_found
Set r_local_range = Nothing
target_sheet.Activate
Set r_local_range = target_sheet.Range(target_sheet.Cells(1, l_col), target_sheet.Cells(Rows.Count, l_col))
For Each my_cell In r_local_range
'The b_look_for_part is for the vertriebscase
If b_look_for_part Then
If target = Left(my_cell, Len(target)) Then
If l_values_found = 1 Then
l_locate_value_row = my_cell.Row
Exit Function
Else
l_values_found = l_values_found - 1
End If
End If
Else
If target = Trim(my_cell) Then
If l_values_found = 1 Then
l_locate_value_row = my_cell.Row
Exit Function
Else
l_values_found = l_values_found - 1
End If
End If
End If
Next my_cell
l_locate_value_row = -1
End Function
Public Sub MyTest()
Dim l_col As Long
l_col = l_locate_value_row("155", ActiveSheet, 3, 1, False)
Debug.Print l_col
End Sub
An ADODB Query is ideal for retrieving data from a closed workbook.
Phone Numbers
Test
Code
Function hasPhoneNumber(FilePath As String, PhoneNumber As Variant) As Boolean
Const adOpenStatic = 3, adLockOptimistic = 3, adCmdText = 1
Dim conn As Object, rs As Object
Set conn = CreateObject("ADODB.Connection")
Set rs = CreateObject("ADODB.Recordset")
conn.Open "Provider=Microsoft.ACE.OLEDB.12.0;" & _
"Data Source=" & FilePath & _
";Extended Properties=Excel 12.0;"
rs.Open "SELECT (Count([Phone Number]) > 0) AS hasPhoneNumber FROM [Sheet1$]" & _
" WHERE Cstr([Phone Number])='" & PhoneNumber & "';", conn, adOpenStatic, adLockOptimistic, adCmdText
hasPhoneNumber = CBool(rs!hasPhoneNumber)
On Error Resume Next
rs.Close
Set rs = Nothing
conn.Close
Set conn = Nothing
End Function

How to read the second last line in a text file

I would like to read a large file in VBA and saw this code online:
Dim MyChar As String, Pointer As Long, LastLine As String
Open "MyTextFile.Txt" For Binary As #1
Pointer = LOF(1) - 2
MyChar = Chr$(32)
Do
Get #1, Pointer, MyChar
If MyChar = vbCr Or MyChar = vbLf Then
Exit Do
Else: Pointer = Pointer - 1
LastLine = MyChar & LastLine
End If
Loop
MsgBox "Last Line is " & LastLine
How do I change this code to get the second last line? Need some help on this.
Thought of this:
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objTextFile = objFSO.OpenTextFile _
"MyTextFile.Txt", 1)
objTextFile.ReadAll
MsgBox objTextFile.Line
But I can't get to the line-2.
The code you're providing works as follows:
It sets a pointer to the last character of the file
It then reads that file backwards until it finds a linebreak
It returns all it has read as last line.
To modify this for your needs, I have added a Boolean secondRun, which lets the code run step 2 again, thus recording the 2nd last line:
Dim MyChar As String, Pointer As Long, LastLine As String
Open "MyTextFile.Txt" For Binary As #1
Pointer = LOF(1) - 2
MyChar = Chr$(32)
Dim secondRun As Boolean
Do
' Read character at position "Pointer" into variable "MyChar"
Get #1, Pointer, MyChar
If MyChar = vbCr Or MyChar = vbLf Then ' Linebreak = line read completely
If Not secondRun Then
' Run again if we've read only one line so far
secondRun = True
LastLine = ""
Pointer = Pointer - 2
Else
Exit Do
End If
Else: Pointer = Pointer - 1
' Add character to result String
LastLine = MyChar & LastLine
End If
Loop
MsgBox " 2nd last line is " & LastLine
Depends on your approach. But if the files are really that big then you probably don't want Excel to load the entire file. So, you'll probably open the files and read line by line without knowing how big the file is and how many rows it has. In that case it's probably easiest do just store two lines at a time in two separate string variables. As soon as you hit the last row you can exit your loop - as shown above in your code - and output not only the last row (as is already done in your code) but also the content of the second last row in that file.
Public Sub GetSecondLastRow()
Dim strSecondLastLine As String
Dim strFileToImport As String
Dim strLastLine As String
Dim intPointer As Integer
Dim lngCounter As Long
strFileToImport = ThisWorkbook.Path & IIf(InStr(1, ThisWorkbook.Path, "\") > 0, "\", "/") & "MyTextFile.txt"
intPointer = FreeFile()
Open strFileToImport For Input Access Read Lock Read As #intPointer
lngCounter = 0
Do Until EOF(lngCounter)
strSecondLastLine = strLastLine
Line Input #intPointer, strLastLine
lngCounter = lngCounter + 1
Loop
Close intPointer
Debug.Print "Content of the second last row:"
Debug.Print "---------------------------------------"
Debug.Print strSecondLastLine
Debug.Print "---------------------------------------"
Debug.Print "Content of the last row:"
Debug.Print "---------------------------------------"
Debug.Print strLastLine
End Sub
The alternative would be to first query the file for its row count and then get the second last record in that file using ADO. But I doubt that would be faster. The problem with ADO is that you get a huge recordset back containing the entire text file. This is due to the fact that you have no where restriction in the clause SELECT * from MyTextFile.txt. So, the entire text file goes into memory before you can do anything with it. Then - of course - you can check the RecordCount and go again through all records with a cursor fast forward until you hit the second last row. Unfortunately, ADO does not support
row_number() over (order by ##ROWCOUNT).
Otherwise, you could first get the row count with select count(1) from MyTextFile.txt and then afterwards only the applicable row.
So, in any case, I am almost certain (without having tested it) that ADO will perform below par and the first solution is the way to go if the text files are as big as you say. If you still prefer ADO then this is the code for that (based on the following SO question / answer: Copying text from .txt file in Excel using ADO ignores first row).
Sub ImportTextFile()
'Imports text file into Excel workbook using ADO.
'If the number of records exceeds 65536 then it splits it over more than one sheet.
Dim strFilePath As String, strFilename As String, strFullPath As String
Dim lngCounter As Long
Dim oConn As ADODB.Connection
Dim oRS As ADODB.Recordset
Dim oFSObj As Object
'Get a text file name
strFullPath = Application.GetOpenFilename("Text Files (*.txt),*.txt", , "Please select text file...")
If strFullPath = "False" Then Exit Sub 'User pressed Cancel on the open file dialog
'This gives us a full path name e.g. C:\temp\folder\file.txt
'We need to split this into path and file name
Set oFSObj = CreateObject("SCRIPTING.FILESYSTEMOBJECT")
strFilePath = oFSObj.GetFile(strFullPath).ParentFolder.Path
strFilename = oFSObj.GetFile(strFullPath).Name
'Open an ADO connection to the folder specified
Set oConn = New ADODB.Connection
oConn.Open "Provider=Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=" & strFilePath & ";" & _
"Extended Properties=""text;HDR=No;FMT=Delimited"""
Set oRS = New ADODB.Recordset
'Now actually open the text file and import into Excel
oRS.Open "SELECT count(1) FROM [" & strFilename & "]", oConn, 3, 1, 1
Range("A1").CopyFromRecordset oRS
Set oRS = New ADODB.Recordset
'Now actually open the text file and import into Excel
oRS.Open "SELECT * FROM [" & strFilename & "]", oConn, 3, 1, 1
While Not oRS.EOF And Not oRS.BOF
If oRS.AbsolutePosition = Range("A1").Value2 Then
Range("A2").Value = oRS.Fields(0).Value
End If
oRS.MoveNext
Wend
oRS.Close
oConn.Close
End Sub
You can try this:
Public Function GetSecondLastLine(sFileName As String, Optional sLineDelimiter As String = vbCrLf) As String
Dim sContent As String
Dim aLines() As String
sContent = TextFromFile(sFileName)
aLines = Split(sContent, sLineDelimiter)
GetSecondLastLine = aLines(UBound(aLines) - 1)
End Function
Public Function TextFromFile(sFileName As String) As String
Dim lFile As Long
lFile = FreeFile
Open sFileName For Input As #lFile
TextFromFile = Input$(LOF(lFile), lFile)
Close #lFile
End Function
If necessary, you can change the line delimiter (e.g. vbCR of vbLF)
"True to request" version:
(Improved on #Verzweifler's answer (imho))
Linux compatible (Linebreaks with LF only instead of CR LF possible)
Accounting for multiple open files
Using an empty, fixed-length defined string as char (no assignment needed)
'Variables
Dim Path As String
Path = "MyTextFile.txt"
Dim FileNumber As Long
FileNumber = FreeFile 'Use first unused file number
Dim Pointer As Long
Dim Char As String * 1 'String of fixed length 1
Dim SecondLastLine As String
Dim SecondRun As Boolean
'Read last two lines of file
Open Path For Binary As FileNumber
Pointer = LOF(FileNumber) 'Set pointer to last file position
Do
Get FileNumber, Pointer, Char 'Read char at position "Pointer" into "Char"
If Char = vbCr Then
Pointer = Pointer - 1 'Just skip CRs for Linux compat
ElseIf Char = vbLf Then
If Not SecondRun Then
SecondRun = True
SecondLastLine = vbNullString
Pointer = Pointer - 1
Else
Exit Do
End If
Else
Pointer = Pointer - 1
SecondLastLine = Char & SecondLastLine 'Add char to result String
End If
Loop
Close FileNumber
MsgBox " 2nd last line is " & SecondLastLine
Extended version:
Gets Count number of last lines
'Variables
Dim Path As String
Path = "MyTextFile.txt"
Dim Count As Long
Count = 2
Dim FileNumber As Long
FileNumber = FreeFile 'Use first unused file number
Dim Pointer As Long
Dim Char As String * 1 'String of fixed length 1
Dim CurrentLineNumber As Long
CurrentLineNumber = 0
Dim LastLines() As String
ReDim LastLines(0 To Count - 1)
'Read Count last lines of file
Open Path For Binary As FileNumber
Pointer = LOF(FileNumber) 'Set pointer to last file position
Do
Get FileNumber, Pointer, Char 'Read char at position "Pointer" into "Char"
If Char = vbCr Then
Pointer = Pointer - 1 'Just skip CRs for Linux compat
ElseIf Char = vbLf Then
If CurrentLineNumber < Count - 1 Then
CurrentLineNumber = CurrentLineNumber + 1
Pointer = Pointer - 1
Else
Exit Do
End If
Else
Pointer = Pointer - 1
LastLines(CurrentLineNumber) = Char & LastLines(CurrentLineNumber) 'Add char to result String
End If
Loop
Close FileNumber
Dim Line As Variant
For Each Line In LastLines
Debug.Print Line
Next

VBA Access: Import CSV with additonal header data

I am new to coding VBA. Was wondering if you all could help me? I have a CSV file which is structured as the following:
- First 22 rows cover the specfic header data(this all loads in one column in excel)
- column headers for table are in Row 23
- the data is actually located from row 24 onward.
What the code needs to do is insert this data in new table with the right column titles. Also while inserting it needs to input the file name and header data in the first few columns of the table.
So far I have imported the entire CSV into an array I believe:
See what I have so far:
Sub readCSV()
Dim fs As Object
Dim fso As New FileSystemObject
Dim tsIn As Object
Dim sFileIn, filename As String
Dim aryFile, aryHeader, aryBody As Variant
sFileIn = "C:\doc\test.csv"
Set filename = fso.GetFileName(sFileIn)
Set fs = CreateObject("Scripting.FileSystemObject")
Set tsIn = fs.OpenTextFile(sFileIn, 1)
sTmp = tsIn.ReadAll
aryFile = Split(sTmp, vbCrLf)
For i = 1 To 22
aryHeader(1, i) = aryFile(i)
Next i
For i = 23 To UBound(aryFile)
aryBody(i) = Split(aryFile(i), ",")
DoCmd.RunSQL "INSERT INTO MAINS VALUES (filename,aryHeader(1),aryBody(i))"
Next i
End Sub
is this correct? Can anyone see of i am taking the right approach
UPDATE - recoded this a bit
Use DoCmd.TransferText instead of rolling out your own code:
http://msdn.microsoft.com/en-us/library/office/ff835958%28v=office.15%29.aspx
In your Import Specification, you can set the starting row.
See Skip first three lines of CSV file (using DoCmd?) in MS Access for more information!
Edit: The import specification can be changed to rename the fields etc. See http://www.access-programmers.com/creating-an-import-specification-in-access-2003.aspx (the Import wizard exists in Access 2007 as well) and the Advanced dialog specifically.
I was a bit irked by the use of multiple arrays in your code (which is super confusing, to me, anyway, because you are looking at counters everywhere) so I thought I would post an alternative for you. If you can do it your way, more power to you, but if you run into problems, you can try this. Code below is much more verbose, but may save you time in the future if you hand it off or even have to come back to it yourself and have no idea what is going on (lol):
Sub ReadCSV()
On Error GoTo ErrorHandler
Dim db As DAO.Database
Dim rst As DAO.Recordset
Dim fso As Scripting.FileSystemObject
Dim tst As Scripting.TextStream
Dim strFileName As String
Dim intCurrentLine As Integer
Dim strCurrentLine As String
Dim intHeaderRows As Integer
Dim strHeader As String
Dim strHeaderDelimInField As String
'Consider these your 'constants', so you don't come back to this code in a month
'and wonder what the random numbers mean.
intHeaderRows = 22 'Number of header rows in CSV.
strHeaderDelimInField = "~" 'The character(s) you want to separate each
'header line, in field.
strFileName = "C:\IrregularCSV.csv"
intCurrentLine = 1 'Keep track of which line in the file we are currently on.
'Next two lines get a reference to your table; will add data via DAO and not SQL,
'to avoid messy dynamic SQL.
Set db = CurrentDb()
Set rst = db.OpenRecordset("Mains", dbOpenDynaset)
Set fso = New Scripting.FileSystemObject
Set tst = fso.OpenTextFile(strFileName, ForReading)
'Instead of storing data in arrays, let's go through the file line by line
'and do the work we need to do.
With tst
Do Until .AtEndOfStream
strCurrentLine = .ReadLine
If intCurrentLine <= intHeaderRows Then
strHeader = strHeader & strHeaderDelimInField & strCurrentLine
Else
'Add the records via DAO here.
rst.AddNew
'In DAO, rst.Fields("FieldName") are the columns in your table.
rst.Fields("FileName") = strFileName
'Remove leading delimiter with Right.
rst.Fields("HeaderInfo") = Right(strHeader, Len(strHeader) - 1)
'Note that Split always returns a zero-based array
'and is unaffected by the Option Base statement.
'The way below is less efficient than storing
'the return of Split, but also less confusing, imo.
rst.Fields("Field1") = Split(strCurrentLine, ",")(0)
rst.Fields("Field2") = Split(strCurrentLine, ",")(1)
rst.Fields("Field3") = Split(strCurrentLine, ",")(2)
rst.Update
End If
intCurrentLine = intCurrentLine + 1
Loop
End With
tst.Close
rst.Close
ExitMe:
Set tst = Nothing
Set fso = Nothing
Set rst = Nothing
Set db = Nothing
Exit Sub
ErrorHandler:
Debug.Print Err.Number & ": " & Err.Description
GoTo ExitMe
End Sub
To be honest, I think there are a lot of gotchas to the way you are going about it. Not saying it won't work, because I think it can, but this method is more robust. An unexpected single quote won't ruin your work and using a data object to do the inserts is not prone (well, less, at least) to SQL injection issues. And I've done it with no persisted arrays. Anyway, some food for thought. Good luck.
this is what i ended up:
Sub ReadCSV2()
Dim fs As Object
Dim filename As String
Dim tsIn As Object
Dim sFileIn As String
Dim aryHeader, aryBody As Variant
Dim Text As String
Dim sqlcre As String
Dim sqlsta As String
sFileIn = "C:\test\test.csv"
filename = GetFilenameFromPath(sFileIn) 'function to get the file name
Set fs = CreateObject("Scripting.FileSystemObject")
Set tsIn = fs.OpenTextFile(sFileIn, 1)
For i = 1 To 23
Tmps = tsIn.ReadLine
Next i
aryHeader = Split(Tmps, ",")
On Error Resume Next
DoCmd.RunSQL "DROP TABLE tempdata"
On Error GoTo 0
sqlcre = "CREATE TABLE tempdata ([Filename] Text,"
For k = LBound(aryHeader) To UBound(aryHeader)
sqlcre = sqlcre & "[" & aryHeader(k) & " " & k + 1 & "] Text,"
Next k
k = k - 1
sqlcre = Left(sqlcre, Len(sqlcre) - 13) & ")"
'Debug.Print k
'Debug.Print sqlcre
DoCmd.RunSQL sqlcre
DoCmd.SetWarnings False
While Not tsIn.AtEndOfStream
Tmps = tsIn.ReadLine
aryBody = Split(Tmps, ",")
sqlsta = "INSERT INTO tempdata VALUES ('" & filename & "','"
For M = LBound(aryBody) To UBound(aryBody)
sqlsta = sqlsta & Replace(aryBody(M), "'", "`") & "', '"
Next M
M = M - 1
Debug.Print M
If M < k Then
Text = ""
For i = 1 To (k - M)
Text = Text & "', '"
Next i
sqlsta = sqlsta & Text
End If
sqlsta = Left(sqlsta, Len(sqlsta) - 7) & ")"
'Debug.Print sqlsta
'Debug.Print k
DoCmd.RunSQL sqlsta
Wend
DoCmd.SetWarnings True
End Sub

Looping Macro in Excel

I would like to loop through an Excel worksheet and to store the values based on a unique ID in a text file.
I am having trouble with the loop and I have done research on it with no luck and my current nested loop continually overflows. Instead of updating the corresponding cell when the control variable is modified, it continues to store the initial Index value for all 32767 iterations.
Please can someone explain why this is happening, and provide a way of correcting it?.
Sub SortLetr_Code()
'sort columns for Letr_Code files
Dim lr As Long
Application.ScreenUpdating = False
lr = Cells(Rows.Count, 1).End(xlUp).Row
Range("A2:B" & lr).Sort key1:=Range("B2"), order1:=1
Application.ScreenUpdating = True
'Value of cell for example B1 starts out as X
Dim x As Integer
Dim y As Integer
x = 2
y = 2
'Cell References
Dim rwCounter As Range
Dim rwCorresponding As Range
Dim rwIndexValue As Range
Dim rwIndexEnd As Range
Dim rwIndexStore As Range
'Variables for files that will be created
Dim FilePath As String
Dim Filename As String
Dim Filetype As String
'Variables defined
FilePath = "C:\Users\Home\Desktop\SURLOAD\"
Filetype = ".dat"
'Use Cell method for Loop
rwIndex = Cells(x, "B").Value
Set rwCounter = Range("B" & x)
'Use Range method for string manipulation
Set rwCorresponding = Range("A" & x)
Set rwIndexValue = Range("B" & y)
Set rwIndexStore = Range("B" & x)
Set rwIndexEnd = Range("B:B").End(xlUp)
'Objects for creating the text files
Dim FileCreate As Object
Set FileCreate = CreateObject("Scripting.FileSystemObject")
'Object for updating the file during the loop
Dim FileWrite As Object
For Each rwIndexStore In rwIndexEnd.Cells
'Get Substring of cell value in BX for the file name
Do Until IsEmpty(rwCounter)
Filename = Mid$(rwIndexValue, 7, 5)
Set FileWrite = FileCreate.CreateTextFile(FilePath + Filename + Filetype)
'Create the file
FileWrite.Write (rwCorresponding & vbCrLf)
Do
'Add values to the textfile
x = x + 1
FileWrite.Write (rwCorresponding & vbCrLf)
Loop While rwCounter.Value Like rwIndexValue.Value
'Close this file
FileWrite.Close
y = x
Loop
Next rwIndexStore
End Sub
I don't see a place you are setting rwCounter inside the loop.
It looks like it would stay on range("B2") and x would just continue to increase until it hits an error, either at the limit of integer or long.
add Set rwCounter = Range("B" & x) somewhere inside your loop to increment it
This is the solution.
Sub GURMAIL_File()
'sort columns for Letr_Code files
Dim lr As Long
Application.ScreenUpdating = False
lr = Cells(Rows.Count, 1).End(xlUp).Row
Range("A2:B" & lr).Sort key1:=Range("B2"), order1:=1
Application.ScreenUpdating = True
'Variables that store cell number
Dim Corresponding As Integer
Dim Index As Integer
Dim Counter As Integer
Corresponding = 2
Index = 2
Counter = 2
'Cell References
Dim rwIndexValue As Range
'Variables for files that will be created
Dim l_objFso As Object
Dim FilePath As String
Dim Total As String
Dim Filename As String
Dim Filetype As String
Dim FolderName As String
'Variables defined
FilePath = "C:\Users\Home\Desktop\SURLOAD\"
'Name of the folder to be created
FolderName = Mid$(ActiveWorkbook.Name, 9, 8) & "\"
'Folder path
Total = FilePath & FolderName
'File Extension
Filetype = ".dat"
'Object that creates the folder
Set l_objFso = CreateObject("Scripting.FileSystemObject")
'Objects for creating the text files
Dim FileCreate As Object
Set FileCreate = CreateObject("Scripting.FileSystemObject")
'Object for updating the file during the loop
Dim FileWrite As Object
'Get Substring of letter code in order to name the file. End this loop once ID field is null.
Do While Len(Range("A" & Corresponding)) > 0
'Create the directory if it does not exist
If Not l_objFso.FolderExists(Total) Then
l_objFso.CreateFolder (Total)
End If
'Refence to cell containing a letter code
Set rwIndexValue = Range("B" & Index)
'Substring of that letter code
Filename = Mid$(rwIndexValue, 7, 5)
'Create the file using the substring and store it in the proper location
Set FileWrite = FileCreate.CreateTextFile(Total + Filename + Filetype, True)
'For each letter code, find the corresponding values. End the loop once the last value for the letter code is stored.
Do While Range("B" & Index) Like Range("B" & Counter)
'Add each line to the text file.
FileWrite.WriteLine (Range("A" & Corresponding))
'Incrementer variables that allow you to exit the loop
'if you have reached the last value of the current letter code.
Corresponding = Corresponding + 1
Counter = Counter + 1
Loop
'Close the file you were writing to
FileWrite.Close
'Make sure that Index value is updated to the next letter code
Index = Counter
'In case Index value needs updating (safeguard to make sure that the new letter code is stored to index value).
Set rwIndexValue = Range("B" & Index)
Loop
End Sub

VBA Access 2010 - prompt user to pick a file and dim the filename as variable

my Access database exports a report in xls, that needs to be further reworked (some manual adjustments of columns etc. + vlookuping some comments from report from previous day).
Here is the part of the code I created so far:
Option Compare Database
Function Adjustment()
' First I want to prompt user to select the report from previous day*
Dim f As Object
Dim strFile As String
Dim strFolder As String
Dim varItem As Variant
Set f = Application.FileDialog(3)
f.AllowMultiSelect = True
If f.Show Then
For Each varItem In f.SelectedItems
strFile = Dir(varItem)
MsgBox (strFile)
Next
End If
Set f = Nothing
' here my Access database opens current report that has been exported
Dim xl As Object
Set xl = CreateObject("Excel.Application")
xl.Workbooks.Open ("I:\Temp\reports.xlsx")
xl.Visible = True
' in currently open report, I want fill cell I2 and J2 with VLOOKUP function referencing to previously selected file
Range("I2").FormulaR1C1 = "=VLOOKUP(RC7,'[" & strFile & "]SheetXY'!C7:C12,3,0)"
Range("J2").FormulaR1C1 = "=VLOOKUP(RC7,'[" & strFile & "]SheetXY!C7:C12,4,0)"
End function
Problem: I am being prompted every every time to select the file, when formula is being filled in I2 and J2, so how can I disable this and keep Access to reference strFile only once?
Question: So far, every first sheet in the refrenced workbook is called SheeyXY, but what if I would like to reference also a different Sheets (let`s say always the first sheet in the workbook no matter what its name is).
Maybe you can try this ..
Option Compare Database
Function Adjustment(SheetName as String) '---> add parameter such as "SheetXY"
' First I want to prompt user to select the report from previous day*
Dim f As Object
Dim strFolder As String
Dim varItem As Variant
Static strFile As String
If strFile = "" Then
Set f = Application.FileDialog(3)
f.AllowMultiSelect = True
If f.Show Then
For Each varItem In f.SelectedItems
strFile = Dir(varItem)
MsgBox (strFile)
Next
End If
Set f = Nothing
Endif
' here my Access database opens current report that has been exported
Dim xl As Object
Set xl = CreateObject("Excel.Application")
xl.Workbooks.Open ("I:\Temp\reports.xlsx")
xl.Visible = True
' in currently open report, I want fill cell I2 and J2 with VLOOKUP function referencing to previously selected file
Range("I2").FormulaR1C1 = "=VLOOKUP(RC7,'[" & strFile & "]" & SheetName & "'!C7:C12,3,0)"
Range("J2").FormulaR1C1 = "=VLOOKUP(RC7,'[" & strFile & "]" & SheetName & "'!C7:C12,4,0)"
End function
Have you tried taking out the .FormularR1C1 from those lines?
Also, I'm not sure exactly what you're trying to do with the sheet names, but you could probably hack something together from this?
Debug.Print Worksheets(1).Name
or
For Each ws In Worksheets
Debug.Print ws.Name
Next
UPDATE:
Try this, and report back?
With xl.Worksheets(1)
.Range("I2").FormulaR1C1 = "=VLOOKUP(RC7,[" & strFile & "]Sheet1!C7:C12,3,0)"
.Range("J2").FormulaR1C1 = "=VLOOKUP(RC7,[" & strFile & "]Sheet1!C7:C12,4,0)"
End With
So with the extra clarification of which range, and without the extra apostrophe