What is correct way to set up VBA ADO connection from Excel to Access for multiple users and files? - vba

I have several excel files that are used for entering data. Files are identical in functionality, one for each service center of ours. In the form there is button that launches a macro which transforms data to table format on another sheet which is then later uploaded to Access db.
Everything worked fine on my own computer. Adding new rows, updating existing rows and deleting existing roles. I had used early binding which lead to problems when I moved files to our network drive. I managed to convert files to late binding but then other problems arose.
Most of the time, uploading to Access isn't working, especially when multiple users try to do stuff at the same time. Most common error code is that I am not using updateable query or that this method doesn't support backwards scrolling. I sorry for not reporting actual error codes, but I can't replicate them at the moment.
My connection code is as follows, it is bit of a mix of copy paste code from different examples.
Opening the connection and other prestuff
Sub excel2access()
Const adUseClient = 3
Const adUseServer = 2
Const adLockOptimistic = 3
Const adOpenKeyset = 1
Const adOpenDynamic = 2
Dim oConn As Object
Dim cmd As Object
Dim rs As Object
Dim r As Long
Dim criteria As String
Dim Rng As Range
Set oConn = CreateObject("ADODB.Connection")
Set cmd = CreateObject("ADODB.Command")
oConn.Open "Provider=Microsoft.ACE.OLEDB.12.0;" & _
"Data Source= '" & Range("dbpath").Value & "\" & Range("dbfile").Value & "' ;"
Set rs = CreateObject("ADODB.Recordset")
rs.CursorLocation = adUseClient
rs.CursorType = adOpenStatic
rs.LockType = adLockOptimistic
rs.Open "Select * from need_rows WHERE service_center = '" & Range("scenter_name").Value & "'", oConn
r = 2 ' the start row in the worksheet
Sheets("data").Select
This following bit looks through data in excel sheet and tries to find match from recordset found for that service center. If match is not found new record is created and if match is found the old record is updated.
Do While Len(Range("A" & r).Formula) > 0
With rs
criteria = Range("D" & r).Value
.Find "identifier='" & criteria & "'"
If (.EOF = True) Or (.BOF = True) Then
.AddNew ' create a new record
.Fields("service_center") = Range("scenter_name").Value
.Fields("product_id") = Range("A" & r).Value
.Fields("quantity") = Range("B" & r).Value
.Fields("use_date") = Range("C" & r).Value
.Fields("identifier") = Range("D" & r).Value
.Fields("file_type") = Range("file_type").Value
.Fields("use_type") = Range("E" & r).Value
.Fields("updated_at") = Now
.Update
Else
If .Fields("quantity") <> Range("B" & r).Value Then
.Fields("quantity") = Range("B" & r).Value
.Fields("updated_at") = Now
.Update ' stores the new record
End If
End If
.MoveFirst
End With
r = r + 1
Loop
rs.Close
Set rs = Nothing
Set oConn = Nothing
MsgBox "Confirmation message"
End Sub
Edit: Based on link by barrowc I changed cursor type to adOpenStatic. I made a test with several users trying to upload data at the same time and everything worked perfectly. Until one user stayed in the file and spent quite a while editing data there and then tried to upload data to db and got following error message: https://dl.dropbox.com/u/3815482/vba_error.jpg
Again, I am back where I started from.
Also, I am open to feedback on my code in general as well.
I am using Office 2010.
Am I doing it wrong? All help is appreciated.

You are going to have a lot of issues with the database being locked by other users. This is for several reasons:
From what I can see you are not handling errors. Therefore if your script errors half way through the connection will be left open thus causing lock problems.
By the looks of this, the macros could potentially keep the connection open for a decent amount of time (assuming no errors).
Having created a lot of macros connecting to an MS Access database I can tell you straight up. You are going to have a lot of connection issues where the database is being locked by spreadsheets that someone has left open all day/night due to things such as not handling unexpected errors (the connection never closes).
Even once you fix the problems all you need is ONE person to be using the spreadsheet with the old code and they will continue to lock the database.
One massive problem is that if someone connects to the database when its already open by someone else I believe they inherit the connection type of the already opened database resulting in a daisy chain of write locks. You then need to make sure all connections are severed in order to reset the connection.
You have also not shown us how the data is put into the spreadsheet in the first place. Perhaps you are not correctly closing the connection and that could potentially be the reason why sometimes the database is locked.
There are many different things you could try to get around this:
Easiest would be to use MS Access Front End + MS Access Back End.
Instead of pressing this button and uploading the data through connection strings you could make it save the file in a folder which would then be processed by an ms access database that is sitting there watching the folder. This would mean that you upload script would be written in MS Access and just be processing the files. Wouldn't be as instantaneous as your current approach but all write connections would be coming from the same machine/user in this circumstance.
Persist with the current method: eventually you may get it to a stable state but it will be a lot of frustration and effort as determining the reason for a lock may not always be easy. You could at least look at who has the file locked at the time and work from there but as mentioned earlier they may not have been the cause of the lock. They may have just inherited the lock type.
Personally I like to use MS Excel to display MS Access data for users, but avoid like a plague getting it to update MS Access. However if I was to do this I would do it through the use of oConn.Execute commands not opening a recordset, comparing and slowing pushing as that would keep the connection open too long.
Sorry for the wall of text. Hope this information helps.

sounds to me like Jet isn't reliable enough for your environment. I frequently use SQL Server / Access Data Projects to consolidate information from multiple spreadsheets into a single database backend that doesn't barf when you add a half dozen users.

You could also try using action queries.
First I would try to update using (you may need to format the now value)
dim count as long
oConn.Execute "UPDATE need_rows SET quantity = " & Range("B" & r).Value & ", updated_at = #" & Now & "# WHERE service_center = '" & Range("scenter_name").Value & "' AND identifier='" & Range("D" & r).Value & "' AND quantity <> " & Range("B" & r).Value", count
If count is zero then no row was updated, so either there was no row to update or the quantity hasn't changed. Either way we can try to INSERT, which will fail in the latter case, but without causing problems.
if count = 0 then
count = oConn.Execute "INSERT ...", count
if count = 0 then
' quantity has not changed, so nothing to do
else
' new record inserted
end if
else
' record updated
end if

Related

Insert Query/Select Query works in access, but not in vba

I have created a query that works great with no errors in Access, and while trying to translate this to my vba setup I can't figure out why I am not getting any values, even though the query clearly works in access, and causes no errors on the VBA side.
Pulling my hair out here, I have created a side table to see if I could "pseudo-insert" the calculated value, but I get the same issue as above - insert works with no errors in access, goes through in vba with no issues, but doesn't actually insert any data.
I have copied the string queries while pausing code to make sure EVERYTHING matches up between the Access query that works and the VBA query, and haven't found any differences.
I read somewhere since I am trying to pull a "first line" data piece that there may be some HDR setting that I could change, but when I tried to apply any fixes I found they opened another instance of excel and opened a dialogue box.
Please let me know what I am doing wrong here.
Public Function PullNextLineItemNumB(QuoteNum) As Integer
Dim strQuery As String
Dim ConnDB As New ADODB.Connection
Dim myRecordset As ADODB.Recordset
Dim QuoteModifiedNum As String
ConnDB.Open ConnectionString:="Provider = Microsoft.ACE.OLEDB.12.0; data
source=" & ThisWorkbook.Path & "\OEE Info.accdb"
'Query to try and make Access dump the value "18" into the table so I can
grab it after the query is finished, sidestepping excel not working
strQuery = "INSERT INTO TempTableColm (TempColm) SELECT
MAX(MID([Quote_Number_Line],InStr(1,[Quote_Number_Line]," & Chr(34) & "-"
& Chr(34) & ")+1)) AS MaxNum from UnifiedQuoteLog where Quote_Number_Line
like '" & QuoteNum & "*'"
ConnDB.Execute strQuery
'Original query, returns "18" as expected in Access, and null or empty in
the recordset
strQuery = "SELECT MAX(MID([Quote_Number_Line],InStr(1,
[Quote_Number_Line]," & Chr(34) & "-" & Chr(34) & ")+1)) from
UnifiedQuoteLog where Quote_Number_Line like '" & QuoteNum & "*'"
Set myRecordset = ConnDB.Execute(strQuery)
Do Until myRecordset.EOF
For Each fld In myRecordset.Fields
Debug.Print fld.Name & "=" & fld.Value
Next fld
myRecordset.MoveNext
Loop
myRecordset.Close
Set myRecordset = Nothing
ConnDB.Close
Set ConnDB = Nothing
End Function
Actual output from access is "18" which is expected, output from excel's vba recordset is always null or empty string.
It appears I solved the problem, while looking into this apparently the excel operator using ADODB with access is % for LIKE and NOT * (because reasons). As soon as I made that change everything started working.
Can someone explain why that is a thing? I really want to understand why this was a design choice.

Setting two rowsources in same procedure is terrible slow - Access 2010

I have two Listboxes which are filled via vba on a click event. The table 'Project' is a odbc datasource with 250 records.
List1.RowSource = "SELECT Name FROM Project WHERE ProjectID = " & ProjectID.Caption & " AND Year = " & ActualYear.Caption & " ORDER BY Name"
List2.RowSource = "SELECT ProjectShare FROM Project WHERE ProjectID = " & ProjectID.Caption & " AND Year = " & ActualYear.Caption & " ORDER BY Name"
So far so good. But when I run this code, it takes everytime up to 30sec to complete. I thought, okay it's because of odbc and so on. But when I run only one line of this code (no matter which), it is fast as lightning (0,1sec).
How can it be, that one query takes 0,1sec and two querys 30sec? May I could make a break between these two lines? Btw. without odbc everything works like a charm, no matter how many lines
You can bind both listboxes to the same recordset by manually creating the recordset. This allows Access to only query the table once instead of twice at the same time, avoiding any locking conflicts, and tends to avoid other problems as well.
This also allows you to use parameters, fixing any errors introduced by string concatenation.
Dim rs As DAO.Recordset
With CurrentDb.CreateQueryDef("", "SELECT Name, ProjectShare FROM Project WHERE ProjectID = p1 AND Year = p2 ORDER BY Name")
.Parameters(0).Value= ProjectID.Caption
.Parameters(1).Value = ActualYear.Caption
Set rs = .OpenRecordset(dbOpenSnapshot) 'Snapshot because it won't be updated
End With
Set list1.Recordset = rs
Set list2.Recordset = rs
Note that I have had errors occur when an object bound to a recordset with parameters was requeried, so you might want to use string concatenation if that's happening.

Excel VBA Late Bind to Access and SQL Insert

I am having a frustrating issue with late binding to MS Access from Excel VBA to execute a DML statement like Insert or Update. All of the data I use in vba comes from user defined Classes. I can query just fine but writing to the DB gets different errors each time I try a different way to do the same thing. Below are some links to the same/similar issues, however each is slightly out of context and therefore I could not get passed my problem.
Microsoft.ACE.OLEDB.12.0 Current Recordset does not support updating error received when trying to update access
Operation must use an Updateable Query / SQL - VBA
Update an excel sheet using VBA/ADO
Operation must use an updatable query. (Error 3073) Microsoft Access
https://msdn.microsoft.com/en-us/library/bb220954%28v=office.12%29.aspx?f=255&MSPPError=-2147217396
http://www.access-programmers.co.uk/forums/showthread.php?t=225063
My end goal is to simply execute a DML string statement and it has to use late binding. Mainly I get the 3251 error saying my connection is 'Read Only' or a missing ISAM when I add ReadOnly=0 to the connection string. Fyi, getProjectFile just returns a path to a file starting from the parent folder of my project. I am pretty sure I can just use the connDB.Execute so I only need SQL Insert, I don't want to query first because the queries will get fat quick. I also think something might be wrong with the enum params because the ExecuteOptions want bitmasks instead of just a Long and I don't really know how to do that. From most of my research, I kept getting referred to the LockType and/or cursor not being right. For my environment; Windows 8.1 64bit, MS Office 2010 32bit(required). Does anyone see what is wrong here?
Sub ADO_Tester()
Dim strSQL, strFile, strConnection As String
Dim connDB As Object
'late bind to the ADODB library and get a connection object
Set connDB = CreateObject("ADODB.Connection")
'Connect to the DB
strFile = Application.ActiveWorkbook.Path & "\" & "PortfolioDB.accdb"
strConnection = "Provider = Microsoft.ACE.OLEDB.12.0; data source=" & strFile & ";"
connDB.Open strConnection
'insert statement for a test record
strSQL = "INSERT INTO underlying_symbol (symbol) VALUES ('xyz')"
'execute the
connDB.Execute strSQL, , 2, 1 + 128
'clear the object
connDB.Close
Set connDB = Nothing
End Sub
Edit:
Early binding:
connDB.Execute strSQL, , adCmdText + adExecuteNoRecords
Late Binding: How to enter the value for adExecuteNoRecords? On msdn it is 0x80 and another post says &H0001,either way it gives a syntax error. It says enter a bitmask for this enum value.
connDB.Execute strSQL, , 1 + 0x80
Edit: Now the correct way -
adExecuteNoRecords (the ADO enum value) = 0x80 (a binary value) = 128 (a decimal value)
connDB.Execute strSQL, , 1 + 128
Edit: Now the issue gets even deeper. When I execute the code in a test spreadsheet into a test database, it works. When I copy and paste into the actual project spreadsheet and point to actual project db, I get the error: operation must use an updateable query . . . again. Same db name, same dml, same table name. The only difference is the actual DB is a product of a split to separate it from the forms and code in Access. Could this have changed some setting to make it read only?
Edit: It just gets deeper and deeper. The issue causing it not to work in the project db is because I have some Excel Tables querying the db. I made these through the Excel UI, Ribbon -> External Data -> Access -> etc. . . It has now become obvious these are causing me to be unable to insert DML because they are probably set to read only. How can I change the tables connections permissions? Is there another way I could be making these tables so that I can provide the connection? How to get Tables to be friendly with DML in VBA?
This worked for me:
Option Explicit
Private Const acCmdText As Integer = 1
Sub ADO_Tester()
On Error GoTo ErrorHandler
Dim strSQL As String
Dim strFile As String
'Dim adoRecSet As Object
Dim connDB As Object
'late bind to the ADODB library and get a connection object
Set connDB = CreateObject("ADODB.Connection")
'Connect to the DB
strFile = getProjectFile("core", "PortfolioDB.accdb")
connDB.Open connectionString:="Provider = Microsoft.ACE.OLEDB.12.0; data source=" & strFile & ";"
'If State = 1, db connection is okay.
MsgBox "ADO Connection State is " & connDB.State & "."
'SQL to get the whole [underlying_symbol] table
'strSQL = "underlying_symbol" 'if options 2
'strSQL = "SELECT * FROM underlying_symbol" 'if options 1
strSQL = "INSERT INTO underlying_symbol (symbol) VALUES ('xyz')"
'late bind to adodb and get recordset object
'Set adoRecSet = CreateObject("ADODB.Recordset")
'&H0001 = bitmask for aCmdText
connDB.Execute strSQL, , acCmdText
'With adoRecSet
' .Open Source:=strSQL, _
' ActiveConnection:=connDB, _
' CursorType:=1, _
' LockType:=3, _
' Options:=&H1
'.AddNew
'.fields("symbol") = "XYZ"
'.Update
'End With
'------------------
'close the objects
'adoRecSet.Close
connDB.Close
'destroy the variables
'Set adoRecSet = Nothing
Set connDB = Nothing
ExitMe:
Exit Sub
ErrorHandler:
MsgBox Err.Number & ": " & Err.Description
GoTo ExitMe
End Sub
Added some error handling, a constant that defines acCmdText (Why just not add a reference to ADO library? Up to you, though.), and a message box to check the connection state to the database, as I can't test your getProjectFile function. Late binding doesn't seem to be the issue here, I think the key line is:
connDB.Execute strSQL, , 2, &H1
Can really say what's going on here as I've never done it like this (code doesn't even compile), but changing it to
connDB.Execute strSQL, , acCmdText
worked for me.

error on opening a csv file using a jet oledb connection in excel vba

First time I'm using stackoverflow so here goes...
I've been getting errors intermittently when I try to run a macro in excel which I use for pulling in data from a CSV file. The error normally goes away if I start a fresh session, but this time it's been particularly persistant. It basically errors on the .Open line below, giving me a "Run-time error '2147467259' (80004005) Unspecified error":
Public Sub LoadFile()
file_path = Range("FlatFileLocation")
Set oConn = CreateObject("ADODB.Connection")
strcon = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & file_path & "; Extended Properties=""text;HDR=Yes;FMT=Delimited(,)"";"
oConn.Open strcon
.....
Other info:
1) the file I'm trying to access is not open by another user
2) I close my connections at the end of the sub. also, i just tried restarting my machine and the error happened the first time I tried running the file
3) When I open my session up without any of my existing addins it seems to work. Is there any way of checking whether there is some sort of addin conflict going on?
There were other posts that suggest using CSVReader. Questions I have before trying this route are:
1) can I use this CSVReader across multiple user machines? The problem I would have here is needing to install it on a number of machines. I might be able to put a file on a shared drive however.
2) Can I query the resultant file with a SQL string? At the moment I use something like this:
....
strsql = "SELECT * FROM ( " & strsql & " ) WHERE ( ABS(PrevRisk) + ABS(CurrRisk) >= " & RiskThreshold & " ) ;"
Set oResult = New ADODB.Recordset
oResult.Open strsql, oConn
....
Thanks in advance for your help!

Exporting Dataset to Excel

I have the following bit of code that I have used in the past to export a DataSet generated from the Stored Procedure 'HISTORICAL_COSTS' to Excel.
Dim c As Long = 1
For Each dc As DataColumn In Me.WOCostDataSet. & _
HISTORICAL_COSTS.Columns
.Cells(1, c).Value = dc.ColumnName.ToString
.Cells(1, c).Font.Bold = True
c += 1
Next
Dim i As Long = 2
For Each dr As DataRow In Me.WOCostDataSet.HISTORICAL_COSTS.Rows
c = 1
For Each dc As DataColumn In Me.WOCostDataSet. & _
HISTORICAL_COSTS.Columns
.Cells(i, c).Value = dr.Item(dc.ColumnName).ToString
c += 1
Next
i += 1
Next
I am trying to re-use this code an different but similar application, but, I am running into an issue. The previous use of this code was used on a static table in our dBase generated by the Stored Procedure. And while this basically remains the same for the new application, the requirements now require the stored procedure to have an input parameter to be entered by the user (through VB.net) prior to execution. For a little back-story, you can follow that completed process here - Injecting a Parameter into Stored Procedure.
The application itself does in fact return a fully populated dataset, and I'd like our users to have the ability to export that generated dataset to Excel. So, I set up your prototypical 'EXPORT ME' button to do start the dirty work.
Upon raising the event; Excel opens and only my column names are being reiterated throughout the sheet. But, and here is the problem, the cells representing the row data - are blank.
I have come to the conclusion (I do admit that I may be wrong in this assumption) that the rows are not being populated due to the fact that the Stored Procedure needs an input parameter to do it's thing, and without that parameter there isn't any data to return for each row. Basically meaning that my code just won't work for what I am trying to do.
If I am right in my assumptions, any ideas as to how I might get that parameter into my code above so that the rows will be properly generated.
If I am wrong, well, any input on what be wrong with my logic or the code itself would be greatly appreciated.
Thanks,
Jasoomian
Stan,
Here is the code that generates the dataset:
Try
Dim FBConn As FbConnection
Dim MyConnectionString As String
MyConnectionString = "datasource=" _
& MyServer & ";database=" _
& TextBox4.Text & ";user id=SYSDBA;password=" _
& MyPassword & ";initial catalog=;Charset=NONE"
FBConn = New FbConnection(MyConnectionString)
Dim FBCmd As New FbCommand("HISTORICAL_COSTS", FBConn)
FBCmd.CommandType = CommandType.StoredProcedure
FBCmd.Parameters.Add("#I_PN", FbDbType.VarChar, 40)
FBCmd.Parameters("#I_PN").Value = TextBox1.Text.ToUpper
Dim FBadapter As New FbDataAdapter(FBCmd)
Dim dsResult As New DataSet
FBadapter.Fill(dsResult)
Me.HISTORICAL_COSTSDataGridView.DataSource = dsResult.Tables(0)
Dim RecordCount As Integer
RecordCount = Me.HISTORICAL_COSTSDataGridView.RowCount
Label4.Text = RecordCount
Catch ex As System.Exception
System.Windows.Forms.MessageBox.Show _
("There was an error in generating the DataStream, " & _
"please check the system credentials and try again. " & _
"If the problem persists, please contact your friendly " & _
"local IT department.")
End Try
Stackoverflow kindly suggests that I offer a bounty to anyone answering my question,but, since I don't have enough REP to create a sufficient bounty - does my ever-encompassing gratitude garner any coding love?
A few quick updates:
I tested my application by altering the Stored Procedure to inject the results into a new separate table, and then run my excel export against that table - and it works fine. But, since many people will be using the application at the same time, this is not a viable solution.
So, I am back to believing that there is an issue with this instance of the dataset needing a parameter to run correctly for the export.
Happy to answer any and all questions to the best of my ability.
You conclusion is definitely wrong -- once the dataset is populated the parameter is no longer a factor. I don't see anything obviously wrong with your code and, as you said, it worked elsewhere. I would start by setting a breakpoint and making sure that the dataset contains rows as you expect.