if exists, update, else insert new record - sql

i am inserting values into a table
if the record exists already replace it, and if it does not exist then add a new one.
so far i have this code:
INSERT INTO table_name
VALUES (value1, value2, value3,...) where pk="some_id";
but i need something like this
if not pk="some_id" exists then INSERT INTO table_name
VALUES (value1, value2, value3,...) where pk="some_id"; else update table_name where pk="some_id"
what would be the correct SQL syntax for this?
please note that i am using sql access and that i guess it can be a combination of vba and sql

First update rows which match between your import table and master table.
UPDATE table_name AS m
INNER JOIN tblImport AS i
ON m.pk = i.pk
SET
m.field2 = i.field2,
m.field3 = i.field3,
m.field4 = i.field4;
Then add any imported records which don't exist in the master table.
INSERT INTO table_name (
pk,
field2,
field3,
field4)
SELECT
i.pk,
i.field2,
i.field3,
i.field4
FROM
tblImport AS i
LEFT JOIN table_name AS m
ON i.pk = m.pk
WHERE
(((m.pk) Is Null));

This could be done easily with recordsets. The code would then look like that (for an ADODB recordset):
myRecordset.find ....
if myRecordset.EOF then
myRecordset.addNew
endif
....
myRecordset.fields(...) = ...
....
myRecordset.update

I have posted about my approach to this problem many, many times in many different forums, but I'll just recapitulate the basic structure of the approach I use. There is no way to do it in one step, though.
update the existing records from the external data source.
insert records that don't already exist.
This assumes a common primary key that can be used to link the existing table with the external data source.
Task #2 is pretty trivial, just an outer join for the records that don't already exist.
One can use brute force for #1, writing an UPDATE statement with a SET for each field other than the primary key, but I consider that to be messy and unnecessary. Also, since I have a lot of replicated applications, I can't do that, as it would result in false conflicts (when a field is updated to the same value as it started with).
So, for that purpose, I use DAO and write an on-the-fly SQL statement to update COLUMN-BY-COLUMN. The basic structure is something like this:
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim fld As DAO.Field
Dim strField As String
Dim strSet As String
Dim strWhere As String
Dim strSQL As String
Set db = CurrentDB
Set rs = db.OpenRecordset("DestinationTable")
For Each fld in rs.Fields
strField = fld.Name
If strField <> "PKField" Then
strSet = "DestinationTable." & strField & " = ExternalTable." & strField
strWhere = "Nz(DestinationTable." & strField & ",'') = Nz(ExternalTable." & strField & ", '')"
strSQL = "UPDATE DestinationTable "
strSQL = strSQL & " SET " & strSet
strSQL = strSQL & " WHERE " & strWhere
db.Execute strSQL, dbFailOnError
Debug.Print strField & ": " & db.RecordsAffected
End If
Next fld
Now, the complicated part is handling numeric vs. date vs. string fields, so you have to have some logic to write the WHERE clauses to use proper quotes and other delimiters according to the field type. Rather than test the field type, I generally just use a CASE SELECT like this, making string fields the default:
Dim strValueIfNull As String
Select Case strField
Case "DateField1", "DateField2", "NumericField2", "NumericField2", "NumericField3"
strValueIfNull = "0"
Case Else
strValueIfNull = "''"
strWhere = "Nz(DestinationTable." & strField & ", '') = Nz(ExternalTable." & strField & ", '')"
End Select
strWhere = "Nz(DestinationTable." & strField & ", " & strValueIfNull & ") = Nz(ExternalTable." & strField & ", " & strValueIfNull & ")"
I could have the details there wrong, but you get the idea, I think.
This means you'll run only as many SQL updates as there are updatable fields, and that you'll only update records that need updating. If you're also stamping your records with a "last updated" date, you'd do that in the UPDATE SQL and you'd only want to do that on the records that really had different values.

Related

Access: Combine 2 Fields in 1 with SQL Code

I'm trying to combine two field in one in my table. This shall be excecuted by clicking on a button. I thought it could work like this:
Private Sub Button_Click()
Dim strSQL As String
strSQL = "SELECT Table.Field1 & "" & Table.Field2 AS Table.Field12"
CurrentDb.Execute strSQL
End Sub
Clearly this isn't working at all.
Any ideas how to solve the problem?
You need the UPDATE statement, since you want to change values in your table:
UPDATE Table SET Field12 = Field1 & Field2;
If you wish to update, you must use an action query, like:
strSQL = "Update Table Set Field12 = Field1 & Field2"
If you wish to select, you must a valid select query:
strSQL = "Select *, Field1 & Field2 AS Field12 From Table"
but that you cannot "execute". However, you can open it as recordset.
Hmmm, you can't have periods in a column alias unless you escape them. Do either of these work?
strSQL = "SELECT Table.Field1 & "" & Table.Field2 AS Field12"
strSQL = "SELECT Table.Field1 & "" & Table.Field2 AS [Table.Field12]"

Access SQL - Insert records with null fields?

I have Insert statement that inserts records from one table to another. SQL works, but not If field in WHERE clause is Null. No errors, just nothing gets inserted. How can I fix this ? This is what I have (fields are named same in both tables - Me.[Serial_No] represents bound field - textbox :
dim SQL as String
SQL = "INSERT INTO Table1 (Serial_No,Name,Description)" & _
"SELECT Table2.Serial_No, Table2.Name, Table2.Description" & _
" FROM Table2" & _
" WHERE Table2.Serial_No='" & Me.[Serial_No] & "'"
DoCmd.RunSQL SQL
I am not sure if this is the answer you require, it doesn't appear very logical.
If name and description are never null values then ensure that they are a unique composite key in your table. You can create these unique keys in the index button in table design view. Then you could look up the serial_no using the other field values. Seems a bit long winded to me but should give you the record you require.
dim SQL as String
dim varSerialNo as string
varSerialNo = dlookup("Serial_No", "table2", "Name='" & me.Name & "' AND Description='" & me.description & "'")
SQL = "INSERT INTO Table1 (Serial_No,Name,Description)" & _
"SELECT Table2.Serial_No, Table2.Name, Table2.Description" & _
" FROM Table2" & _
" WHERE Table2.Serial_No='" & varSerialNo & "'"
DoCmd.RunSQL SQL

docmd.runsql ("delete * from &strValue&")

The below code reads through the names of selected queries in the combo box. Then it finds the table that corresponds to that query and has to delete data from it, in order to run Insert into select query. I had Select into query, but this would delete the tables each time. Can you please help with the syntax in the docmd.Runsql ("delete * from .....) line? I need to empty the table before repopulating it with data and was therefore thinking of passing a string value with table name in the sql command. Thank you for help!
For Each valSelect In Me.Combo29.ItemsSelected
DoCmd.SetWarnings (WarningsOff)
strValue = Me.Combo29.ItemData(valSelect)
strValue3 = DLookup("TableName", "[List of Queries]", "QueryName = '" & strValue & "'")
DoCmd.RunSQL ("delete * from '& strValue3 &'")
DoCmd.OpenQuery (strValue)
Me.Combo29.Selected(valSelect) = False
Just remove the asterisk from your DELETE statement. Also, your double quotes don't look right. Might want to add some square brackets as well in case your table names have spaces in them:
DoCmd.RunSQL "delete from [" & strValue3 & "]"

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.)

How to retain the AutoNumber of a Primary Key when executing a query in MS Access?

I am trying to do something like the following in a query:
Dim rs As RecordSet
Dim NewPrimaryKey as Long
Set rs = Currentdb.OpenRecordset("SELECT * FROM MyTable WHERE MyPrimaryKey Is Null;")
With rs
.AddNew
NewPrimaryKey = !MyPrimaryKey
!DateValue = Now()
...
.Update
End With
Any pointers on how to do t his using a query that I can execute in MS Access 2003 using the JET engine would be greatly appreciated.
You can use two SQL statements to accomplish what I think you want. First an INSERT. Then "SELECT ##Identity" to get the last added autonumber value. Use an object variable for the database connection with both SQL statements.
Dim db As DAO.Database
Dim NewPrimaryKey As Long
Dim strInsert As String
strInsert = "INSERT INTO MyTable ([DateValue])" & vbCrLf & _
"VALUES (Now());"
Set db = CurrentDb
db.Execute strInsert, dbFailOnError
NewPrimaryKey = db.OpenRecordset("SELECT ##Identity")(0)
Debug.Print NewPrimaryKey
Set db = Nothing
I enclosed the field name DateValue in square brackets because it is a reserved word.
Edit: If you insert multiple records with one SQL statement, SELECT ##Identity will still give you the last autonumber. It's the last autonumber for inserts performed through that connection instance. And you don't get a sequence of the autonumbers used; only the last one.
strInsert = "INSERT INTO MyTable3 ([some_text])" & vbCrLf & _
"SELECT TOP 3 foo_text FROM tblFoo" & vbCrLf & _
"WHERE foo_text Is Not Null ORDER BY foo_text;"