I'd like to update my Excel sheet's data via an SQL query. I know you can 'connect' to a sheet via ADODB.Connection and retrieve (SELECT) data from it in a ADODB.Recordset. However using the same process for an UPDATE query produces an 'Operation must use an updateable query' error. Is there any other way to achieve this?
An example code that produces the error:
Sub SQLUpdateExample()
Dim con As ADODB.Connection
Dim rs As ADODB.Recordset
Set con = New ADODB.Connection
con.Open "Driver={Microsoft Excel Driver (*.xls)};" & _
"DriverId=790;" & _
"Dbq=" & ThisWorkbook.FullName & ";" & _
"DefaultDir=" & ThisWorkbook.FullName
Set rs = New ADODB.Recordset
Set rs = con.Execute("UPDATE [Sheet1$] SET a = 10 WHERE b > 2")
Set rs = Nothing
Set con = Nothing
End Sub
The code expects to be in a saved .xls worksheet where Sheet1 includes a table with column headers (at least) a and b.
Didn't think that would be all to make it work...
Answer
Connections to Excel files are set to ReadOnly as default.
You have to add ReadOnly=False to your Connection String.
More information can be found at Microsoft Support
Related
I am getting the following error message, on the line rs.Open strSQL, cn , when trying to do a SQL select statement against a table range in an Excel spreadsheet:
The Microsoft Access database engine could not find the object
'Sheet1$A1:NC3'. Make sure the object exists and that you spell its
name and the path name correctly. If 'Sheet1$A1:NC3' is not a local
object, check your network connection or contact the server
administrator
I have run through the various Stack Overflow side bar suggestions for similar questions, and let Google have a run at it, but i have still not resolved this.
The object is local - check
Workbook is the current open workbook which is saved to the desktop -check
Network connection fine - check
Observations
Putting the SQL string as "Select * from [Sheet1$];" works fine.
Extend that, by adding the range reference, and the error comes back i.e. "Select * from [Sheet1$A1:NC3];" does not work.
As noted in comments, up to column IV i.e. [Sheet1$A1:IV3] is ok; column IW onward, and error message is back.
Note: Debug.Print confirms
ListObjects("Table1").Range.Address as $A$1:$NC$3
rngAddress is [Sheet1$A1:NC3];
Can anyone please spot what i have done incorrectly? Or suggest a workaround so i can still use this range (and preferably not select the entire sheet)
Here is the full code:
Option Explicit
Public Sub TestSQL()
'Tools > references > Microsoft ActiveX Object Library
Dim wb As Workbook
Dim wsSource As Worksheet
Dim strFile As String
Dim strCon As String
Dim strSQL As String
Set wb = ThisWorkbook
Set wsSource = wb.Worksheets("Sheet1")
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
strFile = ThisWorkbook.FullName
strCon = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & strFile _
& ";Extended Properties=""Excel 12.0;HDR=Yes;IMEX=1"";"
Set cn = CreateObject("ADODB.Connection")
Set rs = CreateObject("ADODB.Recordset")
cn.Open strCon
Dim rngAddress As String
rngAddress = "[" & wsSource.Name & "$" & Replace(wsSource.ListObjects("Table1").Range.Address, "$", "") & "];"
strSQL = "SELECT * FROM " & rngAddress
rs.Open strSQL, cn
End Sub
Environment: Windows 8, Excel 2016 64 bit.
I am trying to create a dynamic client excel file(s) that is linked to a "server" master excel file.
The goal is to keep all data updated in each of the files. Basically, when the client file is opened I have an update from the master file, and I then want to update the master file according to every change made in the client file.
I can get data using SELECT very easily but update query won't work.
Here is a part of the code :
Option Explicit
Private Type FichierSource
'Objet Fichier source.
Path As String
SourceSheet As String
TargetSheet As String
Columns As String
Filter As String
Name As String
End Type
Sub GetFiles()
'Take !M sheet to create files and their informations
Dim Base As FichierSource
'----------------------------
'Create files object
'----------------------------
'Fichier Source
Base.Path = "U:\Macros\SQL\Base.xlsx"
Base.SourceSheet = "DATA"
Base.TargetSheet = "Base2"
Base.Columns = "*"
Base.Filter = ""
Base.Name = "Base.xlsx"
'---------------------------
'Launch queries
'---------------------------
With Base
Call UPDATEQUERY(.Path, .SourceSheet, .TargetSheet, .Columns, .Filter)
End With
End Sub
Sub UPDATEQUERY(SourcePath As String, SourceSheet As String, TargetSheet As String, _
Columns As String, Filter As String)
Dim Cn As ADODB.Connection
Dim QUERY_SQL As String
Dim CHAINE_HDR As String
Dim STRCONNECTION As String
Dim i As Long
CHAINE_HDR = "[Excel 12.0 Macro;Provider=Microsoft.ACE.OLEDB.12.0;Extended Properties='HDR=YES;'] "
Set Cn = New ADODB.Connection
QUERY_SQL = _
"UPDATE [" & TargetSheet & "$] SET [Col] = (SELECT [Col] FROM [" & SourceSheet & "$] " & _
"IN '" & SourcePath & "' " & CHAINE_HDR & Filter & ")"
STRCONNECTION = _
"Provider=Microsoft.ACE.OLEDB.12.0;" & _
"Data Source='" & ThisWorkbook.FullName & "';" & _
"Extended Properties=""Excel 12.0 Macro;"";"
' QUERY_SQL = _
' "UPDATE [" & TargetSheet & "$] SET " & _
' "[Col] = '3'"
'MsgBox (QUERY_SQL)
Cn.Open STRCONNECTION
Cn.Execute (QUERY_SQL)
'--- Fermeture connexion ---
Cn.Close
Set Cn = Nothing
End Sub
When I execute the commented Sql Query so as to update the column 'Col' to '3
' it works perfectly however when I'm trying to update using the SELECT from the master file I get the following error
Operation must use an updatable query
UPDATE : I think the real problem is there :
I've read questions raised on the subject but any worked for me. Indeed If I set 'ReadOnly=False' in my connection string I get the following error 'Pilote ISAM introuvable' ('ISAM Driver not found).
UPDATE 2 : ISAM Driver error pops up whenever the connection string is not correct. (ex: a bad excel version number).
The ReadOnly=False (or Mode='Share Deny Write') is needed, so is the inner join.
I've already achieved all of this manually by adding a connection to the master file in excel connection so I know this should be possible.
Thanks
I have made a similar test with an update and a join, just for fun, and it worked perfectly. Here is my code:
Sub SQLUpdateExample()
Dim con As ADODB.Connection
Dim rs As ADODB.Recordset
Set con = New ADODB.Connection
con.Open "Driver={Microsoft Excel Driver (*.xls)};" & _
"DriverId=790;" & _
"Dbq=" & ThisWorkbook.FullName & ";" & _
"DefaultDir=" & ThisWorkbook.FullName & ";ReadOnly=False;"
Set rs = New ADODB.Recordset
Set rs = con.Execute("UPDATE [Sheet1$] inner join [Sheet2$] on [Sheet1$].test1 = [Sheet2$].test1 SET [Sheet1$].test3 = [Sheet2$].test3 ")
Set rs = Nothing
Set con = Nothing
End Sub
Perhaps all you need is this ;ReadOnly=False; in your connect string ?
Note that , despite the name I use for the driver, this works in a .XLSM file.
I added the SQL tag to your question so maybe an SQL guru can help you better. However, looking at the UPDATE syntax, then an UPDATE query without a WHERE clause will update the specified column of every row of the table with the same value. Looking at your SELECT part of the query, it looks as if that will retrieve more than one value.
If you want to update the column of the table with the value of a matching column in another table, you must join the tables using a WHERE clause. I think the following would be a correct example:
UPDATE table1 SET col = (SELECT col FROM table2 WHERE table1.key=table2.key)
OR
UPDATE t1
SET t1.Col = t2.Col
FROM table1 AS t1
INNER JOIN table2 AS t2
ON t1.Key = t2.Key
I cant seem to find an easy way of doing outside of just accessing the SQL from ACCESS SQL View and doing it manually. Is there some magic way to use this code below and do that?
Its worth pointing out that I am trying to do this from Excel's VBA.
Private Sub tryagain()
Dim con As ADODB.Connection
Dim rs As ADODB.Recordset
Set con = New ADODB.Connection
With con
.Provider = "Microsoft.ACE.OLEDB.12.0"
.Open "C:\Users\Ashleysaurus\Desktop" & "\" & "xyzmanu3.accdb"
End With
con.Execute "Invoice Query"
'How do output to Worksheet?
rs.Close
cmd.ActiveConnection.Close
End Sub
Simply use the ADO recordset object which you initialize, call the query, and then run the Range.CopyFromRecordset method (specifying the leftmost worksheet cell to place results).
Also, see the changed connection open routine with proper connection string. And because recordsets do not pull in column headers automatically but only data, an added loop was included iterating through recordset's field names.
Private Sub tryagain()
Dim con As New ADODB.Connection
Dim rs As New ADODB.Recordset
Dim strConnection As String
Dim i as Integer, fld As Object
strConnection = "Provider=Microsoft.ACE.OLEDB.12.0;" _
& "Data Source='C:\Users\Ashleysaurus\Desktop\xyzmanu3.accdb';"
con.Open strConnection
rs.Open "SELECT * FROM [Invoice Query]", con
' column headers
i = 0
Sheets(1).Range("A1").Activate
For Each fld In rs.Fields
ActiveCell.Offset(0, i) = fld.Name
i = i + 1
Next fld
' data rows
Sheets(1).Range("A2").CopyFromRecordset rs
rs.Close
cn.Close
End Sub
By the way, this same above setup can even query Excel workbooks as the Jet/ACE SQL Engine is a Windows technology (.dll files) available to all Office or Windows programs.
strConnection = "Provider=Microsoft.ACE.OLEDB.12.0;" _
& "Data Source='C:\Path\To\Workbook.xlsm';" _
& "Extended Properties=""Excel 8.0;HDR=YES;"";"
strSQL = "SELECT * FROM [Sheet1$]"
I want to use Excel vba to pull data from one database (server) and then push the same to another (local). However, I don't want to paste the data in any excel sheets during the process as this slows down the Excel a lot. How can I do this? Below is my work so far. I have highlighted where my code stops with error - Wrong number of arguments or invalid property assignment
Sub get_data_temp()
Dim cn As Object
Dim rs As Object
Dim strConnection As String
Dim server, port, id, pass As String
Dim strSQL As String
'get data from server (MS SQL based)
strSQL = Range("query_1").Value
server = Range("db_server").Value
port = Range("db_port").Value
id = Range("db_id").Value
pass = Range("db_pass").Value
Set cn = CreateObject("ADODB.Connection")
strConnection = "Provider=SQLOLEDB;Data Source=" & server & "," & port & ";User ID=" & id & ";Password=" & pass & ";"
cn.Open strConnection
Set rs = cn.Execute("SET NOCOUNT ON;" & strSQL)
'connect to ms access to import data
Dim xcn As Object
Dim xrs As Object
Dim xdbpath As String
Dim xstrConnection As String
Dim xdb_name As String
Dim xstrSQL As String
''''''''the code stops at the line below'''''''''''''
xstrSQL = "insert into crm_main select * from " & rs
''''''''the code stops at the line above'''''''''''''
xdb_name = "temp.accdb"
xdbpath = ThisWorkbook.Path & Application.PathSeparator & xdb_name
Set xcn = CreateObject("ADODB.Connection")
xstrConnection = "Provider=Microsoft.ACE.OLEDB.12.0;" & "Data Source=" & dbpath
xcn.Open xstrConnection
xcn.Execute(xstrSQL)
cn.Close
Set cn = Nothing
xcn.Close
Set xcn = Nothing
End Sub
I have the following code that is intended to update an Access database however when i run the macro i get an automation error. If i execute the SELECT statement, it runs fine. I don't need to select any values from the worksheet to update the database.
Private Sub UpdateRecord()
ThisWorkbook.Activate
Dim cn As Object
Dim rs As Object
Dim strSql As String
Dim strConnection As String
Set cn = CreateObject("ADODB.Connection")
strConnection = "Provider=Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=C:\temp\test.mdb"
strSql = "UPDATE table1 SET Name1='Test' WHERE Object_ID=2076;"
'strSql = "SELECT * FROM table1;"
cn.Open strConnection
Set rs = cn.Execute(strSql)
Worksheets("Sheet1").Select
Sheets("Sheet1").Range("A6").CopyFromRecordset rs
rs.Close
Set rs = Nothing
cn.Close
Set cn = Nothing
End Sub
Unfortunately I cannot repeat your situation and use ADODB. I recommend you to use native DAO library to work with MSJet (Access) database.
Sub qwe()
Dim dbe As New DAO.DBEngine
Dim dbs As DAO.Database
Dim rst As DAO.Recordset
Set dbs = dbe.OpenDatabase("C:\Users\nmaksudov\Documents\Database2.accdb")
dbs.Execute("UPDATE Table1 SET Field2='zzz' WHERE Field1=2")
Set rst = dbs.OpenRecordset("select * from Table1")
While Not rst.EOF
MsgBox rst.Fields(1).Value & "," & rst.Fields(2).Value
rst.MoveNext
Wend
MsgBox rst.RecordCount
End Sub
This should work perfect. Just add DAO library of correct version to your project. To find correct library open VBA editor in Access and choose Tools/References… menu. Find data access library in the list (in my case it is «Microsoft Office 12.0 Access database engine Object Library» or it could be «DAO 3.6» etc. Depens on version). After that open the same dialog in Excel and add the the object library.