VBA Save data in a collection globally - mysql

I'm quite new in VBA, I started yesterday... So, I want to click in a range of cell and an event save the index of the row that was modified to a collection (or other object that allows to store the row number that was edited). After that, the ribbon will have a button to save the rows that were modified and update or crate an object in myMySQL database. The question is: How to save and manipulate this Collection, I need to put it as Global? It seems that this is not working. Here is a snippet of code:
Global array_modified_rows As New Collection
Private Sub Worksheet_Change(ByVal Target As Range)
Dim KeyCells As Range
Dim result As String
Dim storage(1 To 10000) As String
' The variable KeyCells contains the cells that will
' cause an alert when they are changed.
Set KeyCells = Range("B:O")
If Not Application.Intersect(KeyCells, Range(Target.Address)) Is Nothing Then
result = CStr(KeyCells.Cells(Target.Row, Target.Column))
Debug.Print Target.Row
'MsgBox "Cell " & result & " has changed."
array_modified_rows.Add Target.Row
For i = 0 To array_modified_rows.Count
Debug.Print array_modified_rows.Item(i)
Next i
End If
End Sub`
I tried to save the modified rows to a collection and access it in another sub script. I´m expecting to get the collection data and update just those in my database

Related

Access VBA - Function to create an array with the names of all the worksheets of a selected excel workbook

I am using Access trying to upload all the worksheets of an excel (which I select) as table to the Database.
To do so, I am creating a function that I recall in my procedure; this function should read the names of the worksheets in my excel file and save them in an array/collection (which one do you suggests? the number of worksheets is not fixed). This is my code for the moment but it doesn't work properly because I find in my array only the last worksheet and not all the previous ones:
Function Get_Sheetsname_Array(xlsfile)
Dim sheetsLst As Collection
Dim lookupWB As Excel.Application
Dim txt As String
Set lookupWB = New Excel.Application
lookupWB.Workbooks.Open xlsfile
toIndex = lookupWB.Worksheets.Count
Dim i As Integer
With lookupWB
For Each wrksheet In .Worksheets
sheetsLst = Array(.xlSheet.Name)
Next wrksheet
End With
Get_Sheetsname_Array = sheetLst
End Function
Your code currently overwrites each sheet name, so only the last is saved in the variable. You need to add them to the array without overwriting. Something like this:
Sub SaveSheetsIntoArray()
Dim shArray, i
ReDim shArray(1 To Sheets.count)
For i = 1 To Sheets.count
shArray(i) = ThisWorkbook.Sheets(i).Name
Debug.Print shArray(i)
Next
'do something with the array "shArray"
End Sub
Also, add an "Option Explicit" as the first line in your VBA code, if not already there.

MS Access ListBox column property with ADODB Recordset creates error 424 Object Required

I'm building a simple form in MS Access with a listbox in which I want to display data that is stored in a MySQL Server table. I want two columns to display in the listbox.
I am able to display the first column with the additem property but the second column throws the run time error message 424 Object Required.
I have searched for hours in the web an in my books but I can't figure it out.
What is the problem?
Private Sub cmdSuchenVerantwortlich_Click()
Dim rsAuswahl As New ADODB.Recordset
Dim i As Long
If pConnectDB.State = adStateClosed Then
modConnectDB.Connect_To_DB
End If
Me.lstAuswahl.RowSourceType = "Value List"
'Clear Listbox
For i = Me.lstAuswahl.ListCount - 1 To 0 Step -1
Me.lstAuswahl.RemoveItem i
Next i
With rsAuswahl
.ActiveConnection = pConnectDB
.CursorType = adOpenStatic
.CursorLocation = adUseClient
.Open Source:="select MATNR, AUSNAHME from Ausnahmeliste where VERANTWORTLICH = '" & Me.cboVerantwortlich & "' "
Do Until .EOF
Me.lstAuswahl.AddItem .Fields("MATNR").Value
Me.lstAuswahl.Column(1, Me.lstAuswahl.ListCount - 1) = .Fields("AUSNAHME").Value
.MoveNext
Loop
.Close
End With
End Sub
The code Me.lstAuswahl.Column(1, Me.lstAuswahl.ListCount - 1) = .Fields("AUSNAHME").Value throws the error messsage.
The data type in the MySQL table of MATNR and AUSNAHME is varchar.
When I hold the mouse over .Fields("MATNR").Value I can see the excpected value and holding the mouse over .Fields("AUSNAHME").Value I can see the expected value as well. So the data is the but what's wrong ?
Thanks for anybody's help.
To add values using the "AddItem" method of the listbox, you need to concatenate the values, separated by a comma, so a delimited string as below:
Make sure that the columncount of your listbox is set to 2, or however many columns you want displayed.
Me.lstAuswahl.AddItem .Fields("MATNR").Value & "," & .Fields("AUSNAHME").Value
If you are only adding a few records, this is probably okay, but if you are going to display a lot of rows, it maybe best to transfer the data to a local table in access, and then bound your listbox directly to the table/query local to access.

SQL Query is updated when user updates Excel

I have an excel document that I want to link to an SQL query. In the excel document I have a list of item numbers. Whenever an item number gets changed I want the sql query to query that list of item numbers and return an output. Basically I want the excel sheet to use the Item Number as a parameter for the database item numbers ? The excel item numbers are updated daily.
Keep in mind that this is a mock example for what you are trying to do. With no knowledge of your database or spreadsheet, I can't guarantee that any of this will even work. At the very least, it will require you to make some adjustments before you can use it.
With that in mind, I have commented on various parts of the code to let you know what is going on there. The sections that have a *** are areas that you may want to change. The sections with ### are areas that you will HAVE to change for it to work for you.
This code assumes that you have a list of item numbers in column A of sheet 1, that each item number will only return one record, and that there are no blank cells in your list of item numbers.
Sub GrabItemInfo()
Dim objADO As New ADODB.Connection
Dim objRecSet As New ADODB.Recordset
Dim objCmd As New ADODB.Command
Dim strConn As String
Dim strSQL As String
Dim RowNum As Long
Dim errNum As Long
'open a connection to the database
'### change the properties for the connection to suit your needs
strConn = "DSN=DSNName; DBQ=Database; UID=Username; PWD=Password"
objADO.Open strConn
objCmd.ActiveConnection = objADO
objCmd.CommandType = adCmdText
'errNum is the row that the error log will start on
'***change errNum to change which row it starts on
errNum = 1
'***changeRowNum here to change which row to start on
RowNum = 1
'start the loop
Do Until ThisWorkbook.Sheets(1).Cells(RowNum, 1) = ""
On Error Resume Next
'### change the sql to whatever you need
'*** change the cells section if you're not using the first column
strSQL = "SELECT [field] FROM [table] WHERE ItemNum = " & ThisWorkbook.Sheets(1).Cells(RowNum, 1).Value
objCmd.CommandText = strSQL
Set objRecSet = objCmd.Execute
'pastes results from query into the cell next to the item number
'***change the cells section if you want to use a different column
ThisWorkbook.Sheets(1).Cells(RowNum, 2).CopyFromRecordset objRecSet
'clear out the recordset before the loops starts again
Set objRecSet = Nothing
'put the item number, error number, and error description on the second sheet of the work book
'***change the sheet number to put it on another sheet if you're already using the second
If Err > 0 Then
ThisWorkbook.Sheets(2).Cells(errNum, 1).Value = ThisWorkbook.Sheets(1).Cells(RowNum, 1).Value
ThisWorkbook.Sheets(2).Cells(errNum, 2).Value = Err.Number
ThisWorkbook.Sheets(2).Cells(errNum, 3).Value = Err.Description
On Error GoTo 0
End If
'raise the value for the row for the next iteration
RowNum = RowNum + 1
Loop
'clear out the connection
Set objADO = Nothing
Set objRecSet = Nothing
Set objCmd = Nothing
End Sub
For more information on connection strings, I recommend http://www.connectionstrings.com
It's a great resource to use for figuring out what kind of connection string you need. Connections strings can be...tricky...sometimes, and this really helps.
If you need any resources for SQL, I would recommend http://www.w3schools.com/sql
They have a good introduction to it there. Past that, get a good reference book, find a mentor, join forums(or Q&A sites like this one), etc. If you look into the SQL tag on this site, there is more information, along with some recommended resources as well.
Good luck.

Get Form Recordsource without opening the form

Does MS Access allow to get the recordsource value of the form without opening the form itself? I'm trying to optimize my code as of now, what I did is I just hide the form then get the Recordsource form query but it takes time to load since some of the forms trigger a code upon onload.
I'm late to the game here - I sometimes post answers months or years after the original question was posted, as I post my own solutions when a quick search of the 'Stack finds questions relevant to my own problem of the day, but no answers that I can actually use.
[UPDATE, 06 June 2016]
The 'NameMap' property is not available in document objects from Access 2010 onwards. However, 'Stacker Thunderframe has pointed out that this is now available in the 'MsysNameMap' table.
I have amended the code, and this works in Access 2010 and 2013.
[/UPDATE]
Most of a form's properties are only available when the form is open, but some are available in the form's entry in the DAO Documents collection.
The DAO 'document' is a horrible object: it won't persist in memory and you have to refer to it explicitly every time you use it:
FormName = "MyForm"
For i = 0 To Application.CodeDb.Containers("Forms").Documents(FormName).Properties.Count - 1
Debug.Print i & vbTab & Application.CodeDb.Containers("Forms").Documents(FormName).Properties(i).Name & vbTab & vbTab & Application.CodeDb.Containers("Forms").Documents(FormName).Properties(i).Value
Next
Run that snippet for your form, and you'll see a 'NameMap' property that contains a list of the form's controls, and some of the form's properties.
...In a truly horrible format which needs a binary parser. You might want to stop reading and take an aspirin, right now, before continuing.
Health Warnings:
The NameMap Property is undocumented. It is therefore unsupported and there is no guarantee that this solution will work in future versions of Microsoft Access.
The solution in my code below will stop working if the NameMap's two-byte binary label for a Record Source ever changes, or if it's locale-specific.
This is a horrible hack: I accept no liability for any effects on your sanity.
OK, here's the code:
A VBA function to return the Record Source from a closed MS-Access form:
Private Function FormRecordSource_FromNameMap(FormName As String) As String
' Reads the Record Source from the NameMap Property of the Document object for the form.
' WARNING: there is a potential error here: if the form's RecordSource property is blank
' and it has one or more list controls with a .RecordSource property populating
' the list, this function will return the first list control's Record Source.
' This won't work if you're using non-ASCII characters (Char > 255) in your form name.
Dim i As Integer
Dim j As Integer
Dim k As Integer
Dim arrByte() As Byte
Dim strOut As String
If Application.Version < 12 Then
arrByte = Application.CodeDb.Containers("Forms").Documents(FormName).Properties("NameMap").Value
For i = 1 To UBound(arrByte) - 2 Step 2
' 2-byte marker for a querydef in the NameMap:
If (arrByte(i) = 228 And arrByte(i + 1) = 64) Then
j = i + 2
Do While arrByte(j) = 0 And arrByte(j + 1) = 0 And j < UBound(arrByte)
' loop through the null chars between the marker and the start of the string
j = j + 2
Loop
strOut = ""
Do Until (arrByte(j) = 0 And arrByte(j + 1) = 0) Or j >= UBound(arrByte) - 2
If arrByte(j) = 0 Then j = j + 1
' loop until we reach the null char which terminates this string
' appending the Bchars (not unicode Wchars!) of the table or query
strOut = strOut & Chr(arrByte(j))
j = j + 2
Loop
Exit For ' we only want the first datasource
End If
Next i
Else
arrByte = Nz(DLookup("[NameMap]", "[MSYSNameMap]", "[Name] = '" & FormName & "'"), vbNullChar)
If UBound(arrByte) < 4 Then Exit Function
strOut = ""
For j = 60 To UBound(arrByte) - 2 Step 2
If arrByte(j) = 0 And arrByte(j + 1) = 0 Then Exit For
strOut = strOut & Chr(arrByte(j))
Next j
End If
frmRecordSource_FromNameMap = strOut
Erase arrByte
End Function
If you use the RecordSource in (say) OpenRecordset or a DCOUNT function, I would advise you to encapsulate it in square brackets: you might get the name of a hidden query object saved from a 'SELECT' statement in the RecordSource, and that name will contain '~' tilde characters which need special handling.
And now, something extra that you didn't ask for, but other people will be looking for if they Googled their way here for 'MS Access RecordSource for a closed form':
Getting an MS-Access form's RecordSource, whether it's open or not
Most times, your form will be open. Problem is, you don't know that... And if it's a subform, it might not be visible in the Forms() collection. Worse, a form that's hosted as a subform might exist as multiple instances in several open forms.
Good luck with that, if you're looking to extract dynamic properties... Like filters, or the Record Source if it's set 'on the fly' by VBA.
Public Function GetForm(FormName As String, Optional ParentName As String = "") As Form
' Returns a form object, if a form with a name like FormName is open
' FormName can include wildcards.
' Returns Nothing if no matching form is open.
' Enumerates subforms in open forms, and returns the subform .form object if
' it has a matching name. Note that a form may be open as multiple instances
' if more than one subform hosts it; the function returns the first matching
' instance. Specify the named parent form (or the subform control's name) if
' you need to avoid an error arising from multiple instances of the form.
Dim objForm As Access.Form
If ParentName = "" Then
For Each objForm In Forms
If objForm.Name Like FormName Then
Set GetForm = objForm
Exit Function
End If
Next
End If
If GetForm Is Nothing Then
For Each objForm In Forms
Set GetForm = SearchSubForms(objForm, FormName, ParentName)
If Not GetForm Is Nothing Then
Exit For
End If
Next
End If
End Function
Private Function SearchSubForms(objForm As Access.Form, SubFormName As String, Optional ParentName As String = "") As Form
' Returns a Form object with a name like SubFormName, if the named object SubFormName is subform
' of an open form , or can be recursively enumerated as the subform of an open subform.
' This function returns the first matching Form: note that a form can be instantiated in multiple
' instances if it is used by more than one subform control.
Dim objCtrl As Control
For Each objCtrl In objForm
If TypeName(objCtrl) = "SubForm" Then
If objCtrl.Form.Name Like SubFormName Then
If ParentName = "" Or objForm.Name Like ParentName Or objCtrl.Name Like ParentName Then
Set SearchSubForms = objCtrl.Form
Exit For
End If
Else
Set SearchSubForms = SearchSubForms(objCtrl.Form, SubFormName, ParentName)
If Not SearchSubForms Is Nothing Then
Exit For
End If
End If
End If
Next objCtrl
End Function
Public Function FormRecordSource(FormName As String, Optional ParentName As String = "") As String
' Returns the Recordsource for a form, even if it isn't open in the Forms() collection
' This will look for open forms first. If you're looking for a subform, you may need a
' parent name for the form which hosts the subform: your named form might be open as a
' subform instance in more than one parent form.
' WARNING: there is a potential error here: if the form isn't open, and it has a blank
' RecordSource property, and it has one or more controls with a .RecordSource
' property populating a list, a list control's RecordSource could be returned
Dim objForm As Form
If FormName = "" Then
Exit Function
End If
Set objForm = GetForm(FormName, ParentName)
If objForm Is Nothing Then
FormRecordSource = FormRecordSource_FromNameMap(FormName)
Else
FormRecordSource = objForm.RecordSource
Set objForm = Nothing
End If
End Function
Share and enjoy: and please accept my apologies for any unwanted line breaks in the code sample.
One option would be to save the Record Source of the form as a Query. Say you have a form named [AgentForm] whose Record Source is
SELECT ID, AgentName FROM Agents
In your development .accdb copy of the database, open the form in Design View and open the Record Source in the Query Builder. Click the "Save As" button ...
and save the query as "AgentForm_RecordSource". Now the Record Source property of the form is just a reference to the saved query, and the query itself can be accessed directly through a QueryDef object. So, you could retrieve the SQL statement for the form's Record Source with
Dim cdb As DAO.Database, qdf As DAO.QueryDef, sql As String
Set cdb = CurrentDb
Set qdf = cdb.QueryDefs("AgentForm_RecordSource")
sql = qdf.SQL
or you could go ahead and open a Recordset with
Dim cdb As DAO.Database, qdf As DAO.QueryDef, rst As DAO.Recordset
Set cdb = CurrentDb
Set qdf = cdb.QueryDefs("AgentForm_RecordSource")
Set rst = qdf.OpenRecordset
If the form's Record Source is a SELECT statement rather than the name of a table or saved query, you can check the QueryDefs collection for the hidden QueryDef which Access created for that Record Source statement.
If it exists, you can check its .SQL property.
strFormName = "Form15"
? CurrentDb.QueryDefs("~sq_f" & strFormName).SQL
SELECT DISTINCTROW *
FROM [DB Audits];
You can trap error #3265, "Item not found in this collection", which will be thrown if that QueryDef does not exist.
Since you can't open your form in design view and opening your form regularly is causing performance issues, there are but a few more workarounds:
Depending on how you want to check for the closed form's recordsource, you can set a global variable in the following way, in a separate module:
Public glb_getrecordsource As String
Afterwards, depending on how you call the code, you can do the following:
Private Sub Command1_Click()
glb_getrecordsource = "Yes"
DoCmd.OpenForm "Form1"
'... Do something
End Sub
Then, as the final step, put the following at the beginning of your form's OnLoad event:
Private Sub Form_Load()
If glb_getrecordsource = "Yes" Then
glb_getrecordsource = Me.Form.RecordSource
DoCmd.Close acForm, "Form1", acSaveYes
Exit Sub
End If
'... Usual OnLoad events
End Sub
This will at least solve the performance issues, since you will not trigger any of the time consuming events, in the form's load event.
Another workaround:
You can export your form to a .txt file and then search the text file for the recordsource. The following code will export your forms to .txt files in a specified folder:
Dim db As Database
Dim d As Document
Dim c As Container
Dim sExportLocation As String
Set db = CurrentDb()
sExportLocation = "C:\AD\" 'Do not forget the closing back slash! ie: C:\Temp\
Set c = db.Containers("Forms")
For Each d In c.Documents
Application.SaveAsText acForm, d.Name, sExportLocation & "Form_" & d.Name & ".txt"
Next d
Code partly borrowed from this forum. Afterwards, you only have to open the file and search for the recordsource. If the recordsource is empty it will not be exported, so keep that in mind. Also, I doubt this will improve perfomance, but who knows!

docmd.TransferSpreadsheet Access --> Excel //// specify destination worksheet AND range

I have to write some Access VBA to export data from an Access query into a specific range of cells in an Excel document that has several worksheets.
I am having trouble finding the right way to specify the worksheet AND range.
Here is what I have so far:
docmd.TransferSpreadsheet(TransferType:=acExport, SpreadsheetType:=acSpreadsheetTypeExcel8, TableName:=qry_Main, _
FileName:="c:\test.xlsm", _
HasFieldNames:=False, _
Range:="Main!J9:J10")
The broken piece is Range:="Main!J9:J10"
What's the proper way to make this reference?
You can use CopyFromRecordset and automation:
Sub XLTrans()
''Reference: Microsoft ActiveX Data Object x.x Library
Dim rs As New ADODB.Recordset
Dim xl As Object ''Excel.Application
Dim wb As Object ''Workbook
Set xl = CreateObject("Excel.Application")
''Pick one
''1. New book
Set wb = xl.Workbooks.Add
''2. Existing book
Set wb = xl.Workbooks.Open("z:\docs\book1.xlsx")
''Connection relevant for 2007 or 2010
rs.Open "MyTableOrQuery", CurrentProject.AccessConnection
wb.Sheets("Sheet1").Cells(4, 5).CopyFromRecordset rs
xl.Visible = True
End Sub
Note that this will not include column headings, but you can add them as well, for example:
For i = 0 To rs.Fields.Count - 1
Worksheets("Sheet1").Cells(3, i + 5) = rs(i).Name
Next
http://msdn.microsoft.com/en-us/library/office/ff844793.aspx
http://msdn.microsoft.com/en-us/library/office/aa141565(v=office.10).aspx
You cannot use RANGE for exporting:
"
Range Optional Variant. A string expression that's a valid range of cells or the name of a range in the spreadsheet. This argument applies only to importing. Leave this argument blank to import the entire spreadsheet. When you export to a spreadsheet, you must leave this argument blank. If you enter a range, the export will fail.
"