I made a multi-user interface on Excel to write records on a common Excel file using ADO Connection.
I have four users using this interface (different interface file for each user).
It works, until two or more users try to add or edit a record simultaneously.
When that's the case, VBA opens the Excel file in read-only mode right after it tries to open the ADO connection for the last user.
Read-only Excel file opened
My work-around try was to use an ISWORKBOOKOPEN() function to identify when concurrency occurs and then close the file, close the connection, wait a second and then try again:
Application.ScreenUpdating = False
Dim ADODBCONNECT As New ADODB.Connection
Dim RECORDSET As New ADODB.RECORDSET
Dim SQL_FILTER As String
Dim DATABASE As String
' -------- BACKUP --------
Dim oFSO As Object
Set oFSO = CreateObject("Scripting.FileSystemObject")
Call oFSO.CopyFile(Range("SLITTING_DATABASE"), Range("BACKUP_FILENAME"))
' -------- ADIÇÃO DO REGISTRO --------
DATABASE = Workbooks("S41 - Deacro.xlsm").Sheets("DATA SOURCES").Range("SLITTING_DATABASE")
OPEN_CONN:
ADODBCONNECT.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0; Data Source ='" & DATABASE & "';
Extended Properties='Excel 12.0'"
ADODBCONNECT.Open
If ISWORKBOOKOPEN(DATABASE) = True Then
ADODBCONNECT.Close
Workbooks(DATABASE).Close False
newHour = Hour(Now())
newMinute = Minute(Now())
newSecond = Second(Now()) + 1
WAITTIME = TimeSerial(newHour, newMinute, newSecond)
Application.Wait WAITTIME
GoTo OPEN_CONN
End If
SQL_FILTER = "Select * From [SLIT_INCOME$]"
RECORDSET.Open SQL_FILTER, ADODBCONNECT, adOpenKeyset, adLockOptimistic
RECORDSET.AddNew
RECORDSET!SlittingWO = Workbooks("S41 - Deacro.xlsm").Sheets("INTERFACE").Range("OP_Corte")
RECORDSET!ReelUD = CADASTRO_DE_BOBINAS.REEL_UD_ENTRY
RECORDSET!ReelLenghtm = CADASTRO_DE_BOBINAS.REEL_MTS_IN_ENTRY
RECORDSET!Date = Now
RECORDSET.UPDATE
RECORDSET.Close
ADODBCONNECT.Close
' -------- ATUALIZAÇÃO DOS DADOS NA INTERFACE --------
Call UPDATE_DATA
Application.ScreenUpdating = True
Two other problems occur here:
1 - The workbooks.close seems not to be working when the file opens because of:
Error 9, subscript out of range
2 - If the function is used after opening the ADO connection, it returns true even when the connection has been opened by same user that is checking the file availability.
I copied the function from another question here:
Function ISWORKBOOKOPEN(FileName As String)
Dim ff As Long, ErrNo As Long
On Error Resume Next
ff = FreeFile()
Open FileName For Input Lock Read As #ff
Close ff
ErrNo = Err
On Error GoTo 0
Select Case ErrNo
Case 0: ISWORKBOOKOPEN = False
Case 70: ISWORKBOOKOPEN = True
Case Else: Error ErrNo
End Select
End Function
Is there a way to detect that the file is being accessed through ADO connection by another user, so I can make a loop to force the last user to wait until the first user's connection is closed?
I know Excel has a lot of limitations when used as database file, but it is the only tool my company provides.
Work-around: created a "locking-flag" file that is opened as input and read mode is locked before the ADO connection is stablished. First user to open the file causes the second one to crash on VBA error 70, which I use to loop the second user through the attempt to open the "locking-flag" file untill the first one closes it.
Application.ScreenUpdating = False
Dim ADODBCONNECT As New ADODB.Connection
Dim RECORDSET As New ADODB.RECORDSET
Dim SQL_FILTER As String
Dim DATABASE As String
' -------- BACKUP --------
Dim oFSO As Object
Set oFSO = CreateObject("Scripting.FileSystemObject")
Call oFSO.CopyFile(Range("SLITTING_DATABASE"), Range("BACKUP_FILENAME"))
' -------- BLOQUEAR BANCO DE DADOS --------
DATABASE = Workbooks("S41 - Deacro.xlsm").Sheets("DATA SOURCES").Range("SLITTING_DATABASE")
LOCKING_FLAG_FILE = Workbooks("S41 - Deacro.xlsm").Sheets("DATA SOURCES").Range("LOCKING_FLAG_FILE")
ErrNo = 0
Do
On Error Resume Next
ff = FreeFile()
Open LOCKING_FLAG_FILE For Input Lock Read As #ff
ErrNo = Err
On Error GoTo 0
Loop While ErrNo = 70
' -------- ADIÇÃO DO REGISTRO --------
ADODBCONNECT.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0; Data Source ='" & DATABASE & "'; Extended Properties='Excel 12.0'"
ADODBCONNECT.Open
SQL_FILTER = "Select * From [SLIT_INCOME$]"
RECORDSET.Open SQL_FILTER, ADODBCONNECT, adOpenKeyset, adLockOptimistic
RECORDSET.AddNew
RECORDSET!SlittingWO = Workbooks("S41 - Deacro.xlsm").Sheets("INTERFACE").Range("OP_Corte")
RECORDSET!ReelUD = CADASTRO_DE_BOBINAS.REEL_UD_ENTRY
RECORDSET!ReelLenghtm = CADASTRO_DE_BOBINAS.REEL_MTS_IN_ENTRY
RECORDSET!Date = Now
RECORDSET.UPDATE
RECORDSET.Close
ADODBCONNECT.Close
' -------- DESBLOQUEAR BANCO DE DADOS --------
Close ff
' -------- ATUALIZAÇÃO DOS DADOS NA INTERFACE --------
Call UPDATE_DATA
Application.ScreenUpdating = True
It's far away from being an elegant solution, but if excel is the only tool you have, and there are not a lot of users using the interface (in this case, I only have four), this will do the job.
Related
I currently have 2 separate Excel worksheets. One is data entry and another is display.
The display uses VBA to connect to data entry to obtain data. Normally, it functions well enough. However I'm required to have the 2 worksheets in separate windows, meaning both can be displayed at the same time in separate windows, in the same screen.
The issue in this scenario is that when I click Execute in display to begin SQL query, the display window opens another data entry worksheet (read-only) and reads that instead of the one I opened initially. Is this issue due to my connection string or my ADODB.Recordset has issues?
Here is the sub which contains the connection string and ADODB.Recordset.
Edit: Full code is included to provide full context for those who need it.
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 = ThisWorkbook.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") ' arrange record by newest
Call testsql("4th") ' show final results
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 = ThisWorkbook.Sheets("Inner Workings").Range("B2").Text
Set rgPlaceOutput = ThisWorkbook.Sheets("1st Summary").Range("A2")
End If
If AB = "2nd" Then
stSQLstring = ThisWorkbook.Sheets("Inner Workings").Range("B3").Text
Set rgPlaceOutput = ThisWorkbook.Sheets("2nd Summary").Range("A2")
End If
If AB = "3rd" Then
stSQLstring = ThisWorkbook.Sheets("Inner Workings").Range("B4").Text
Set rgPlaceOutput = ThisWorkbook.Sheets("3rd Summary").Range("A2")
End If
If AB = "4th" Then
stSQLstring = ThisWorkbook.Sheets("Inner Workings").Range("B5").Text
Set rgPlaceOutput = ThisWorkbook.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("3rd Summary").Range("A2:BR5000").Value = Empty
ActiveWorkbook.Sheets("Final Summary").Range("A5:BR5000").Value = Empty
End Sub
Also another thing I noticed. Depending on which file I open first, it can result in both files creating a read-only copy when I click Execute. If I open Display first then Entry form, both in different instances of Excel, it will create read-only copies of both files.
If I open Entry form first, then Display in again, different instances of Excel, only the read-only copy of Display will appear.
The only time a read-only does not appear is when both files are in a single instance of Excel, which is not what I want.
Edit2:
For more info, here is the SQL I used (4 total)
SQL1 - select * from EntryTable
SQL2 - select A.*,[Date + Time] from Summary1 A left join (select [Die No], max (Date + Time) as [Date + Time] from Summary1 group by [Die No]) B on A.[Die No] = B.[Die No]
SQL3 - select * from Summary2 where [Date + Time] = Date + Time
SQL4 - select Project_No, Die_No, Description, Repair_Details, Status from Summary3
Workbook name in cell B9 = V:\Die Maintenance System v2\Die Maintenance Menu.xlsx
Update: My colleague has tested the system on her PC and tested no problems. I've been told its most likely my Excel settings. But for the life of me, I can't figure out what is causing it. What type of setting is used to prevent the read-only file from appearing?
Edit: I can see that this post has gone on too long. I decided to continue this on a new thread right here.
So i would do it with the Workbook.Open() Method.
Sub Example()
Dim wb as Workbook
Dim path as String
path = "C:\Users\User\Desktop\1.xlsx"
set wb = Workbook.Open(path)
End Sub
Now you can use wb to execute every vba function. Then there a options to check if a workbook is already opened, look here. I dont think you can do that with adodb.
I tired using ACE and it worked just fine. It didn't open a new file.
szConnect = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source='" & _
wbWorkBook & "';" & _
"Extended Properties='Excel 12.0;HDR=Yes;IMEX=1';"
Your SQL FROM clause is referencing a different named range. Please post your SQL text. It must be qualified to correct Named Range or sheet name.
SELECT Project No, Die No, Description, Repair Details, Status
FROM DATA1 <- correct this to qualified named range or sheet name
like
FROM [Entry Form$] 'or
FROM [Named Range] <- this can be found in Formulas | Name Manager
Edit:
I am not sure about your "1st" source workbook's location so let us try to insert the line I commented below
wbWorkBook = ThisWorkbook.Sheets("Inner Workings").Range("B9").Text
wbWorkBook = Workbooks(wbWorkbook).FullName '<- add this line
If it still does not work, please post your SQL AND Workbook name in cell B9.
Edit 2:
What is the result if you change the FROM clause like:
select * from [EntryTable$]
Edit 3: Do you have password? if so, try to disable it first to isolate the problem in read only.
I'm trying to import some data from an Access .MDB file into an Excel 2013 spreadsheet.
So far, I've tried converting the database to 2007 format but that hasn't worked (2 methods I've tried so far)
Is there a simpler way of importing data straight from a table in the .MDB file into a sheet in my Excel 2013 spreadsheet? (End result)
Sub convertaccessdb()
Application.ConvertAccessProject _
SourceFilename:="C:\new\pabxcalls.mdb", _
DestinationFilename:="C:\My Documents\pabxcalls2007.accdb", _
DestinationFileFormat:=acFileFormatAccess2007
End Sub
' **** THIS GIVES AN RUNTIME ERROR 438 - OBJECT DOESN'T SUPPORT THIS PROPERTY OR METHOD ***
And
Sub Command7_Click()
Dim app As New Access.Application
app.Visible = True
app.AutomationSecurity = msoAutomationSecurityLow
app.SysCmd 603, "C:\New\pabxcalls.mdb", "C:\new\pabxcalls_new.mdb"
Set app = Nothing
End Sub
' *** THIS RUNS, BUT DOES NOT OUTPUT THE FILE REQUIRED ***
Thanks guys!!!
Found out that I didn't actually have to convert the database. Simply pull data using a query and ADODB connection, as follows.
Thanks anyway!
Sub GetCallData()
Dim objAdoCon As Object
Dim objRcdSet As Object
Dim DailyOutgoing, DailyIncoming, MonthlyOutgoing, MonthlyIncoming As String
DailyOutgoing = "SELECT calls.Extension,Sum(calls.Duration) FROM calls WHERE (((calls.Calldate)=Date())) AND (((calls.calltype)=""O"")) GROUP BY calls.Extension, calls.Calldate ;"
DailyIncoming = "SELECT calls.Extension,Sum(calls.Duration) FROM calls WHERE (((calls.Calldate)=Date())) AND (((calls.calltype)=""I"")) GROUP BY calls.Extension, calls.Calldate ;"
Set objAdoCon = CreateObject("ADODB.Connection")
Set objRcdSet = CreateObject("ADODB.Recordset")
objAdoCon.Open "Provider = Microsoft.Jet.oledb.4.0;Data Source = \\remotehost\PABXSoft\Call Collector\Data\pabxcalls.mdb"
' *** GET DAILY OUTGOING ***
ThisWorkbook.Worksheets("CALL_DATA").Range("A3:B24").Value = ""
objRcdSet.Open DailyOutgoing, objAdoCon
ThisWorkbook.Worksheets("CALL_DATA").Range("A3").CopyFromRecordset objRcdSet
Set objRcdSet = Nothing
' *** GET DAILY INCOMING ***
Set objRcdSet = CreateObject("ADODB.Recordset")
ThisWorkbook.Worksheets("CALL_DATA").Range("A27:B46").Value = ""
objRcdSet.Open DailyIncoming, objAdoCon
ThisWorkbook.Worksheets("CALL_DATA").Range("A27").CopyFromRecordset objRcdSet
Set objAdoCon = Nothing
Set objRcdSet = Nothing
End Sub
I would like to create a program in Excel that loops through a list of Access databases and writes the VBA that exists in the Access modules. I have found some code that I can run from Access which writes the VBA that exists in the Access modules. I am trying to figure out how to reference the database files from Excel and run the program on each database file. I will probably be able to figure out how to loop through the database files. I just need help with referencing the database file in the below code.
I can open the database with something like this:
Dim cstrDbFile As String = "C:\Database51.accdb"
Dim objShell As Object
Set objShell = CreateObject("WScript.Shell")
objShell.Run cstrDbFile
I also tried to set up a reference to Access like this:
Dim appAccess As Object
Set appAccess = CreateObject("Access.Application")
appAccess.OpenCurrentDatabase ("C:\Database51.accdb")
I need to figure out how to refer to the Access database in:
Application.VBE.ActiveVBProject.VBComponents
I probably need to figure out how to create a reference to replace ActiveVBProject.
Below is some code I found which writes the contents of VBA modules. I don't remember where I found it.
For Each Component In Application.VBE.ActiveVBProject.VBComponents
With Component.CodeModule
'The Declarations
For Index = 1 To .CountOfDeclarationLines
Debug.Print .Lines(Index, 1)
Next Index
'The Procedures
For Index = .CountOfDeclarationLines + 1 To .CountOfLines
Debug.Print .Lines(Index, 1)
Next Index
End With
Next Component
The following code will let you see Access database objects, but I don't know how to export the code (DoCmd not in Excel?). Your task would be VERY simple to do from Access, so I would reconsider...
Option Explicit
' Add a reference to the DAO Object Library
Sub Read_Access_VBA()
Dim dbs As DAO.Database
Dim ctr As DAO.Container
Dim doc As DAO.Document
Dim iC As Integer
Dim iD As Integer
Dim i As Integer
Dim mdl As Module
Set dbs = DBEngine.OpenDatabase("c:\TEMP\106thRoster.mdb", False, False, _
"MS Access;")
Debug.Print "----------------------------------------"
For iC = 0 To dbs.Containers.Count - 1
Debug.Print "Container: " & dbs.Containers(iC).Name
If dbs.Containers(iC).Documents.Count > 0 Then
For iD = 0 To dbs.Containers(iC).Documents.Count - 1
Debug.Print vbTab & "Doc: " & dbs.Containers(iC).Documents(iD).Name
Next iD
Else
Debug.Print " No Documents..."
End If
Next iC
'Set ctr = dbs.Containers!Modules
dbs.Close
Set doc = Nothing
Set ctr = Nothing
Set dbs = Nothing
End Sub
I was able to find some code that will assist me with my final goal: Exporting MS Access Forms and Class / Modules Recursively to text files?
Below are the most significant lines that will allow me to make progress with the project.
LineCount = oApp.Forms(Name).Module.CountOfLines
FileName = Path & "\" & Name & ".vba"
F = FreeFile
Open FileName For Output Access Write As #F
Print #F, oApp.Forms(Name).Module.Lines(1, LineCount)
Close #F
I have a shared, protected workbook that has a button to bring up a search form. There are two fields on this form, txtYear and cbxRegion, that I need enabled. Whenever I try to open the fields, it works until I exit Excel.
I have tried unprotecting the workbook, unsharing it, and commenting out any reference in the VBA to reprotecting the form. And still, even the edited VBA reverts back to the original.
This is the section of code referring to the form I need enabled. Any assistance would be greatly appreciated. I'm using Excel 2010.
Private Sub UserForm_Initialize()
Dim strDb As String
Dim rs As ADODB.Recordset
Dim cn As ADODB.Connection
Dim row As Integer
Dim AccessVersionID As String
cbxRegion.Value = Worksheets("Parameters").Cells(5, 14)
Me.txtYear = Worksheets("Parameters").Cells(4, 7)
Me.chkBoth = Worksheets("Parameters").Cells(9, 2)
Me.chkConsultant = Worksheets("Parameters").Cells(7, 2)
Me.chkInHouse = Worksheets("Parameters").Cells(8, 2)
'Set region values
'Open connection
'Select Case SysCmd(acSysCmdAccessVer)
'Case 11: AccessVersionID = "2003"
'End Select
'If AccessVersionID = "2003" Then
' strDb = Worksheets("Parameters").Cells(17, 2).Value 'This will reference the path
'Else
strDb = Worksheets("Parameters").Cells(18, 2).Value
'End If
Set cn = New ADODB.Connection
cn.Open "Provider=Microsoft.ACE.OLEDB.12.0; Data Source=" & strDb & ";"
Set rs = New ADODB.Recordset
'Get recordset
With rs
Set .ActiveConnection = cn
.Open "Select * From LookupRegion"
.Requery
End With
'Add regions
row = 0
With rs
.MoveFirst
Do Until .EOF
cbxRegion.AddItem ![region]
cbxRegion.list(row, 1) = ![RegionName]
row = row + 1
.MoveNext
Loop
End With
'Close the recordset
rs.Close
Set rs = Nothing
cn.Close
Set cn = Nothing
End Sub
I managed to get it to work. In order, I:
Unshared the workbook
Unprotected the workbook
Saved, closed, and reopened the workbook to make sure the settings stayed
Enabled the fields
Saved, closed, and reopened the workbook to make sure the settings stayed
Protected the workbook
Shared the workbook (as required by the specifications I was given, I would rather not share it but it really isn't my call here)
Saved, closed, and reopened the workbook to make sure the settings stayed
And now it works exactly as I need it to.
As far as I am concerned, the best advice anyone can give you for a shared workbook is: don't use them.
Shared Workbooks are impossible to troubleshoot. Their aberrant behaviour cannot be fixed. They don't follow any logic. Once a shared workbook starts acting up and behaving strangely, you have reached the inevitable end stage. Nothing can be done to fix it. The behaviour is not necessarily reproducible.
If you need simultaneous multi-user write access to a dataset, then Excel is the wrong tool. Use a database.
Don't use shared workbooks.
Im trying to load the contents of a textfile into a variable but I get the titular error, searching the VBA knowledgebase led me to belive that
Set mySQL = My.Computer.FileSystem.ReadAllText("C:\sql_query_temp.res")
Would solve it but that just produces 'Error : Object Required' when I run. Heres my code, what have I missed?
'Requires Microsoft ActiveX Data Objects x.x library in references
Public Sub ConnectToOdbc()
Dim myconn As New ADODB.Connection
Dim myrs As New Recordset
Dim mySQL As String
Dim myrows As Long
'Open file containing SQL query
mySQL = My.Computer.FileSystem.ReadAllText("C:\sql_query_temp.res") <----- bad!
'Open Connection
myconn.Open "DSN=database"
'Do Query
myrs.Source = mySQL
Set myrs.ActiveConnection = myconn
myrs.CursorLocation = adUseClient
myrs.Open
'Count Rows
myrows = myrs.RecordCount
'Add text to word document!
Selection.TypeText (myrows)
'Close Connection
myrs.Close
myconn.Close
End Sub
My.Computer is VB.NET which is entirely different from VBA instead you can;
Function readFile(path As String) As String
Dim hF As Integer
hF = FreeFile()
Open path For Input As #hF
readFile = Input$(LOF(hF), #hF)
Close #hF
End Function
...
mySQL = readFile("C:\sql_query_temp.res")
I think this might work.
It will allow you to search any given directory rather than fixating on just the one. This directory is then placed onto the variable 'inFileName'
inFileName = Application.GetOpenFilename("Text & r01 Files(*.*),*.*", , "Open Neutral File", "OPEN)
Hope this helps.