UPDATED QUESTION:
I have Update sheet, this sheet contains unique ID that matched the access database ID, I'm trying to update the fields using excel values in "Update" sheet.
The ID is in the Column A the rest of the fields are stored from Column B to R. I'm trying to achieve the below, As follows:
Update the record(values from Column B to R) if Column A (ID) matched existing Access database ID. Then add text in Column S "Updated"
If the Column A (ID) did not found any match in the existing Access database ID, Then add text in Column S "ID NOT FOUND"
Loop to next value
So far, I have the below Sub for Update and Function for Existing ID (Import_Update Module), but I'm getting this error.
Sub Update_DB()
Dim dbPath As String
Dim lastRow As Long
Dim exportedRowCnt As Long
Dim NotexportedRowCnt As Long
Dim qry As String
Dim ID As String
'add error handling
On Error GoTo exitSub
'Check for data
If Worksheets("Update").Range("A2").Value = "" Then
MsgBox "Add the data that you want to send to MS Access"
Exit Sub
End If
'Variables for file path
dbPath = Worksheets("Home").Range("P4").Value '"W:\Edward\_Connection\Database.accdb" '##> This was wrong before pointing to I3
If Not FileExists(dbPath) Then
MsgBox "The Database file doesn't exist! Kindly correct first"
Exit Sub
End If
'find las last row of data
lastRow = Cells(Rows.Count, 1).End(xlUp).Row
Dim cnx As ADODB.Connection 'dim the ADO collection class
Dim rst As ADODB.Recordset 'dim the ADO recordset class
On Error GoTo errHandler
'Initialise the collection class variable
Set cnx = New ADODB.Connection
'Connection class is equipped with a —method— named Open
cnx.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & dbPath
'ADO library is equipped with a class named Recordset
Set rst = New ADODB.Recordset 'assign memory to the recordset
'##> ID and SQL Query
ID = Range("A" & lastRow).Value
qry = "SELECT * FROM f_SD WHERE ID = '" & ID & "'"
'ConnectionString Open '—-5 aguments—-
rst.Open qry, ActiveConnection:=cnx, _
CursorType:=adOpenDynamic, LockType:=adLockOptimistic, _
Options:=adCmdTable
'add the values to it
'Wait Cursor
Application.Cursor = xlWait
'Pause Screen Update
Application.ScreenUpdating = False
'##> Set exportedRowCnt to 0 first
UpdatedRowCnt = 0
IDnotFoundRowCnt = 0
If rst.EOF And rst.BOF Then
'Close the recordet and the connection.
rst.Close
cnx.Close
'clear memory
Set rst = Nothing
Set cnx = Nothing
'Enable the screen.
Application.ScreenUpdating = True
'In case of an empty recordset display an error.
MsgBox "There are no records in the recordset!", vbCritical, "No Records"
Exit Sub
End If
For nRow = 2 To lastRow
'##> Check if the Row has already been imported?
'##> Let's suppose Data is on Column B to R.
'If it is then continue update records
If IdExists(cnx, Range("A" & nRow).Value) Then
With rst
For nCol = 1 To 18
rst.Fields(Cells(1, nCol).Value2) = Cells(nRow, nCol).Value 'Using the Excel Sheet Column Heading
Next nCol
Range("S" & nRow).Value2 = "Updated"
UpdatedRowCnt = UpdatedRowCnt + 1
rst.Update
End With
Else
'##>Update the Status on Column S when ID NOT FOUND
Range("S" & nRow).Value2 = "ID NOT FOUND"
'Increment exportedRowCnt
IDnotFoundRowCnt = IDnotFoundRowCnt + 1
End If
Next nRow
'close the recordset
rst.Close
' Close the connection
cnx.Close
'clear memory
Set rst = Nothing
Set cnx = Nothing
If UpdatedRowCnt > 0 Or IDnotFoundRowCnt > 0 Then
'communicate with the user
MsgBox UpdatedRowCnt & " Drawing(s) Updated " & vbCrLf & _
IDnotFoundRowCnt & " Drawing(s) IDs Not Found"
End If
'Update the sheet
Application.ScreenUpdating = True
exitSub:
'Restore Default Cursor
Application.Cursor = xlDefault
'Update the sheet
Application.ScreenUpdating = True
Exit Sub
errHandler:
'clear memory
Set rst = Nothing
Set cnx = Nothing
MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure Update_DB"
Resume exitSub
End Sub
Function to Check if the ID Exists
Function IdExists(cnx As ADODB.Connection, sId As String) As Boolean
'Set IdExists as False and change to true if the ID exists already
IdExists = False
'Change the Error handler now
Dim rst As ADODB.Recordset 'dim the ADO recordset class
Dim cmd As ADODB.Command 'dim the ADO command class
On Error GoTo errHandler
'Sql For search
Dim sSql As String
sSql = "SELECT Count(PhoneList.ID) AS IDCnt FROM PhoneList WHERE (PhoneList.ID='" & sId & "')"
'Execute command and collect it into a Recordset
Set cmd = New ADODB.Command
cmd.ActiveConnection = cnx
cmd.CommandText = sSql
'ADO library is equipped with a class named Recordset
Set rst = cmd.Execute 'New ADODB.Recordset 'assign memory to the recordset
'Read First RST
rst.MoveFirst
'If rst returns a value then ID already exists
If rst.Fields(0) > 0 Then
IdExists = True
End If
'close the recordset
rst.Close
'clear memory
Set rst = Nothing
exitFunction:
Exit Function
errHandler:
'clear memory
Set rst = Nothing
MsgBox "Error " & Err.Number & " :" & Err.Description
End Function
My below code is working fine. I tried to address your above three points in a different way.
##########################
IMPORTANT
1) I have removed your other validations; you can add them back.
2) DB path has been hard coded, you can set it to get from a cells again
3) My DB has only two fields (1) ID and (2) UserName; you will have obtain your other variables and update the UPDATE query.
Below is the code which is working fine to meet your all 3 requests...Let me know how it goes...
Tschüss :)
Sub UpdateDb()
'Creating Variable for db connection
Dim sSQL As String
Dim rs As ADODB.Recordset
Dim cn As ADODB.Connection
Set cn = New ADODB.Connection
cn.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\test\db.accdb;"
Dim a, PID
'a is the row counter, as it seems your data rows start from 2 I have set it to 2
a = 2
'Define variable for the values from Column B to R. You can always add the direct ceel reference to the SQL also but it will be messy.
'I have used only one filed as UserName and so one variable in column B, you need to keep adding to below and them to the SQL query for othe variables
Dim NewUserName
'########Strating to read through all the records untill you reach a empty column.
While VBA.Trim(Sheet19.Cells(a, 1)) <> "" ' It's always good to refer to a sheet by it's sheet number, bcos you have the fleibility of changing the display name later.
'Above I have used VBA.Trim to ignore if there are any cells with spaces involved. Also used VBA pre so that code will be supported in many versions of Excel.
'Assigning the ID to a variable to be used in future queries
PID = VBA.Trim(Sheet19.Cells(a, 1))
'SQL to obtain data relevatn to given ID on the column. I have cnsidered this ID as a text
sSQL = "SELECT ID FROM PhoneList WHERE ID='" & PID & "';"
Set rs = New ADODB.Recordset
rs.Open sSQL, cn
If rs.EOF Then
'If the record set is empty
'Updating the sheet with the status
Sheet19.Cells(a, 19) = "ID NOT FOUND"
'Here if you want to add the missing ID that also can be done by adding the query and executing it.
Else
'If the record found
NewUserName = VBA.Trim(Sheet19.Cells(a, 2))
sSQL = "UPDATE PhoneList SET UserName ='" & NewUserName & "' WHERE ID='" & PID & "';"
cn.Execute (sSQL)
'Updating the sheet with the status
Sheet19.Cells(a, 19) = "Updated"
End If
'Add one to move to the next row of the excel sheet
a = a + 1
Wend
cn.Close
Set cn = Nothing
End Sub
You need to put the query inside the loop
Option Explicit
Sub Update_DB_1()
Dim cnx As New ADODB.Connection
Dim rst As New ADODB.Recordset
Dim qry As String, id As String, sFilePath As String
Dim lastRow As Long, nRow As Long, nCol As Long, count As Long
Dim wb As Workbook, ws As Worksheet
Set wb = ThisWorkbook
Set ws = wb.Sheets("Update")
lastRow = ws.Cells(Rows.count, 1).End(xlUp).Row
sFilePath = wb.Worksheets("Home").Range("P4").Value
cnx.open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & sFilePath
count = 0
For nRow = 2 To lastRow
id = Trim(ws.Cells(nRow, 1))
qry = "SELECT * FROM f_SD WHERE ID = '" & id & "'"
Debug.Print qry
rst.open qry, cnx, adOpenKeyset, adLockOptimistic
If rst.RecordCount > 0 Then
' Update RecordSet using the Column Heading
For nCol = 2 To 9
rst.fields(Cells(1, nCol).Value2) = Cells(nRow, nCol).Value
Next nCol
rst.Update
count = count + 1
ws.Range("S" & nRow).Value2 = "Updated"
Else
ws.Range("S" & nRow).Value2 = "ID NOT FOUND"
End If
rst.Close
Next nRow
cnx.Close
Set rst = Nothing
Set cnx = Nothing
MsgBox count & " records updated", vbInformation
End Sub
Related
I have multiple sql query in column c of worksheets("query") where the number of query can change. When there is a cell containing a sql query i want my code to run them one by one and populate the data in another worksheets("RESULT")
the final outcome would be :
run sql query number 1 and get result with the header in sheet RESULT (result will be sperad from range("A:M")
run sql query number 2 and get the reuslt in sheet RESULT right after the result 1 (whithout the hearder)
run sql query number 3 and get the result in sheet RESULT right after the result 1 &2 ( without the header)
...
...
...
...
...
run sql query number x and get the result in sheet RESULT right after the result 1 to x ( without the header)
Sub DCPARAMS()
Dim DBcon As ADODB.Connection
Dim DBrs As ADODB.Recordset
Set DBcon = New ADODB.Connection
Set DBrs = New ADODB.Recordset
Dim SSDF_SSDF As Workbook
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Dim DBQuery As String
Dim ConString As String
Dim SQL_query As String
Dim User As String
Dim Password As String
Dim RowsCount As Double
Dim intColIndex As Double
DBrs.CursorType = adOpenDynamic
DBrs.CursorLocation = adUseClient
Windows("SSDF MACRO.xlsm").Activate
Set SSDF_SSDF = ActiveWorkbook
User = SSDF_SSDF.Sheets("MACROS").Range("B4").Value
Password = SSDF_SSDF.Sheets("MACROS").Range("B5").Value
'error handling
On Error GoTo err
'I WANT THIS VALUE TO CHANGE BASED ON QUERY SHEETS COLUMN C
**SQL_query = Worksheets("query").Range("C2").Value**
' DELETING OLD VALUES
SSDF_SSDF.Sheets("RESULT").Select
SSDF_SSDF.Sheets("RESULT").Range("A1:Q1000000").Select
Selection.ClearContents
If User = "" Then MsgBox "Please fill in your user ID first"
If User = "" Then Exit Sub
If Password = "" Then MsgBox "Please fill in your Password first"
If Password = "" Then Exit Sub
'Open the connection using Connection String
DBQuery = "" & SQL_query
ConString = "Driver={Oracle in OraClient12Home1_32bit};Dbq=prismastand.world;Uid=" & User & ";Pwd=" & Password & ";"
DBcon.Open (ConString) 'Connecion to DB is made
'below statement will execute the query and stores the Records in DBrs
DBrs.Open DBQuery, DBcon
If Not DBrs.EOF Then 'to check if any record then
' Spread all the records with all the columns
' in your sheet from Cell A2 onward.
SSDF_SSDF.Sheets("RESULT").Range("A2").CopyFromRecordset DBrs
'Above statement puts the data only but no column
'name. hence the below for loop will put all the
'column names in your excel sheet.
For intColIndex = 0 To DBrs.Fields.Count - 1
Sheets("RESULT").Cells(1, intColIndex + 1).Value = DBrs.Fields(intColIndex).Name
Next
RowsCount = DBrs.RecordCount
End If
'Close the connection
DBcon.Close
'Informing user
Worksheets("REUSLT").Select
If Range("A2").Value <> "" Then
MsgBox "ALL GOOD, RUN NEXT MACRO"
Else: MsgBox "DATA IS MISSING IN DB PLEASE CHECK"
Exit Sub
End If
'alerts
Application.ScreenUpdating = True
Application.DisplayAlerts = True
Windows("SSDF MACRO.xlsm").Activate
SSDF_SSDF.Sheets("dc").Select
Exit Sub
err:
MsgBox "Following Error Occurred: " & vbNewLine & err.Description
DBcon.Close
'alerts
Application.ScreenUpdating = True
Application.DisplayAlerts = True
End Sub
I tired to run the code attached but it get only the sql query in cells c2. Iam sorry im very novice
Define a range for the results and offset it by the number of rows returned.
Option Explicit
Sub DCPARAMS()
Const WBNAME = "SSDF MACRO.xlsm"
Dim DBcon As ADODB.Connection, DBrs As ADODB.Recordset
Dim SSDF_SSDF As Workbook, wsResult As Worksheet, wsQuery As Worksheet
Dim rngResult As Range
Dim ConString As String, SQL_query As String
Dim User As String, Password As String
Dim intColIndex As Long, lastrow As Long, r As Long
Application.ScreenUpdating = False
Windows("SSDF MACRO.xlsm").Activate
Set SSDF_SSDF = Workbooks(WBNAME)
With SSDF_SSDF
Set wsResult = .Sheets("RESULT")
Set wsQuery = .Sheets("Query")
End With
'error handling
On Error GoTo err
' connect to db
With SSDF_SSDF.Sheets("MACROS")
User = .Range("B4").Value
Password = .Range("B5").Value
If User = "" Or Password = "" Then
MsgBox "Please fill in your user ID and password", vbCritical
Exit Sub
End If
End With
ConString = "Driver={Oracle in OraClient12Home1_32bit};Dbq=prismastand.world;" & _
"Uid=" & User & ";Pwd=" & Password & ";"
Set DBcon = New ADODB.Connection
DBcon.Open ConString 'Connecion to DB is made
Set DBrs = New ADODB.Recordset
DBrs.CursorType = adOpenDynamic
DBrs.CursorLocation = adUseClient
' delete old results
With wsResult
.Range("A:Q").ClearContents
Set rngResult = .Range("A2")
End With
With wsQuery
lastrow = .Cells(.Rows.Count, "C").End(xlUp).Row
For r = 2 To lastrow
'I WANT THIS VALUE TO CHANGE BASED ON QUERY SHEETS COLUMN C
SQL_query = .Cells(r, "C")
'below statement will execute the query and stores the Records in DBrs
DBrs.Open SQL_query, DBcon
If Not DBrs.EOF Then 'to check if any record then
'Above statement puts the data only but no column
'name. hence the below for loop will put all the
'column names in your excel sheet.
If wsResult.Cells(1, 1) = "" Then
For intColIndex = 0 To DBrs.Fields.Count - 1
wsResult.Cells(1, intColIndex + 1) = DBrs.Fields(intColIndex).Name
Next
End If
' Spread all the records with all the columns
' in your sheet from Cell A2 onward.
rngResult.CopyFromRecordset DBrs
' move down for next query
Set rngResult = rngResult.Offset(DBrs.RecordCount)
End If
DBrs.Close
Next
End With
'Close the connection
DBcon.Close
'Informing user
If wsResult.Range("A2").Value <> "" Then
MsgBox "ALL GOOD, RUN NEXT MACRO", vbInformation, "Rows = " & rngResult.Row - 2
Else:
MsgBox "DATA IS MISSING IN DB PLEASE CHECK", vbCritical
Exit Sub
End If
SSDF_SSDF.Activate
SSDF_SSDF.Sheets("dc").Select
Application.ScreenUpdating = True
Exit Sub
err:
MsgBox "Following Error Occurred: " & vbNewLine & err.Description
DBcon.Close
'alerts
Application.ScreenUpdating = True
End Sub
I have been using the below code which I have got from one of website, however, the user always tend to forget to check whether the data (IDs) are already sent to the Access Database, is there any way to loop the process and still export the data and change the font color of item not processed and introduce a new column with text "Not Imported"?
Sub Export_Data()
Dim cnn As ADODB.Connection 'dim the ADO collection class
Dim rst As ADODB.Recordset 'dim the ADO recordset class
Dim dbPath
Dim x As Long, i As Long
Dim nextrow As Long
'add error handling
On Error GoTo errHandler:
'On Error Resume Next
'Variables for file path and last row of data
dbPath = ActiveSheet.Range("I3").Value
nextrow = Cells(Rows.Count, 1).End(xlUp).Row
'Initialise the collection class variable
Set cnn = New ADODB.Connection
'Check for data
If Sheet1.Range("A2").Value = "" Then
MsgBox " Add the data that you want to send to MS Access"
Exit Sub
End If
'Connection class is equipped with a —method— named Open
'—-4 aguments—- ConnectionString, UserID, Password, Options
'ConnectionString formula—-Key1=Value1;Key2=Value2;Key_n=Value_n;
cnn.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & dbPath
'two primary providers used in ADO SQLOLEDB —-Microsoft.JET.OLEDB.4.0 —-Microsoft.ACE.OLEDB.12.0
'OLE stands for Object Linking and Embedding, Database
'ADO library is equipped with a class named Recordset
Set rst = New ADODB.Recordset 'assign memory to the recordset
'ConnectionString Open '—-5 aguments—-
'Source, ActiveConnection, CursorType, LockType, Options
rst.Open Source:="PhoneList", ActiveConnection:=cnn, _
CursorType:=adOpenDynamic, LockType:=adLockOptimistic, _
Options:=adCmdTable
'you now have the recordset object
'add the values to it
For x = 2 To nextrow
rst.AddNew
For i = 1 To 7
rst(Cells(1, i).Value) = Cells(x, i).Value
Next i
rst.Update
Next x
'close the recordset
rst.Close
' Close the connection
cnn.Close
'clear memory
Set rst = Nothing
Set cnn = Nothing
'communicate with the user
MsgBox " The data has been successfully sent to the access database"
'Update the sheet
Application.ScreenUpdating = True
'show the next ID
'Sheet1.Range("J3").Value = Sheet1.Range("K3").Value + 1
'Clear the data
'Sheet1.Range("A2:G1000").ClearContents
On Error GoTo 0
Exit Sub
errHandler:
'clear memory
Set rst = Nothing
Set cnn = Nothing
MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure Export_Data"
End Sub
Appreciate any help, as I don't know how to begin with the loop resume next. I hope you guys can help me change or modify this code.
After reading the Code, I think I finally understood what you wanted to do:
You wanted to check first if the Row has been exported or not before proceeding
So here is the updated Script.
I changed the sequence of several sections of the code as they were not coherent or will cause you errors.
I added a Column H supposing your columns are A to G, where H will store «Exported» after it has been successfully stored on the Database.
So this should work for you now:
Main Sub Exporting to Access
Sub Export_Data_Updated()
Dim dbPath As String
Dim lastRow As Long
Dim exportedRowCnt As Long
'add error handling
On Error GoTo exitSub
'Check for data
'##> This should be first as it is useless to open cnx or find path/last row if this will exit the sub
If Sheet1.Range("A2").Value = "" Then
MsgBox " Add the data that you want to send to MS Access"
Exit Sub
End If
'##> Only Continue when the above is fine
'##> Check if the path exits first
'Variables for file path
dbPath = ActiveSheet.Range("J3").Value '##> This was wrong before pointing to I3
If Not FileExists(dbPath) Then
MsgBox "The Database file doesn't exist! Kindly correct first"
Exit Sub
End If
'##> Only then that you can proceed
'find las last row of data
lastRow = Cells(Rows.Count, 1).End(xlUp).Row
'##> Change the Error handler now
Dim cnx As ADODB.Connection 'dim the ADO collection class
Dim rst As ADODB.Recordset 'dim the ADO recordset class
On Error GoTo errHandler
'Initialise the collection class variable
Set cnx = New ADODB.Connection
'Connection class is equipped with a —method— named Open
'—-4 aguments—- ConnectionString, UserID, Password, Options
'ConnectionString formula—-Key1=Value1;Key2=Value2;Key_n=Value_n;
cnx.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & dbPath
'two primary providers used in ADO SQLOLEDB —-Microsoft.JET.OLEDB.4.0 —-Microsoft.ACE.OLEDB.12.0
'OLE stands for Object Linking and Embedding, Database
'ADO library is equipped with a class named Recordset
Set rst = New ADODB.Recordset 'assign memory to the recordset
'ConnectionString Open '—-5 aguments—-
'Source, ActiveConnection, CursorType, LockType, Options
rst.Open Source:="PhoneList", ActiveConnection:=cnx, _
CursorType:=adOpenDynamic, LockType:=adLockOptimistic, _
Options:=adCmdTable
'##> Continue reading Database now
'you now have the recordset object
'add the values to it
'Wait Cursor
Application.Cursor = xlWait
'Pause Screen Update
Application.ScreenUpdating = False
'##> Set exportedRowCnt to 0 first
exportedRowCnt = 0
'##> Let's suppose Data is on Column A to G.
' --> So let's put the "Exported" on Column H
For nRow = 2 To lastRow
'##> Check if the Row has already been imported?
'If it it isn't then continue
If IdExists(cnx, Range("A" & nRow).Value) Then
'Item already exported, so update the Status
Range("H" & nRow).Value2 = "Exported"
Else
rst.AddNew 'Add New RecordSet
'Itirating Columns
For nCol = 1 To 7
rst.Fields(Cells(1, nCol).Value2) = Cells(nRow, nCol).Value 'Using the Excel Sheet Column Heading
Next nCol
rst.Update 'Update RecordSet
'##>Update the Status on Column H when the record is successfully updated
Range("H" & nRow).Value2 = "Exported"
'Increment exportedRowCnt
exportedRowCnt = exportedRowCnt + 1
End If
Next nRow
'close the recordset
rst.Close
' Close the connection
cnx.Close
'clear memory
Set rst = Nothing
Set cnx = Nothing
If exportedRowCnt > 0 Then
'communicate with the user
MsgBox exportedRowCnt & " row(s) successfully sent to the access database"
End If
'Update the sheet
Application.ScreenUpdating = True
exitSub:
'Restore Default Cursor
Application.Cursor = xlDefault
'Update the sheet
Application.ScreenUpdating = True
Exit Sub
errHandler:
'clear memory
Set rst = Nothing
Set cnx = Nothing
MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure Export_Data"
Resume exitSub
End Sub
Function Checking if File Exists:
NB: Now valid ID is only String Type
Function IdExists(cnx As ADODB.Connection, sId As String) As Boolean
'##> Set IdExists as False and change to true if the ID exists already
IdExists = False
'##> Change the Error handler now
Dim rst As ADODB.Recordset 'dim the ADO recordset class
Dim cmd As ADODB.Command 'dim the ADO command class
On Error GoTo errHandler
'Sql For search
Dim sSql As String
sSql = "SELECT Count(PhoneList.ID) AS IDCnt FROM PhoneList WHERE (PhoneList.ID='" & sId & "')"
'##> Execute command and collect it into a Recordset
Set cmd = New ADODB.Command
cmd.ActiveConnection = cnx
cmd.CommandText = sSql
'ADO library is equipped with a class named Recordset
Set rst = cmd.Execute 'New ADODB.Recordset 'assign memory to the recordset
'Read First RST
rst.MoveFirst
'##> If rst returns a value then ID already exists
If rst.Fields(0) > 0 Then
IdExists = True
End If
'close the recordset
rst.Close
'clear memory
Set rst = Nothing
exitFunction:
Exit Function
errHandler:
'clear memory
Set rst = Nothing
MsgBox "Error " & Err.Number & " :" & Err.Description
End Function
NB: Kindly read any comments preceeded by «'##>» as those are principal changes or explanations you need to understand
Updates:
Here is the File after the updates: https://drive.google.com/open?id=1XqEboSFed_6exDwvBZGOqcZWkN8YaslY
---> Fortunately, you sent me the file as I saw the DbPath was pointing on a wrong cell causing a missing file error. Now all is Fixed and working!
I wish you all the best!
I am working on an Excel application which allows users to enter hours work through userforms and info is stored in a Access DB. I am new to excel and access connections. I am able to connect to the database but record is not saved/created due to a run-time error at the .Update command.
Run-Time Error '-2147467259 (80004005)': Operation must use an updatable query.
I have searched and searched and can't find a solution to this problem. I hope someone is able to help. (code below)
Sub Export_Data_Access_TI1()
Dim dbPath As String
Dim x As Long, i As Long
Dim nextrow As Long
Dim user As String
Dim NewSht As Worksheet
Dim strQuery As String
Dim recDate As String
Dim Week_Of As String
user = Sheet1.Range("A1").Text
On Error GoTo ErrHandler:
'Variables for file path and last row of data
dbPath = "H:\PROJECTS\CAI_DOT-Time Tracker\CAI_EMP_SignIn_Database.accdb"
nextrow = Cells(Rows.Count, 1).End(xlUp).Row
'Initialise the collection class variable
Set cnn = New ADODB.Connection
'Check for data
If Sheets(user).Range("A2").Value = "" Then
MsgBox " There is no data to send to MS Access"
Exit Sub
End If
cnn.Mode = adModeReadWrite
'cnn.Mode = adModeShareDenyNone
cnn.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & dbPath
Set rst = New ADODB.Recordset 'assign memory to the recordset
rst.CursorLocation = adUseClient
rst.Open Source:="DATA", ActiveConnection:=cnn, _
CursorType:=adOpenKeyset, LockType:=adLockPessimistic, _
Options:=adCmdTable
'rst.Supports (adAddNew)
x = 2 'the start row in the worksheet
Do While Len(Sheets(user).Range("A" & x).Formula) > 0
With rst
.AddNew 'create a new record
.Fields("Date") = ActiveWorkbook.Sheets(user).Range("A" & x).Value
.Fields("Week_Of") = Sheets(user).Range("B" & x).Value
.Fields("Month") = Sheets(user).Range("C" & x).Value
.Fields("Name") = Sheets(user).Range("D" & x).Value
.Fields("Time_In") = Sheets(user).Range("E" & x).Value
.Fields("Time_Out") = Sheets(user).Range("F" & x).Value
.Fields("Time_In2") = Sheets(user).Range("G" & x).Value
.Fields("Time_Out2") = Sheets(user).Range("H" & x).Value
.Fields("Group") = Sheets(user).Range("I" & x).Value
.Fields("UniqueID") = Sheets(user).Range("J" & x).Value
.Fields("Comments") = Sheets(user).Range("K" & x).Value
.Update 'stores the new record
End With
x = x + 1 'next row
Loop
rst.Close
cnn.Close
Set rst = Nothing
Set cnn = Nothing
'communicate with the user
MsgBox " The data has been successfully sent to the access database"
'Update the sheet
Application.ScreenUpdating = True
'Clear the data
'Sheets(user).Range("A1:K1000").ClearContents
On Error GoTo 0
Exit Sub
ErrHandler:
'clear memory
Set rst = Nothing
Set cnn = Nothing
MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure Export_Data"
End Sub
As I understand, DATA is a query in the remote accdb. If so, it should be updateable. See, for example this: Why is my query not updateable? for criterias. If this is a table, check if you have read-write rights on accdb and the file has no read-only attribute.
I have a recordset object generated by the following code.
Private Sub GetID_Click()
'first find max id on sheet; used for if no ID is found on sheet
Dim myRange As Range
Dim maxIdOnSheet As Long
Dim clientSheet As Worksheet
Set clientSheet = Sheets("Client Codes")
Set myRange = clientSheet.Range("A1:A1048576")
maxIdOnSheet = WorksheetFunction.max(myRange) + 1
'set up connections with Nina's housing database
Dim cmd As New ADODB.Command
Dim conn As New ADODB.Connection
Dim rs As New ADODB.Recordset
Dim strConn As String
Dim strSQL As String
Dim IDdb As Long
Dim IDwb As Long
'connection string
strConn = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\db\path\here\db.accdb; Persist Security Info=False"
'open connection database
conn.Open strConn
'sql statement
strSQL = "SELECT * FROM Clients WHERE (((Clients.FirstName)='" & FirstName.Value & "') AND ((Clients.LastName)='" & LastName.Value & "'));"
'open connection with the recordset
rs.Open strSQL, conn, adOpenDynamic, adLockOptimistic
'use the late-bound application match method to find out where the firstname and lastname values are in the worksheet, if found
Dim first As Long
Dim last As Long
Dim foundWB As Boolean
Dim foundDB As Boolean
foundWB = False
foundDB = False
Dim base As Long
Dim curRow As Long
base = 1
'First check to make sure if both values are in the worksheet
If Not IsError(Application.Match(FirstName.Value, Range("c" & base & ":c1048576"), False)) And Not IsError((Application.Match(LastName.Value, Range("b" & base & ":b1048576"), False))) Then
'if it is in the worksheet, find where it is
While found = False
first = Application.Match(FirstName.Value, Range("c" & base & ":c1048576"), False)
last = Application.Match(LastName.Value, Range("b" & base & ":b1048576"), False)
If first = last Then
foundWS = True
curRow = curRow + first
IDwb = Cells(curRow, 1)
Else
If first < last Then
base = first + 1
curRow = curRow + first
ElseIf last < first Then
base = last + 1
curRow = curRow + last
End If
End If
Wend
Else
'if its not in the WS, it is now the highest +1
IDwb = WorksheetFunction.max(Range("a1:a1048576")) + 1
End If
'find if its in the database
If rs.EOF Then
'if its not in the database, find the highest number and add 1
rs.Close
strSQL = "SELECT MAX(Clients.[Client ID]) FROM Clients;"
rs.Open strSQL, conn, adOpenDynamic, adLockOptimistic
IDdb = rs.Fields(0) + 1
MsgBox (rs.Properties.Item("Address"))
Else
'if it is, find the first column
IDdb = rs.Fields(0)
foundDB = True
MsgBox (rs.Properties.Item("Address"))
End If
If foundWB = True Then
ClientID.Value = IDwb
ElseIf foundDB = True Then
ClientID.Value = IDdb
Else
If IDdb > IDwb Then
ClientID.Value = IDdb
ElseIf IDwb > IDdb Then
ClientID.Value = IDwb
Else
ClientID.Value = IDwb
End If
End If
End Sub
I have two data sources - the worksheet this macro is located in Excel, and an Access database. I enter client data, and assign it a specific code. The code does that successfully.
I also want to fill out a userform based on that code which is received. The code above successfully queries the database and can get the client ID. I also want things like address, city, state, zip, household income, that is stored in the DB, from the query.
If I was doing this in strictly Excel, I would use a match statement, and if strictly through Access, a SQL query. I'm trying to run this query on both an Excel worksheet and an Access database at the same time, or in the same code. This requires setting up a recordset object: documentation is found here
http://www.w3schools.com/asp/ado_ref_recordset.asp and here
https://msdn.microsoft.com/en-us/library/ms675841(v=vs.85).aspx.
I know that I can get the information with something like
name = rs.fields(1)
address = rs.fields(4)
city = rs.fields(5)
'...
I'd rather get the index dynamically. If people change the database around I'd like for the formula to be stable.
Lets say if the field "Address" could be index 4, 5, 6, 7, until whenever.
How do I dynamically find the index of a specific field in a recordset object?
So, how do I dynamically find the index of a specific field in a recordset object? Lets say if the field "Address" could be index 4,5,6,7, until whenever.
There isn't a direct property in an ADO recordset to get this, but you can find it by looping through the fields and keeping a tally like I do in this function:
Public Function GetRecordsetFieldIndexFromName(rs As adodb.Recordset, ColumnName As String) As Variant
' Pass in an ADODB recordset and a column name and return the column index.
' Returns index in base 0.
' Ben S. - 11/8/2019
On Error GoTo ErrorHandler
Dim i As Long
If rs Is Nothing Then
' Recordset is not loaded
Else
For i = 0 To rs.Fields.count - 1
'Debug.Print i, rs.Fields(i).Name
If rs.Fields(i).Name = ColumnName Then
GetRecordsetFieldIndexFromName = i
Exit For
End If
Next
End If
Exit_Function:
Exit Function
ErrorHandler:
MsgBox "Error #" & err.Number & " - " & err.Description & vbCrLf & "in procedure GetRecordsetFieldIndexFromName"
GoTo Exit_Function
Resume Next
Resume
End Function
Here is a simple test that you can try in Access.
Public Sub TestADOrs()
' BS 11/8/2019 - Test for GetRecordsetFieldIndexFromName
Dim i As Long
Dim strSQL As String
Dim conn As New adodb.Connection
Dim rs As New adodb.Recordset
' Set an object pointing to the current database connection
Set conn = CurrentProject.Connection
strSQL = "Select Top 1 * From MSysObjects"
rs.Open strSQL, conn, adOpenStatic, adLockOptimistic
Debug.Print GetRecordsetFieldIndexFromName(rs, "Flags") ' This should return 4
Set rs = Nothing
Set conn = Nothing
End Sub
I made similar functions to this that work with List Box and Combo Box controls. They will let you return the index or the value from the control by passing the control and the column/field name.
https://stackoverflow.com/a/58773219/1898524 - Reference List Box Column by Field Name
So basically I would like to be able to send date from excel to access, run some queries, and send the results back to excel, and do this all from excel VBA.
I would like to send my data, which is in an excel worksheet with sheet name "Enrollment Data Aggregate" to an access database H:\My file extension\Nameofdatabase.accdb and save it as a table with the name "Enrollment Data Aggregate".
I already have queries put together that run from the table "Enrollment Data Aggregate", so I think (hope) that I can delete that table, and when I pull in the new one from excel, the queries will still update. Then I would like access to run/update the queries and export them as separate tabs to my excel file.
Ideally, I would then like to continue doing other things in excel VBA. In other words, I'm really hoping that I can control all of this from excel. I have plenty of experience with excel and access, but I've only done about 4 VBA projects that were large like this (I've done lots of small macros) so I'm not super experienced. Any suggestions would be greatly appreciated.
To export data from Excel to Access:
Sub ADOFromExcelToAccess()
' exports data from the active worksheet to a table in an Access database
' this procedure must be edited before use
Dim cn As ADODB.Connection, rs As ADODB.Recordset, r As Long
' connect to the Access database
Set cn = New ADODB.Connection
cn.Open "Provider=Microsoft.Jet.OLEDB.4.0; " & _
"Data Source=C:\FolderName\DataBaseName.mdb;"
' open a recordset
Set rs = New ADODB.Recordset
rs.Open "TableName", cn, adOpenKeyset, adLockOptimistic, adCmdTable
' all records in a table
r = 3 ' the start row in the worksheet
Do While Len(Range("A" & r).Formula) > 0
' repeat until first empty cell in column A
With rs
.AddNew ' create a new record
' add values to each field in the record
.Fields("FieldName1") = Range("A" & r).Value
.Fields("FieldName2") = Range("B" & r).Value
.Fields("FieldNameN") = Range("C" & r).Value
' add more fields if necessary...
.Update ' stores the new record
End With
r = r + 1 ' next row
Loop
rs.Close
Set rs = Nothing
cn.Close
Set cn = Nothing
End Sub
To import data from Access to Excel:
Sub ADOImportFromAccessTable(DBFullName As String, _
TableName As String, TargetRange As Range)
' Example: ADOImportFromAccessTable "C:\FolderName\DataBaseName.mdb", _
"TableName", Range("C1")
Dim cn As ADODB.Connection, rs As ADODB.Recordset, intColIndex As Integer
Set TargetRange = TargetRange.Cells(1, 1)
' open the database
Set cn = New ADODB.Connection
cn.Open "Provider=Microsoft.Jet.OLEDB.4.0; Data Source=" & _
DBFullName & ";"
Set rs = New ADODB.Recordset
With rs
' open the recordset
.Open TableName, cn, adOpenStatic, adLockOptimistic, adCmdTable
' all records
'.Open "SELECT * FROM " & TableName & _
" WHERE [FieldName] = 'MyCriteria'", cn, , , adCmdText
' filter records
RS2WS rs, TargetRange ' write data from the recordset to the worksheet
' ' optional approach for Excel 2000 or later (RS2WS is not necessary)
' For intColIndex = 0 To rs.Fields.Count - 1 ' the field names
' TargetRange.Offset(0, intColIndex).Value = rs.Fields(intColIndex).Name
' Next
' TargetRange.Offset(1, 0).CopyFromRecordset rs ' the recordset data
End With
rs.Close
Set rs = Nothing
cn.Close
Set cn = Nothing
End Sub
Running an Access Query from Excel:
Sub CreateAndRunQuery()
'------------------------------------------------------------------------------------------
'This macro opens the Sample.accdb database, creates and runs an SQL query (filtering
'all the customers from Canada). Then, it copies selected fields back in the Excel sheet.
'The code uses late binding, so no reference to external library is required.
'Written By: Christos Samaras
'Date: 05/10/2013
'Last Updated: 29/11/2014
'E-mail: xristos.samaras#gmail.com
'Site: http://www.myengineeringworld.net
'------------------------------------------------------------------------------------------
'Declaring the necessary variables.
Dim con As Object
Dim rs As Object
Dim AccessFile As String
Dim strTable As String
Dim SQL As String
Dim i As Integer
'Disable screen flickering.
Application.ScreenUpdating = False
'Specify the file path of the accdb file. You can also use the full path of the file like:
'AccessFile = "C:\Users\Christos\Desktop\Sample.accdb"
AccessFile = ThisWorkbook.Path & "\" & "Sample.accdb"
'Set the name of the table you want to retrieve the data.
strTable = "Customers"
On Error Resume Next
'Create the ADODB connection object.
Set con = CreateObject("ADODB.connection")
'Check if the object was created.
If Err.Number <> 0 Then
MsgBox "Connection was not created!", vbCritical, "Connection Error"
Exit Sub
End If
On Error GoTo 0
'Open the connection.
con.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & AccessFile
'Create the SQL statement to retrieve the data from table.
'Get the necessary information (first name etc.) for all the Canadian customers.
SQL = "SELECT FirstName, LastName, Address, City, Phone FROM " & strTable & " WHERE COUNTRY='Canada'"
On Error Resume Next
'Create the ADODB recordset object.
Set rs = CreateObject("ADODB.Recordset")
'Check if the object was created.
If Err.Number <> 0 Then
'Error! Release the objects and exit.
Set rs = Nothing
Set con = Nothing
'Display an error message to the user.
MsgBox "Recordset was not created!", vbCritical, "Recordset Error"
Exit Sub
End If
On Error GoTo 0
'Set thee cursor location.
rs.CursorLocation = 3 'adUseClient on early binding
rs.CursorType = 1 'adOpenKeyset on early binding
'Open the recordset.
rs.Open SQL, con
'Check if the recordet is empty.
If rs.EOF And rs.BOF Then
'Close the recordet and the connection.
rs.Close
con.Close
'Release the objects.
Set rs = Nothing
Set con = Nothing
'Enable the screen.
Application.ScreenUpdating = True
'In case of an empty recordset display an error.
MsgBox "There are no records in the recordset!", vbCritical, "No Records"
Exit Sub
End If
'Copy the recordset headers.
For i = 0 To rs.Fields.Count - 1
Sheets("New Query").Cells(1, i + 1) = rs.Fields(i).Name
Next i
'Write the query values in the sheet.
Sheets("New Query").Range("A2").CopyFromRecordset rs
'Close the recordet and the connection.
rs.Close
con.Close
'Release the objects.
Set rs = Nothing
Set con = Nothing
'Adjust the columns' width.
Sheets("New Query").Columns("A:E").AutoFit
'Enable the screen.
Application.ScreenUpdating = True
'Inform the user that the macro was executed successfully.
MsgBox "The Canadian customers were successfully retrieved from the '" & strTable & "' table!", vbInformation, "Done"
End Sub
You will find more info here.
http://www.myengineeringworld.net/2013/10/running-access-queries-from-excel-vba.html