VBA Update Existing Records - ms-access

I was looking to setup VBA that would add new records to a table, if they did not currently exist (same ID #) and then update all of the columns for existing records (same ID #). I was able to setup the insert statement, but am coming up with errors for updating.
The code below states that I have a
Syntax error (missing operator) in query expression '[update].[date] from [update].
Any help you may provide would be very helpful.
Function Update()
Dim dbs As Database
Set dbs = CurrentDb
dbs.Execute "Update historic_archive Set historic_archive.DATE = [update].[DATE] FROM [update], historic_archive WHERE [update].[id] = historic_archive.id;"
End Function

Two things.
1. Please read more about SQL update command.
2. Never use names that resemble functions like date, update. Read more about naming conventions.
'Below update command will update historic date with update table.date where both id's are same.
Function Update()
Dim dbs As Database
Set dbs = CurrentDb
dbs.Execute "UPDATE historic_archive inner join [UPDATE] ON [update].[id] = historic_archive.id " & _
"SET historic_archive.[DATE] = [update].[DATE];"
End Function

You probably want to change your "WHERE" to "ON"
Function Update()
Dim dbs As Database
Set dbs = CurrentDb
dbs.Execute "UPDATE historic_archive " _
& "SET historic_archive.DATE = [update].[DATE] " _
& "FROM [update] " _
& "JOIN historic_archive ON [update].[id] = historic_archive.id;"
End Function

Related

In VBA, how to convert filter to sql string where clause

Has anyone got a better way to do this? I want the users to be able to filter to a set of records, and then use a sql statement to do something to just the filtered records.
My code at the moment looks something like the following, and it works. But I do wonder if there is any better (or other?) way to achieve this that could avoid the clunky Replace statement? I wondered if I could do it using a recordset instead, but unless I'm missing something, there's no way to do set operations over a recordset, I'd have to loop through the records individually to do that.
Any other suggestions?
Dim db As Database
Dim frm As Form
Dim sqlCmd As String
Dim filter As String
Set db = CurrentDb
Set frm = Forms("frmReplen").Controls("FrmReplenSheet").Form
filter = Replace(frm.filter, "[FrmReplenSheet].", "")
sqlCmd = "UPDATE tblRCmDataSheetExtract " & _
"SET Completed = 1 " & _
"WHERE " & filter
db.Execute sqlCmd
The only thing you could do is to shorten your procedure. You could do something like this:
currentdb.Execute "UPDATE tblRCmDataSheetExtract SET Completed = 1 WHERE " & Replace(Forms!frmReplen!FrmReplenSheet.Form.Filter, "[FrmReplenSheet].", "")
Or when you're running it from within frmReplen you could use
currentdb.Execute "UPDATE tblRCmDataSheetExtract SET Completed = 1 WHERE " & Replace(Me.FrmReplenSheet.Form.Filter, "[FrmReplenSheet].", "")
or from within the subform "FrmReplenSheet":
currentdb.Execute "UPDATE tblRCmDataSheetExtract SET Completed = 1 WHERE " & Replace(Me.Form.Filter, "[FrmReplenSheet].", "")
that puts your whole procedure into one single command. It's up to you if you like better readability or shorter procedures.

MS Access Passthrough Query Update

I am trying to make an Update to a Passthrough query using MS Access to an ODBC server that I have no control over. The reason I have to use a Passthrough is that the records I am accessing have more than 255 fields (I would use a linked table if I could).
I've been using this resource to get the data using Passthrough (http://www.techonthenet.com/access/tutorials/passthrough/basics09.php)
The query is simply: SELECT FullName, PointNumber FROM DNP3.CDNP3AnalogIn
The ODBC Connect Str is: ODBC;DSN=SCX6_DB;LOCATION=Main;UID=admin;PWD=password;LOCALTIME=False;
Now inside an Access Database I have a table (SCADA DB Tags) with same name for the Fields (FullName, PointNumber), and I want to update the fields inside the ODBC Database using an Update Passthrough query, but I am unsure how to do this.
I saved the previous Query as DNP3_CDNP3AnalogIn Query, and tried to make a new Query:
UPDATE [DNP3_CDNP3AnalogIn Query] INNER JOIN [SCADA DB Tags] ON
[DNP3_CDNP3AnalogInQuery].FullName = [SCADA DB Tags].FullName
SET [DNP3_CDNP3AnalogIn Query].[PointNumber] = [SCADA DB Tags].[PointNumber];
But I get an error from Access: Operation must use an updateable query.
I know there is someway to do this but I can't seem to find an example (I might not be googling the correct phrase). Microsoft page (http://technet.microsoft.com/en-us/library/bb188204%28v=sql.90%29.aspx) says: There is, however, one important limitation: the results returned by SQL pass-through queries are always read-only. If you want to enable users to perform updates based on the data retrieved, you must write code to handle this. Unfortunately it doesn't give an example to do it!
Can anyone give me a solution, I can use VBA if required? I can also give more background if required. Unfortunately I'm not an expert in Access, I'm just trying to come up with an automated solution that could save me some time.
When they said that "If you want to enable users to perform updates based on the data retrieved [from a pass-through query], you must write code to handle this" they probably meant something like this:
Option Compare Database
Option Explicit
Public Sub UpdateSqlServer()
Dim cdb As DAO.Database, rst As DAO.Recordset
Dim con As Object ' ADODB.Connection
Dim cmd As Object ' ADODB.Command
Const adParamInput = 1
Const adInteger = 3
Const adVarWChar = 202
Set cdb = CurrentDb
Set rst = cdb.OpenRecordset( _
"SELECT " & _
"[SCADA DB Tags].FullName, " & _
"[SCADA DB Tags].PointNumber " & _
"FROM " & _
"[DNP3_CDNP3AnalogIn Query] " & _
"INNER JOIN " & _
"[SCADA DB Tags] " & _
"ON [DNP3_CDNP3AnalogIn Query].FullName = [SCADA DB Tags].FullName", _
dbOpenSnapshot)
Set con = CreateObject("ADODB.Connection")
con.Open "DSN=SCX6_DB;"
Set cmd = CreateObject("ADODB.Command")
cmd.ActiveConnection = con
cmd.CommandText = _
"UPDATE DNP3.CDNP3AnalogIn SET " & _
"PointNumber=? " & _
"WHERE FullName=?"
cmd.Parameters.Append cmd.CreateParameter("?", adInteger, adParamInput) ' PointNumber
cmd.Parameters.Append cmd.CreateParameter("?", adVarWChar, adParamInput, 255) ' FullName
cmd.Prepared = True
Do Until rst.EOF
cmd.Parameters(0).Value = rst!PointNumber
cmd.Parameters(1).Value = rst!FullName
cmd.Execute
rst.MoveNext
Loop
Set cmd = Nothing
con.Close
Set con = Nothing
rst.Close
Set rst = Nothing
Set cdb = Nothing
End Sub
Notes:
The code uses your existing ODBC DNS.
It uses a Prepared Statement to perform the updates, increasing efficiency and protecting against failures related to SQL Injection.
The source Recordset performs an INNER JOIN on the pass-through query to ensure that the code only tries to update rows on the server that actually exist on the server.
Are you saying that [DNP3_CDNP3AnalogIn Query] is server side based and that table [SCADA DB Tags] is local based? In that case you cannot use a pass-through query.
However since the tables ARE in different locations pass-though cannot touch BOTH at the same time.
You can however execute "single" server side (pass-though) in a loop. If you setup a pass-though query and SAVE it, then this code will work:
Dim qdfPass As DAO.QueryDef
Dim rstLocal As DAO.Recordset
Dim strSQL As String
Dim strSQL2 As String
Set qdfPass = CurrentDb.QueryDefs("MyPass")
strSQL = "UPDATE [DNP3_CDNP3AnalogIn Query] " & _
"SET [DNP3_CDNP3AnalogIn Query].[PointNumber] = 'xxxx' " & _
"WHERE [DNP3_CDNP3AnalogInQuery].FullName = 'zzzz' "
Set rstLocal = CurrentDb.OpenRecordset("[SCADA DB Tags]")
Do While rstLocal.EOF = False
strSQL2 = Replace(strSQL, "xxxx", rstLocal!PointNumber)
strSQL2 = Replace(strSQL2, "zzzz", rstLocal!FullName)
qdfPass.SQL = strSQL2
qdfPass.Execute
rstLocal.MoveNext
Loop
rstLocal.Close

How to insert several fields into a table using ADO when two of the fields contain commas

I have an ado created recordset in access 2010 it returns 9 different fields from a stored procedure on sql server 2008 r2.
I am trying to use this recordset (which does populate) to insert all of the records into a table that matches the output. My issue is that two of the fields are name fields that have commas in them. For example Smith, Joseph-- I need to insert that comma into the appropriate field. Right now it throws an error because of the comma in the field.
Here is the code that I am using:
Option Compare Database
'Executes the filtering routine
Private Sub cmdApplyFilter_Click()
'If txtStartDate.Value And txtEndDate.Value Is Not Null Then
' QuickFilter
'Else
' DefaultRun
'End If
QuickFilter
'********** Filter as you type **********
'Private Sub txtFilter_Change()
' QuickFilter
'End Sub
End Sub
'Perform the actual filtering on the subform
Private Sub QuickFilter()
Dim Sql As String
Dim filter As String
If txtStartDate = vbNullString Then
'Reset the filter if the textbox is empty
'This will be the default sql statement to fill the subreport
SubForm.Form.FilterOn = False
Else
'Some common substitutions that users may have already inserted as wildchars
filter = Replace(txtStartDate, "%", "*")
filter = Replace("*" & filter & "*", "**", "*")
'Construct the filter for the sql statement
'/*********** GROUP BY GOES HERE ***********/
'Assign the filter to the subform
'SubForm.Form.filter = Sql
'SubFomr.Form.FilterOn = True
End If
End Sub
Private Sub Form_Load()
'Sets up the connection with the sql server database retrieves the stored procedure, executes it and puts the result set into a table
Dim Conn As ADODB.Connection
Dim Cmd As ADODB.Command
Dim Rs As ADODB.Recordset
Dim rs1 As ADODB.Recordset
Dim Connect As String
Dim filter As String
Connect = "Provider =SQLNCLI10; Data Source=10.50.50.140; Initial Catalog=CCVG; User Id = oe; Password = Orth03c0; "
'Establish the connection with sql server
Set Conn = New ADODB.Connection
Conn.ConnectionString = Connect
Conn.Open
'Open the recorset
Set Cmd = New ADODB.Command
Cmd.ActiveConnection = Conn
Cmd.CommandText = "dbo.cusGenNoNotesReport"
Cmd.CommandType = adCmdStoredProc
Set Rs = Cmd.Execute()
Dim x As Integer
If Not Rs.BOF And Not Rs.EOF Then
If Not Rs.BOF Then Rs.MoveFirst
Do Until Rs.EOF
For x = 0 To Rs.Fields.Count - 1
MsgBox Rs.Fields(x)
'DoCmd.RunSQL "INSERT INTO tblNoNotes (Provider, Facility, TicketNumber, Charges, FinancialClass, CPT, CPTDescription, PatientFullName, DateOfEntry) SELECT " & Rs.Fields(x).Value & ""
Next x
Rs.MoveNext
Loop
End If
'Process results from recordset, then close it.
'DoCmd.RunSQL "INSERT INTO tblNoNotes (Provider, Facility, TicketNumber, Charges, FinancialClass, CPT, CPTDescription, PatientFullName, DateOfEntry) VALUES (""" & Rs![Provider] & """,""" & Rs![Facility] & """ & Rs![TicketNumber] & """, """ & Rs![Charges] & """, """ & Rs![FinancialClass] & """, """ & Rs![CPT] & """, """ & Rs![CPTDescription] & """, """ & Rs![PatientFullName] & """, """ & Rs![DateOfEntry] & """ )"
Rs.Open
Rs.Close
Conn.Close
Set Rs = Nothing
Set Cmd = Nothing
Set Conn = Nothing
End Sub
You have an ADO Recordset, Rs, which contains data you want to add to your Access table. Instead of trying to fix the INSERT statement to add each row, it should be easier to open a DAO Recordset for the destination table and store the values from each ADO row by adding a new row the the DAO Recordset. Although this is still a RBAR (row by agonizing row) approach, it should be significantly faster than building and executing an INSERT statement for each row.
First of all, make sure to add Option Explicit to your module's Declarations section.
Option Compare Database
Option Explicit
Then use this code to append the ADO Recordset data to your table.
Dim db As DAO.Database
Dim rsDao As DAO.Recordset
Set db = CurrentDb
Set rsDao = db.OpenRecordset("tblNoNotes", _
dbOpenTable, dbAppendOnly + dbFailOnError)
Do While Not Rs.EOF
rsDao.AddNew
rsDao!Provider.Value = Rs!Provider.Value
rsDao!Facility.Value = Rs!Facility.Value
rsDao!TicketNumber.Value = Rs!TicketNumber.Value
rsDao!Charges.Value = Rs!Charges.Value
rsDao!FinancialClass.Value = Rs!FinancialClass.Value
rsDao!CPT.Value = Rs!CPT.Value
rsDao!CPTDescription.Value = Rs!CPTDescription.Value
rsDao!PatientFullName.Value = Rs!PatientFullName.Value
rsDao!DateOfEntry.Value = Rs!DateOfEntry.Value
rsDao.Update
Rs.MoveNext
Loop
rsDao.Close
Set rsDao = Nothing
Set db = Nothing
Note this approach means you needn't worry about whether PatientFullName contains a comma, or apostrophe ... or struggle with properly quoting field values to produce a valid INSERT statement. You simply store the value from one recordset field to the appropriate field in another recordset.
I think the real problem you're complaining about here is that your data in the ADO Recordset has quotes (sometimes called apostrophes) in it. Anytime quotes could possibly exist in your data you will need to check for and escape them before using the data in an SQL Statement. You will need to know this not only for inserts but also for performing filtering and creating WHERE statements as well. For example:
Replace(Rs![PatientFullName], "'", "''")
A simpler way to do this is to make your own little function. The "PQ" stands for pad quotes. You can name it whatever you want.
PQ(rs![PatientFullName])
Public Function PQ(s as String) as String
PQ = Replace(s, "'", "''")
End Function
But I also agree with HansUp that it's much easier to use recordsets for inserts. I basically never use SQL Insert statements anymore, except for places where I have no option such as SQL Server T-SQL.
Be aware that if you do want to use insert statements, you should consider using the following:
CurrentDb.Execute "INSERT INTO Statement Goes Here", dbFailOnError
This is considered to be a more robust solution than DoCmd.RunSQL, mostly because it runs in the context of the underlying Database Engine instead of the Access interface. Using CurrentDb.Execute prevents you from having to use DoCmd.SetWarning statements to turn off warnings.

How do I delete rows in a table from one database to the other in MS Access

I have two databases, a "Master" source (held locally) and a "Copy" database (to be distributed). Neither will be able to see each other once distributed (either locally or across a network), so we can't perform queries across the databases after distribution. I need to remove some content from the Copy database before it is distributed, so I decided to create a VBA script to produce the Copy databases for distribution.
There are lookups in the tables, so I have decided to hold a template database (which was copied from the master source, and then stripped the tables out of them), then am dropping the tables and have recreated them in an appropriate order.
I'm now needing to remove some of the data, and I'm struggling.
DeviceTable:
AutoNumber(ID)
Text(DeviceName)
Integer(ClusterID)
Text(Distribution)
ClusterTable:
AutoNumber(ID)
Text(ClusterName)
VirtualSystemTable:
AutoNumber(ID)
Text(VirtualSystemName)
Integer(ClusterID)
Integer(DeviceID)
InterfaceTable:
AutoNumber(ID)
Integer(VirtualSystemID)
Integer(ClusterID)
Integer(DeviceID)
Text(Description)
I need to remove entries from DeviceTable, ClusterTable, VirtualSystemTable and InterfaceTable for anything which is not marked as Distribution: "Public"
Normally I would do (in psudocode):
arrDEV = SQL("SELECT ID, ClusterID FROM DeviceTable WHERE Distribution<>"Public"")
Then, for each response, I would
arrVSYS = SQL("SELECT ID FROM VirtualSystemTable WHERE DeviceID=$arrDEV.ID OR ClusterID=$arrDEV.ClusterID")
SQL("DELETE FROM InterfaceTable WHERE DeviceID=$arrDEV.ID OR ClusterID=$arrDEV.ClusterID OR VirtualSystemID=$arrVSYS.ID")
SQL("DELETE FROM VirtualSystemTable WHERE DeviceID=$arrDEV.ID OR ClusterID=$arrDEV.ClusterID")
SQL("DELETE FROM ClusterTable WHERE ID=$arrDEV.ClusterID")
My issue is that I can't work out how to perform these queries across the database link. I amusing ADODB. I normally code in PHP, so this is a bit of a struggle for me!
You have the right idea but VBA is quite a bit different from PHP. You can't use variables inside strings with first exiting the string, concatenating the variable, and then starting the string again.
Where you would typically use an array called arrDev or arrVSys, we use a DAO Recordset or ADO Recordset object inside MS Access.
Updated answer to reflect the information you gave:
Dim db As DAO.Database
Dim sSQL as String
Dim arrDEV As DAO.Recordset, Dim arrVSYS as DAO.Recordset
Set db = OpenDatabase("C:\SomeDatabase.accdb")
sSQL = "SELECT ID, ClusterID FROM DeviceTable WHERE Distribution <> 'Public'"
Set arrDEV = db.Open(sSQL)
If Not (arrDEV.EOF and arrDEV.BOF) Then
arrDEV.movefirst
Do Until arrDEV.eof = True
sSQL = "SELECT ID FROM VirtualSystemTable WHERE DeviceID = " & arrDEV("ID") & _
" OR ClusterID = " & arrDEV("ClusterID")
Set arrVSYS = CurrentDb.Open(sSQl)
sSQL = "DELETE FROM InterfaceTable WHERE DeviceID = " & arrDEV("ID") & _
" OR ClusterID = " & arrDEV("ClusterID") & " OR VirtualSystemID = " & arrVSYS("ID")
CurrentDb.Execute sSQl, dbFailOnError
sSQL = "DELETE FROM VirtualSystemTable WHERE DeviceID = " & arrDEV("ID") & " OR ClusterID = " & arrDEV("ClusterID")
CurrentDb.Execute sSQl, dbFailOnError
sSQL = "DELETE FROM ClusterTable WHERE ID = " & arrDEV("ClusterID")
CurrentDb.Execute sSQl, dbFailOnError
arrVSYS.Close
arrDEV.MoveNext
Loop
End If
'Cleanup
Set arrVSYS = Nothing
arrDEV.Close
Set arrDEV = Nothing
db.close
Set db = Nothing
The code is untested and could have some errors. The only thing I didn't plan for was having more than one record in arrVSYS. If that recordset will have multiple records then you'll need yet another loop.
As you can see, you don't need to use ADO to access another Access database. But if the "outside" database is something other than Access then yes, you would need to use ADO.

"Transaction cannot have multiple recordsets with this cursor type." Error

I am getting the following error in my classic asp application:
Microsoft OLE DB Provider for ODBC Drivers error '80004005'
Transaction cannot have multiple recordsets with this cursor type.
Change the cursor type, commit the transaction, or close one of the
recordsets.
i am working on migrating the code from Oracle to SQL Server 2008, and this is an issue that i keep seeing here and there, all through out the application.
can't seem to find any fixes for it.
this particular case in this block of code: (i changed the selects to make them shorter)
Set MyConn = Server.CreateObject("ADODB.Connection")
Call OpenORPSConnect(MyConn)
ql = "Select username from mytable"
set rs = MyConn.Execute(sql)
if not rs.EOF then username = rs(0)
if username = "" then username = theUser
rs.close()
set rs = nothing
MyConn.BeginTrans()
sql = "Select someReport from MyTable"
set rs = MyConn.Execute(sql)
do while not rs.EOF
TIMESTAMP = rs("TIMESTAMP")
rev = rs("REV")
select case whatChange
case "Target date"
sql = "Insert into " & caJustTable & _
" (TEXT, TIMESTAMP, CURRENTFLAG)" & _
" Values ( Text& "','" & COPY_TS & "', 'Y')""
MyConn.Execute(sql)
end select
sql = "update table, set this to that"
MyConn.Execute(sql) <-------- error happens here sometimes....
end if
rs.movenext
loop
rs.close()
set rs = nothing
Since this was answered in the comments I wanted to turn it into a better answer
Your problem seems to be the MyConn.BeginTrans() has no MyConn.CommitTrans() or MyConn.RollbackTrans() after the Insert Statement in your select case; therefore, an error is thrown when you try to update the data. If you commit or Rollback after that insert execute then your next execute should work just fine. The fact that the MyConn.BeginTrans() is before a simple select statement you might consider moving it after the select.
I would do something like this (if you want to use transactions):
'MyConn.BeginTrans()
sql = "Select someReport from MyTable"
set rs = MyConn.Execute(sql)
do while not rs.EOF
TIMESTAMP = rs("TIMESTAMP")
rev = rs("REV")
select case whatChange
case "Target date"
MyConn.BeginTrans()
sql = "Insert into " & caJustTable & _
" (TEXT, TIMESTAMP, CURRENTFLAG)" & _
" Values ( Text& "','" & COPY_TS & "', 'Y')""
MyConn.Execute(sql)
MyConn.CommitTrans() 'You'll want to validate your data inserts properly before committing
end select
MyConn.BeginTrans()
sql = "update table, set this to that"
MyConn.Execute(sql) <-------- error happens here sometimes....
MyConn.CommitTrans()'You'll want to validate your data inserts properly before committing
end if
rs.movenext
loop
rs.close()
set rs = nothing
Transactions are generally used for inserting/updating or deleting data. Since you commented you don't know why the BeginTrans() statement is there then yes you could remove it altogether but I would recommend reading up on transactions and making sure you don't need it after your insert and update statements which occur later in the code.
Here is a reference for SQL transactions:
http://www.firstsql.com/tutor5.htm
I think MyConn may need to be closed at the end. Is this something you can try??
You already have an open recordset on the connection, so I think the problem is that your database does not support additional actions on the same connection until the recordset is closed. As a fix, I would recommend one of three options:
Use a second connection (on which you run the transaction) to run the sql statements that update the table.
Collect all statements into a list while you loop through the recordset, close the recordset, and then run the statements (using the same connection).
Or pull the data into a data table and loop through that rather than an open recordset.