My difficulty is this: I have a Excel spreadsheet where I need a UserForm ListBox to populate itself with some rows of data depending on previous user input.
So the UserForm should pick up the user's input in Sheet1 (say it's 123abc) and then search for similar results in Sheet2. The nearest results (probably a range of about five similar results) should then fill the listbox.
So it could return something like 123a, 123ab, 1235c, and 1355abc from the list
Hope this makes sense - thanks for your help in advance.
The code I've tried (and doesn't work, turns a 1004) with notes for clarification:
Private Sub UserForm_Initialize()
Dim cs As Worksheet
Dim c As Range
Dim i As Integer
Dim code As Variant
Dim co As String
Set code = Sheets("Sheet1").Range("B4") 'picks up user input
co = Left(code, 2) 'just takes first two letters of input to find similar
Set cs = Sheets("Sheet2")
Set c = cs.Range("A2:A20000").Find(co) 'find code (obviously :))
For i = 1 To cs.Cells(c.Offset(5, 0), 1).End(xlUp).Row Step 1
If cs.Cells(i, 1).Value <> vbNullString Then Me.ListBox1.AddItem cs.Cells(i, 1).Value
Next i 'populate listbox
End Sub
Many thanks!
EDIT
I have marked the below as the accepted answer, but for others browsing this, the problem still exists as to integrating this with the template Excel files, as it returns the error 'External table is not in the expected format'. I had to change ThisWorkbook.FullName to the full template path to remove another error too.
Below is the aforementioned example. The code is mostly commented with what is needed. Please feel free to ask any questions.
I did a quick performance test searching for a text string in a data set of about 65,000 records. It look less than a second.
Here's the code:
'Making these variable public so they don't need to be recreated each time
'Make sure you add a reference to ADO through the tool-->references menu
Public MyConnection As New ADODB.Connection
Public MyRecordset As New ADODB.Recordset
Private Sub UserForm_Initialize()
Dim ColumnName As String: ColumnName = "[Number]" ' The name of the Column in Sheet2 you are searching for, it should have a header in row 1
Dim SearchStr As String: SearchStr = Left(Sheets("Sheet1").Range("B4").Value2, 2) ' This is what will be searched
If MyConnection.State <> adStateOpen Then
With MyConnection
'Connection string below is assuming you are using an XLSX file 2007 or higher
'It also assumes Sheet2 has column headers, e.g. HDR=Yes in the connection string
.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & ThisWorkbook.FullName & _
";Extended Properties='Excel 12.0 Xml;HDR=YES;IMEX=1';"
.Open
End With
End If
If MyRecordset.State = adStateOpen Then MyRecordset.Close ' Make sure recordset is closed when open
MyRecordset.Open "Select top 5 " & ColumnName & " from [Sheet2$] where " & ColumnName & _
" like '%" & SearchStr & "%'", MyConnection, adOpenForwardOnly, adLockReadOnly
Me.ListBox1.Clear ' Clear all entries from the listbox
If Not MyRecordset.EOF Then MyRecordset.MoveFirst 'Move to start of recordset
Do Until MyRecordset.EOF 'Loop recordset and add all the items
Me.ListBox1.AddItem MyRecordset.Fields(0).Value
MyRecordset.MoveNext
Loop
End Sub
Related
I am trying to select a single cell value in an excel spreadsheet named "AtwickShortfall" as follow
StrSql = "SELECT AtwickShortfall FROM [" & Glob_WsheetNameTradeLogOpsActions & "$AtwickShortfall]"
obviously does not work; can someone tell me the correct syntax ?
AtwickShortfall cell value is the sum of other values in the speadsheet; there are no tables in this spreadsheet at all.
UPDATE 1
tried this syntax
StrSql = "SELECT [AtwickShortfall] FROM [" & Glob_WsheetNameTradeLogOpsActions & "$]"
I get an error "No value given for one or more required parameters"
UPDATE 2
after amending my code as per #Dick Kusleika answer I now have this code
StrSql = "SELECT * FROM [AtwickShortfall]" ' & Glob_WsheetNameTradeLogOpsActions & "$AH34]"
Debug.Print (StrSql)
Set RecSet = ConnObj.Execute(StrSql)
If Not (RecSet Is Nothing) Then
With RecSet
If Not (.BOF) And Not (.EOF) Then
Debug.Print RecSet.Fields(0).Value
Else
'UdtKpi.HornInjMeterSum = 0
End If
End If
End With
End If
no errors but the recodset is empty (BOF is true); the value of AtwickShortfall is a formula that gives 0 at the moment. I also tried to remove the formula and fill the cell with 0 but same result: recordset BOF is true
UPDATE 3
I found a solution but please have a look at the screenshot below
cell AtwickShortfall is the one that I have now added a value of 125 on the column AH why this code work ?
StrSql = "SELECT Atw FROM [Operational_Actions$AH33:AH34]"
why it recognize the "atw" as a header of the range I gave??
If I change the above query to
StrSql = "SELECT Atw FROM [Operational_Actions$AH34:AH34]"
I get an epty recordset; It does not make sense how excel works to me; note that I have not implemented the worksheet data, it has been given to me
I think because the named range is at the workbook level (rather than worksheet level) that you can't specify what worksheet it's on. It's looking for a worksheet level named range when you specify the worksheet. This worked for me.
Sub getcell()
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Set cn = New ADODB.Connection
cn.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=\\99991-dc01\99991\dkusleika\My Documents\testadonamedrange.xlsx;Extended Properties=""Excel 12.0 Xml;HDR=NO"";"
Set rs = cn.Execute("select * from [AtwickShortfall]")
Debug.Print rs.Fields(0).Value
rs.Close
cn.Close
Set rs = Nothing
Set cn = Nothing
End Sub
I have a code in vba through which whenever i will save any new value in a particular cell it will show in the message box that what was the old value stored in the cell and what was the new value which i have just saved below is the code for that
Option Explicit
Dim OldVals As New Dictionary
Private Sub Worksheet_Change(ByVal Target As Range)
Dim myCell As Range
For Each myCell In Target
If OldVals.Exists(myCell.Address) Then
MsgBox "New value of " & Replace(myCell.Address, "$", "") & " is " & myCell.Value & "; old value was " & OldVals(myCell.Address)
Else
MsgBox "No old value for " + Replace(myCell.Address, "$", "")
End If
OldVals(myCell.Address) = myCell.Value
Next myCell
End Sub
the output window of the code will come like this in the picture below --
so i want to export the values which was displayed in the message box to the access database table using vba one after the another
however i have written a code to export and save the values of excel sheet cells into access database table the code is below
Const TARGET_DB = "\Database3.accdb"
Sub PushTableToAccess()
Dim cnn As ADODB.Connection
Dim MyConn
Dim rst As ADODB.Recordset
Dim i As Long, j As Long
Dim Rw As Long
Sheets("Sheet1").Activate
Rw = Range("A1").End(xlDown).Row
Set cnn = New ADODB.Connection
MyConn = ThisWorkbook.Path & Application.PathSeparator & TARGET_DB
With cnn
.Provider = "Microsoft.ACE.OLEDB.12.0"
.Open MyConn
End With
Set rst = New ADODB.Recordset
rst.CursorLocation = adUseServer
rst.Open Source:="Table1", ActiveConnection:=cnn, _
CursorType:=adOpenDynamic, LockType:=adLockOptimistic, _
Options:=adCmdTable
For i = 2 To Rw
rst.AddNew
For j = 1 To 3
rst(j) = Cells(i, j).Value
Next j
rst.Update
Next i
rst.Close
cnn.Close
Set rst = Nothing
Set cnn = Nothing
End Sub
the above code will export and save all the values in excel cells to access database table .
but i am not sure how to combine both the codes so that my first code whenever it will display the old and new values of cell and when i click on OK button it will export and save the value displayed in message box (eg- new value of A1 is 7 ; old value was 88) to access database table one after the another .
It seems you want to make an audit/logging function of all changes to a an Excel workbook.
You have two pieces of code, one to identify the change and one to write information to a datbase, an you want to combine this. The resulting functionality wold then be to write every change the user makes to a database.
The code you have should give you enough guidance on the particular VBA statements. I'll limit this solution to the approach.
As you will need the database connection during the whole time the user has the worksheet opened, you should make the database connection in the Workbook Open event:
Public cnn As ADODB.Connection
Public MyConn
Private Sub Workbook_Open()
Set cnn = New ADODB.Connection
MyConn = ThisWorkbook.Path & Application.PathSeparator & TARGET_DB
With cnn
.Provider = "Microsoft.ACE.OLEDB.12.0"
.Open MyConn
End With
End Sub
Then you continue in the Change events:
Private Sub Worksheet_Change(ByVal Target As Range)
'.... (your code to get the change)
Set rst = New ADODB.Recordset
rst.AddNew ' allocate new record
rst(j) = Cells(i, j).Value ' populate the record (this must be your code)
rst.Update ' update/insert record
rst.Close ' done record.
End Sub
Finally you close the database in the Workbook_BeforeClose event.
I'm currently working on an Excel application that can get info from my Form worksheet and display it into Display worksheet. So far the data can transfer perfectly, except for one tiny quirk.
The Display worksheet will display data when Execute is clicked.
Here is the screenshot:
Before I execute the Display, the Time field in the Form worksheet is formatted as h:mm AM/PM. However, when I click Execute in Display worksheet, the Time format suddenly changed in the Form Worksheet as seen here:
Furthermore the change in format is also seen in the Display table as well. I tried setting the format to be identical for both worksheets and the result is still the same.
Is it the issue of the SQL1 statement or the coding in general? here is the code sample.
Public Sub QueryWorksheet(szSQL As String, rgStart As Range, wbWorkBook As String, AB As String)
Dim rsData As ADODB.Recordset
Dim szConnect As String
On Error GoTo ErrHandler
If AB = "1st" Then
wbWorkBook = ActiveWorkbook.Sheets("Inner Workings").Range("B9").Text
End If
Application.StatusBar = "Retrieving data ....."
'Set up the connection string to excel - thisworkbook
szConnect = "Provider=Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=" & wbWorkBook & ";" & _
"Extended Properties=Excel 8.0;"
Set rsData = New ADODB.Recordset
'Run the query as adCmdText
rsData.Open szSQL, szConnect, adOpenForwardOnly, adLockReadOnly, adCmdText
'Check if data is returned
If Not rsData.EOF Then
'if the recordset contains data put them on the worksheet
rgStart.CopyFromRecordset rsData
Else
End If
'Close connection
rsData.Close
'Clean up and get out
Set rsData = Nothing
Application.StatusBar = False
Exit Sub
ErrHandler:
'an error occured in the SQL-statement
MsgBox "Your query could not be executed, the SQL-statement is incorrect."
Set rsData = Nothing
Application.StatusBar = False
End Sub
Sub process()
Call clear
Call testsql("1st") ' populate 1st Summary
Call testsql("2nd") ' find Date+Time
Call testsql("3rd") ' GET LATEST RECORD
End Sub
Sub testsql(AB As String)
Dim rgPlaceOutput As Range 'first cell for the output of the query
Dim stSQLstring As String 'text of the cell containing the SQL statement
Dim rg As String, SQL As String
If AB = "1st" Then
stSQLstring = ActiveWorkbook.Sheets("Inner Workings").Range("B2").Text
Set rgPlaceOutput = ActiveWorkbook.Sheets("1st Summary").Range("A2")
End If
If AB = "2nd" Then
stSQLstring = ActiveWorkbook.Sheets("Inner Workings").Range("B3").Text
Set rgPlaceOutput = ActiveWorkbook.Sheets("2nd Summary").Range("A2")
End If
If AB = "3rd" Then
stSQLstring = ActiveWorkbook.Sheets("Inner Workings").Range("B4").Text
Set rgPlaceOutput = ActiveWorkbook.Sheets("Final Summary").Range("A5")
End If
QueryWorksheet stSQLstring, rgPlaceOutput, ThisWorkbook.FullName, AB
End Sub
Sub clear()
ActiveWorkbook.Sheets("1st Summary").Range("A2:BR5000").Value = Empty
ActiveWorkbook.Sheets("2nd Summary").Range("A2:BR5000").Value = Empty
ActiveWorkbook.Sheets("Final Summary").Range("A2:BR5000").Value = Empty
End Sub
If anyone can help with this, I greatly appreciate it.
Update:
Apparently, this quirk is larger than I thought. After more testing I found out that the second summary sheet is also affected as well as seen here. . Albeit the lower half at least. The mystery keeps piling up...
I think you have to look at NumberFormat iirc add something like "DD/MM/YYYY" to a Range (columns in your case). I'm on a Mac with Office 365, and although VBA is now in the app, the sort of intellisense is absent so unless you know the Excel Object model by heart it's a royal PITA!
I have a project where I want to automate the reporting of a stupidly large set of data in Excel VBA. Basically I have a 1,000,000+ record database that I want to pull ~100,000 records from. The only way I have the 100,000 items is in an excel sheet, and I can't dump it into the same database to filter or into a temp table on the same server.
Is there a way to treat the Excel spreadsheet values as a database and call it within the SQL query in the VBA? I'd prefer not to use a loop because the database response is already poor enough as it is.
Ideas?
Thanks.
Edit - My assumption of "looping being bad" is incorrect according to one comment. Is this true? Would looping through the ID's have to poll the database 100,000 times or does it treat it as a single data pull?
You can use ADODB if you want to query within your workbook using sql like syntax.
I have included a sub to as an example of how to do this. You could call said sub like below:
Call queryTable("select top 100000 * from [Sheet6$A1:AI31]", range("Sheet5!A1"))
This would query data located in the range Sheet6$A1:AI31 (the first row being the headers) and would dump the data starting with the top left cell being Sheet5!A1.
Sub queryTable(sqlStr As String, destination As Range)
Dim strFile As String
Dim stADO As String
Dim cnt As ADODB.Connection
Dim recordcount As Long
Dim fieldcount As Long
Dim resultrange As Range
Dim mydestination As Range
strFile = ThisWorkbook.FullName
'connection string may need to be altered a little bit depending on your excel version
stADO = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & strFile _
& ";Extended Properties=""Excel 12.0;HDR=Yes;IMEX=1"";"
Set cnt = New ADODB.Connection
'Running query
With cnt
.CursorLocation = adUseClient
.Open stADO
.CommandTimeout = 0
Set rst = .Execute(sqlStr)
End With
Set mydestination = destination.Cells(1, 1).Offset(1, 0)
'Copying data (not headers) to destination
mydestination.CopyFromRecordset rst
'Setting some important variables
recordcount = rst.recordcount
fieldcount = rst.Fields.Count
Set range_collection = Range(mydestination.Cells(1, 1).Offset(-1, 0), mydestination.Cells(1, 1).Offset(recordcount - 1, fieldcount - 1))
'Copying the headers
For i = 0 To fieldcount - 1
mydestination.Cells(1, 1).Offset(-1, i).value = rst.Fields(i).name
Next i
'Closing everything down
cnt.Close
Set cnt = Nothing
End Sub
I have a range I'd like to arbitrarily sort and filter using vba. I don't, however, want it to affect the worksheet. I'd like to essentially copy the range into some native class that supports filtering and sorting (so i don't have to reinvent the wheel) and use that class to return a result to calling code.
Are there any classes I can use to this end? ListObject looked the most promising but it appears to require being tied to a worksheet range to work properly.
You can use recordsets. Here are some notes:
'Reference: Microsost ActiveX n.n Object Library '
Dim rs As ADODB.Recordset
Dim cn As ADODB.Connection
'From: http://support.microsoft.com/kb/246335 '
strFile = Workbooks(1).FullName
strCon = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & strFile _
& ";Extended Properties=""Excel 8.0;HDR=Yes;IMEX=1"";"
Set cn = CreateObject("ADODB.Connection")
Set rs = CreateObject("ADODB.Recordset")
cn.Open strCon
'Must have client-side cursor for sorting '
rs.CursorLocation = adUseClient
'Substitute a name range for [Sheet1$] '
'or include a range of cells : [Sheet1&A1:C7] '
strSQL = "SELECT * FROM [Sheet1$] " _
& "WHERE TransID>2 ORDER BY MyTime"
rs.Open strSQL, cn, 3, 3
rs.Filter = "TransID=3"
rs.Sort = "Mytime"
'Write out to another sheet '
Worksheets(2).Cells(2, 1).CopyFromRecordset rs
You may find this thread interesting: syncing two lists with VBA
If you'd like to read and parse complex sets of data you can use the Microsoft ActiveX Data Objects Recordset 2.8 Library. With this you can read your data into a recordset, then filter, sort, append, delete and pass it to other functions.
I regularly use this, because i often have to manipulate and display large datasets. If it's in a recordset i can use the same manipulation and presentation routines over and over again.
See Merge Excel Sheets.... for an example of throwing data into a recordset. After you have the data in a recordset then use r.filter = "ColumnA = 1", or r.sort = "ColumnC, ColumnA".
Turns out I can create a recordSet to do this. Unlike, Remou's answer though we don't have to invoke a heavy weight odbc process on our sheet.
The following function (adapted from Mark Nold's answer) will create a record set from the supplied range. It assumes column headers are in the first row of the supplied range. This can be made more robust but its a good starting spot
Function CreateRecordSet(rSource As range) As Recordset
' Constants
Const MAX_CHARS = 1200
' Declarations
Dim rs As Recordset
Dim c As Long
Dim r As Long
Dim colCount As Long
Dim rowCount As Long
Dim fldName As String
colCount = rSource.Columns.Count
rowCount = rSource.rows.Count
' Setup record set
Set rs = New Recordset
r = 1 ' assume first row contains headers
For c = 1 To colCount
fldName = rSource.Cells(r, c).Value
rs.Fields.Append fldName, adVarChar, MAX_CHARS
Next c
' Fill record set
rs.Open
r = 2 ' skip header row
For r = 2 To rowCount
rs.AddNew
Debug.Print "row "; r & " of " & rowCount & " created"
For c = 1 To colCount
rs.Fields(c - 1) = CStr(rSource.Cells(r, c).Value)
Debug.Print "-- row(" & r; "): added col " & c & " of " & colCount
Next c
Next r
Set CreateRecordSet = rs
End Function
Sub TestCreateRecordSet()
Dim r As range
Dim rs As Recordset
Set r = range("A1:B4")
Set rs = CreateRecordSet(r)
End Sub
You want to use a Range class (just like CasperOne says). Here's some example VBA code
Function SortAndFilter(rSource As Range) As Range
Dim rResult As Range
Dim vaTemp As Variant
Dim wsTemp As Worksheet
Dim wbTemp As Workbook
vaTemp = rSource.Value
Set wbTemp = Workbooks.Add
Set wsTemp = wbTemp.Sheets(1)
Set rResult = wsTemp.Range("A1").Resize(UBound(vaTemp, 1), UBound(vaTemp, 2))
rResult.Value = vaTemp
rResult.Sort rResult.Cells(1), xlDescending
Set SortAndFilter = rResult
End Function
Sub Testit()
Dim rTest As Range
Set rTest = SortAndFilter(Selection)
'Do stuff with your range object
rTest.Parent.Parent.Close False 'close temp workbook
End Sub
Why not copy the data to a new, hidden worksheet, perform your sort/filter there, and then copy the data back when done?