Running Update Qry in VBA Based on a Query Value - vba

I am trying to run an Update Query in VBA and am at a lost as to what I'm supposed to write for the code. I'm running a query to find the most recent date from a table. That query works fine. Now I want to run an update query to update another table's date field to equal to the date that was queried. Here is what I have:
Dim Date1 As Date
Dim newdate1
'selects datadate 1
Date1 = CurrentDb.OpenRecordset("Select Max(Date1_Event) from TBL_Event WHERE ID = '" & [Forms]![FRM_Main]![ID] & "'")(0)
'update datadate 1
newdate1 = CurrentDb.OpenRecordset("Update Tbl_Name set CollectionDate = DataDate1 WHERE PID = '" & [Forms]![FRM_Main]![ID] & "'")(0)
Is there a way to run an update query like this? Thank you.

Action queries (DELETE, UPDATE, INSERT INTO) are to be executed (CurrentDb.Execute) while SELECT queries are to be opened as recordsets (CurrentDb.OpenRecordset).
Additionally, consider using parameterization to avoid any need of quote enclosure or string concatenation in query. And here the max date is calculated with domain aggregate, DMax(), instead of opening another query.
Dim strSQL As String
Dim qdef As Querydef
' PREPARE SQL STATEMENT
strSQL = "PARAMETERS [MaxDateParam] Date, [FormIDParam] Long;" _
& "UPDATE Tbl_Name SET CollectionDate = [MaxDateParam]" _
& " WHERE PID = [FormIDParam];"
' BUILD TEMP QUERY
Set qdef = CurrentDb.CreateQueryDef("", strSQL)
' BIND PARAMETERS
qdef!MaxDateParam = DMax("Date1_Event", "TBL_Event", "ID=" & [Forms]![FRM_Main]![ID])
qdef!FormIDParam = [Forms]![FRM_Main]![ID]
' EXECUTE ACTION
qdef.Execute dbFailOnError
Set qdef = Nothing
Though above may look unusual and slightly more lines. Don't be intimidated and run for the easy 1-2 lines. Parameterization is a programming industry best practice not just in VBA but across all general purpose languages that run dynamic SQL queries using values from user input.

Related

MS Access Update Query where column updated depends on field in another table

I have a table (InfoTable) that I would like to update based on another table (UpdTable). The UpdTable has three columns, OldEntry, NewEntry, and UpdateField. InfoTable has columns that will match UpdateField. I will have one line per update. So, for example, UpdTable:
OldEntry
NewEntry
UpdateField
Peter
Paul
Customer
123khjiv
3546sfdg
Serial Number
I would like to feed a SQL query or SQL code that takes UpdTable and have it update InfoTable. InfoTable has many columns, but I only want the columns in UpdateField modified.
I am trying to streamline this process since I am currently doing the updates one by one by hand. At this time, I do not have any code written as I have been unable to formulate a working plan.
Thank you.
Consider looping through the records of UpdTable and pass values into a parameterized UPDATE query using DAO Recordset and QueryDef objects:
Dim updRST As DAO.Recordset
Dim qDEF As DAO.QueryDef
' OPEN RECORDSET
Set updRST = CurrentDb.OpenRecordset("UpdTable")
updRST.MoveFirst
' LOOP THROUGH RECORDS
Do While Not updRST.EOF
' PREPARED SQL STATEMENT
sql = "PARAMETERS [new_val] TEXT, [old_val] TEXT; " _
& "UPDATE InfoTable SET [" & updRST!UpdateField & "] = [new_val] " _
& "WHERE [" & updRST!UpdateField & "] = [old_val]"
Set qDEF = CurrentDb.CreateQueryDef("", sql)
qDEF!new_val = updRST!NewEntry ' BIND PARAMS
qDEF!old_val = updRST!OldEntry
qDEF.Execute dbFailOnError ' EXECUTE ACTION
Set qDEF = Nothing
updRST.MoveNext
Loop
updRST.Close
Set updRST = Nothing

How can I combine these two SQL queries (Access/VBA)

I am using two SQL queries in VBA that i believe they could be done in one, but I cant get it to work. I Want to turn the VBA portion into a Query outside of VBA, the VBA keeps breaking my file due to the amount of data it processes. (By break i mean it gives a message that says "this file is not a valid database" rendering the file corrupted). I search for that error but all i found was not related to breaking because of VBA code.
Anyways, here are the two queries ran with VBA.
SELECT ET.VerintEID AS EID, Sum(ET.ExceptMin)/60 AS Exeptions
FROM Tbl_VExceptTime AS ET
INNER JOIN Tbl_VCodes ON ET.Exception = Tbl_VCodes.Exception
WHERE (ET.ExceptDate Between #" & sDate & "# And #" & eDate & "#)
GROUP BY ET.VerintEID, Tbl_VCodes.IsApd
HAVING Tbl_VCodes.IsApd = ""OFF"";
I loop these results to update a table.
Do While Not .EOF
SQL = "UPDATE Tbl_AttendanceByAgent SET EXC = " & recSet.Fields(1).Value & _
" WHERE VerintID = '" & recSet.Fields(0).Value & "'"
CurrentDb.Execute SQL
.MoveNext
Loop
I know that i can save the results from the first query into a table and without looping I can update the main table with another SQL query, but I believe it can be done on a single SQL. I have tried using an UPDATE with a SELECT of the first query but it just errors out on me with an invalid syntax.
Yes this could be achieved in one single query as shown below
UPDATE Tbl_AttendanceByAgent
SET Tbl_AttendanceByAgent.EXC = t2.Exeptions
from Tbl_AttendanceByAgent t1
inner join (
SELECT ET.VerintEID AS EID, Sum(ET.ExceptMin)/60 AS Exeptions
FROM Tbl_VExceptTime AS ET
INNER JOIN Tbl_VCodes as TV ON ET.Exception = TV.Exception
WHERE (ET.ExceptDate Between #" & sDate & "# And #" & eDate & "#)
GROUP BY ET.VerintEID, TV.IsApd
HAVING Tbl_VCodes.IsApd = 'OFF'
) AS t2 on t2.EID = t1.VerintID
Note: I suppose you will replace sDate, eDate with values within your code
This question is an answer to the described errors and the given code, although it technically does not answer the request for a single SQL statement. I started adding a comment, but that's just too tedious when this answer box allows everything to be expressed efficiently at once.
First of all, referring to CurrentDb is actually NOT a basic reference to a single object instance. Rather it is more like a function call that generates a new, unique "clone" of the underlying database object. Calling it over and over again is known to produce memory leaks, and at the least is very inefficient. See MS docs for details.
Although the given code is short, it's not sweet. Not only is it repeatedly creating new database objects, it is repeatedly executing an SQL statement to update what I assume is a single row each time. That also entails regenerating the SQL string each time.
Even if executing the SQL statement repeatedly was an efficient option, there are better ways to do that, like creating a temporary (in-memory) QueryDef object with parameters. Each loop iteration then just resets the parameters and executes the same prepared SQL statement.
But in this case, it may actually be more efficient to load the table being updated into a DAO.Recordset, then use the in-memory Recordset to search for a match, then use the recordset to update the row.
I suspect that addressing a couple of those issues would make your VBA code viable.
Dim db as Database
Set db = CurrentDb 'Get just a single instance and reuse
Dim qry as QueryDef
SQL = "PARAMETERS pEXC Text ( 255 ), pID Long; " & _
" UPDATE Tbl_AttendanceByAgent SET EXC = pEXC " & _
" WHERE VerintID = pID"
set qry = db.CreateQueryDef("", SQL)
'With recSet '???
Do While Not .EOF
qry.Parameters("pEXC") = recSet.Fields(1).Value
qry.Parameters("pID") = recSet.Fields(0).Value
qry.Execute
.MoveNext
Loop
'End With recSet '???
'OR an alternative
Dim recUpdate As DAO.Recordset2
Set recUpdate = db.OpenRecordset("Tbl_AttendanceByAgent", DB_OPEN_TABLE)
Do While Not .EOF
recUpdate.FindFirst "VerintID = " & recSet.Fields(0).Value
If Not recUpdate.NoMatch Then
recUpdate.Edit
recUpdate.Fields("EXC") = recSet.Fields(1).Value
recUpdate.Update
End If
.MoveNext
Loop
I realized in commenting on Gro's answer, that the original query's aggregate clauses will produce unique values on EID, but it then becomes obvious that there is no need to group on (and sum) values which do not have Tbl_VCodes.IsApd = 'OFF'. The query would be more efficient like
SELECT ET.VerintEID AS EID, Sum(ET.ExceptMin)/60 AS Exeptions
FROM Tbl_VExceptTime AS ET
INNER JOIN Tbl_VCodes ON ET.Exception = Tbl_VCodes.Exception
WHERE (ET.ExceptDate Between #" & sDate & "# And #" & eDate & "#)
AND Tbl_VCodes.IsApd = 'OFF'
GROUP BY ET.VerintEID;
BTW, you could consider implementing the same temporary QueryDef pattern as I showed above, then you'd change the first WHERE expression to something like
PARAMETERS PsDate DateTime, PeDate DateTime;
...
WHERE (ET.ExceptDate Between [PsDate] And [PeDate])
...

Adding a new record with VBA

I have a form in which one of the ComboBoxes lists all the documents of a given project. The user should select one and after pressing a button, and if present in Table Dessinsit opens a second form showing that record. If it is not present in that table, I want to add it in.
One of my collegues told me all I had to do was to execute an SQL query with VBA. What I have so far is this:
Dim rsDessin As DAO.Recordset
Dim strContrat As String
Dim strProjet As String
Dim strDessin As String
Dim sqlquery As String
'I think these next 3 lines are unimportant. I set a first query to get information I need from another table
strDessin = Me.Combo_Dessin
strProjet = Me.Combo_Projet
sqlquery = "SELECT [Projet HNA] FROM [Projets] WHERE [Projet AHNS] = '" & strProjet & "'"
Set rsDessin = CurrentDb.OpenRecordset(sqlquery)
If Not rsDessin.RecordCount > 0 Then 'If not present I want to add it
strContrat = rsDessin![Projet HNA]
sqlquery = "INSERT INTO Feuilles ([AHNS], [Contrat], [No Projet]) VALUES (strDessin, strContrat, strDessin)"
'Not sure what to do with this query or how to make sure it worked.
End If
'Checking my variables
Debug.Print strProjet
Debug.Print strContrat
Debug.Print strDessin
'By here I'd like to have inserted my new record.
rsDessin.Close
Set rsDessin = Nothing
I also read online that i could achieve a similar result with something like this:
Set R = CurrentDb.OpenRecordset("SELECT * FROM [Dessins]")
R.AddNew
R![Contrat] = strContrat
R![Projet] = strProjet
R![AHNS] = strDessin
R.Update
R.Close
Set R = Nothing
DoCmd.Close
Is one way better than the other? In the case where my INSERT INTO query is better, what should I do to execute it?
You're asking which is preferable when inserting a record: to use an SQL statement issued to the Database object, or to use the methods of the Recordset object.
For a single record, it doesn't matter. However, you could issue the INSERT statement like this:
CurrentDb.Execute "INSERT INTO Feuilles ([AHNS], [Contrat], [No Projet]) VALUES (" & strDessin & ", " & strContrat & ", " & strDessin & ")", dbFailOnError
(You should use the dbFailOnError option to catch certain errors, as HansUp points out in this answer.)
For inserting multiple records from another table or query, it is generally faster and more efficient to issue an SQL statement like this:
Dim sql = _
"INSERT INTO DestinationTable (Field1, Field2, Field3) " & _
"SELECT Field1, Field2, Field3 " & _
"FROM SourceTable"
CurrentDb.Execute sql
than the equivalent using the Recordset object:
Dim rsSource As DAO.Recordset, rsDestination As DAO.Recordset
Set rsSource = CurrentDb.OpenRecordset("SourceTable")
Set rsDestination = CurrentDb.OpenRecordset("DestinationTable")
Do Until rs.EOF
rsDestination.AddNew
rsDestination!Field1 = rsSource!Field1
rsDestination!Field2 = rsSource!Field2
rsDestination!Field3 = rsSource!Field3
rsDestination.Update
rs.MoveNext
Loop
That said, using an SQL statement has its limitations:
You are limited to SQL syntax and functions.
This is partially mitigated in Access, because SQL statements can use many VBA built-in functions or functions that you define.
SQL statements are designed to work on blocks of rows. Per-row logic is harder to express using only the Iif, Choose, or Switch functions; and logic that depends on the current state (e.g. insert every other record) is harder or impossible using pure SQL. This can be easily done using the Recordset methods approach.
This too can be enabled using a combination of VBA and SQL, if you have functions that persist state in module-level variables. One caveat: you'll need to reset the state each time before issuing the SQL statement. See here for an example.
One part* of your question asked about INSERT vs. Recordset.AddNew to add one row. I suggest this recordset approach:
Dim db As DAO.Database
Dim R As DAO.Recordset
Set db = CurrentDb
Set R = db.OpenRecordset("Dessins", dbOpenTable, dbAppendOnly)
With R
.AddNew
!Contrat = rsDessin![Projet HNA].Value
!Projet = Me.Combo_Projet.Value
!AHNS = Me.Combo_Dessin.Value
.Update
.Close
End With
* You also asked how to execute an INSERT. Use the DAO.Database.Execute method which Zev recommended and include the dbFailOnError option. That will add clarity about certain insert failures. For example, a key violation error could otherwise make your INSERT fail silently. But including dbFailOnError ensures you get notified about the problem immediately. So always include that option ... except in cases where you actually want to allow an INSERT to fail silently. (For me, that's never.)

Access VBA Code

Could any expert tell me why I always get this error "Expected:End of Statement" when I run the following code in Access VBA :
FNSQL = DoCmd.RunSQL _
"SELECT First_Name FROM tblPatient_Info WHERE Patient_ID = '" & 1967 & "';"
Thank you so much in advance!
Your code is wrong in a couple different ways.
You don't tell us what FNSQL is, so one can only imagine.
Mistake #1
Mentioned by #simoco, DoCmd.RunSQL is used in the following scenarios:
Here's the API: DoCmd.RunSQL SQLStatement UseTransaction
SQLStatement : A string expression that's a valid SQL statement for an action query or a data-definition query. It uses an INSERT INTO, DELETE, SELECT...INTO, UPDATE, CREATE TABLE, ALTER TABLE, DROP TABLE, CREATE INDEX, or DROP INDEX statement.
Include an IN clause if you want to access another database.
Mistake #2
FNSQL
I'm not sure what you are aiming to do with the results of the query, but this is for your general knowledge because if you didn't know the RunSQL syntax you may be unfamiliar with Recordsets.
You can assign your stored query's results to a Recordset and do with it as you please, much like a table.
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim SQL As String
Dim firstName As String
SQL = "SELECT First_Name FROM tblPatient_Info WHERE Patient_ID = 1967;"
' OR
SQL = "SELECT First_Name FROM tblPatient_Info WHERE Patient_ID = '" & 1967 & "';"
' You can write Debug.Print SQL to make sure you're getting the right SQL.
Set db = CurrentDb
Set rs = db.OpenRecordset(SQL)
' The disadvantage of this approach is that the query string must be compiled each time it runs
' OR
Set rs = db.OpenRecordset(YourSaveQueryName)
'This would be the faster method of the 2.
You can then manipulate your data however you want, and loop through the entire recordset.
firstName = "Larry"
If rs.BOF And rs.EOF Then
' Do nothing, the recordset is empty
Else
Do Until rs.EOF
rs.Edit
rs!FirstName = firstName 'This is how you access properties in the recordset
rs.Update
rs.MoveNext
Loop
etc.
I think you need parentheses around your SELECT statement when you pass it to RunSQL, because it's called as a function that returns "something" that gets assigned to FNSQL. So something like this:
FNSQL = DoCmd.RunSQL( _
"SELECT First_Name FROM tblPatient_Info WHERE Patient_ID = '" & 1967 & "';")
In fact, looking at the RunSQL documentation, RunSQL doesn't return anything so, even if you fix your original issue, you'll still find there's an error there.
Furthermore, again according to the documentation, you can only execute SELECT ... INTO statements with RunSQL. For simple SELECT ... FROM statements, simoco is right -- you need to use a Recordset.
2 errors.
First, DoCmd.RunSQL won't do anything with just a select. Maybe you are missing your action (delete or something) or maybe you want to read it, so you should use CurrentDb.OpenRecordset . You'll need to post more code so we can better understand where you are going with this.
Second, if Patient_ID is an integer (and I'm guessing it is), you don't need the '. You don't need the ; either.
So the query should look like this:
varInt = 1967
"SELECT First_Name FROM tblPatient_Info WHERE Patient_ID = " & varInt
If you don't want to use a var for the int, just insert it directly into the string like so:
"SELECT First_Name FROM tblPatient_Info WHERE Patient_ID = 1967"
In case Patient_ID is a string, you will indeed need ', but you are also missing " around the string, so it should look like this:
"SELECT First_Name FROM tblPatient_Info WHERE Patient_ID = '" & "1967" & "'"
Not a correct answer to this question but it's worth noting I just wanted to see if a row existed in the database. I assumed that would require me to write some sort of select or result query. However access has a separate function just to run count operations.
So this code I ended up with:
Dim count As Long
count = DCount("release_id", "list_releases", "list_id = 1 AND release_id = " & Me!release_id)
If count Then
Me!owned.Enabled = False
MsgBox "You own this release."
End If

Invoking a SQL Procedure in the database via VBA code

I have an SQL query which takes up a parameter stored in the db. I want to invoke the same from VBA code which is running on the same db. I am using MS Access for my work.
So for example consider, I have an SQL query 'Q' which takes a parameter 'p', I intend to invoke this SQL query from my VBA code 'C' , which also naturally involves passing this parameter 'p' to the query.
Help much appreciated
Soham
There are a few possibilities here.
Let us say it is a SELECT query built on a form that holds the parameters to be used and that the input is safe:
s = "SELECT * FROM MyTable WHERE AText ='" & Me.MyText & "'"
This can be used like so:
Forms!SomeForm.RecordSource = s
Or
Set qdf = CurrentDb.CreateQueryDef("NewQuery", s)
However, the above can be done in other, better ways.
Let us say it is a ACTION query run from a form that holds the parameters to be used and that the input is safe:
s = "UPDATE MyTable Set AText ='" & Me.MyText & "'"
Then
Set db = CurrentDB
db.Execute s, dbFailOnError
Or you can use a temporary query, which can be safer:
'Temporary query
s = "UPDATE MyTable Set AText = MyRext"
Set qdf = db.CreateQueryDef("", s)
qdf.Parameters!MyText = Me.MyText
qdf.ReturnsRecords = False
qdf.Execute dbFailOnError
Something similar to the above would also be suitable for an existing query.
You can also pass the parameter to a procedure, in which case Me.MyText becomes a variable, or you can use Inputbox, which is rarely a good idea.
After that, there is the whole world of ADO.