What's the best (simple) way to UPDATE or INSERT a SQL record in VB.NET - sql

I've been fumbling my way though writing my first application with SQL database access and I've been getting along ok with single commands using system.data.sqlclient.sqlcommand and doing something like:
SQLCmd.CommandText = ("DELETE from ContactRelationships WHERE ID = 'someid'")
SQLCmd.ExecuteNonQuery()
Or to store changes on a form and then save or cancel them:
Dim x As New SqlCommand("DELETE from SharedDataLocations where Location = #Loc and UserID= #UID ;--", cnn)
x.Parameters.Add("#Loc", SqlDbType.NVarChar, 300).Value = strRemDLoc
x.Parameters.Add("#UID", SqlDbType.Int).Value = UserID
PendingSQLChanges.Add(x)
'followed later by
For x = 0 To PendingSQLChanges.Count - 1
PendingSQLChanges(x).ExecuteNonQuery()
Next
PendingSQLChanges.Clear()
I haven't tackled anything more complex than that yet but I'm willing to learn. What I need to do now is take the id for the current STAFF record and see if it is already set in the STAFFMANAGERS relationship table and either update it with the just selected id from the MANAGERS table or create the record if a manager wasn't previously set. Both staff and manager ids are stored in form variables at this point so can just be inserted into any SQL commands.
I've seen various ways I could do this such as adding multiple lines to a SQLCmd.commandtext (although I'm not sure on format for this) but I have no idea if this is a foolproof solution or prone to problems or if it will even work.
Without stretching too far out of my current experience (or giving me an in depth explanation of something more complex) how can I best accomplish this?

I am not sure if this will help but, I have something similar in my VB.NET SQL code. What I did is use a IF THEN statement to check if the record exists. If there is no record then it will INSERT it with a new UID, but if the UID already exists it will UPDATE.
Public Class YourForm
Private UniqueID as Integer
Private Sub
If AddRecord Then
SQLCmd.CommandText = "INSERT into YourTableName (YourColumnName1,YourColumnName2) values (" & _
"'" & YourTextBox.Text & "')")
Else
RunSql ("Update YourTableName set YourColumnName='" & YourTextBox.Text & "')" & _
" where YourUID=" & UniqueID)
End Sub
End Class

Maybe you need the MERGE statement.

Related

Is it possible to make a dynamic sql statement based on combobox.value in access?

I made a form in access with 2 different comboboxes. The user of
This tool can choose in combobox1: the table (which has to be filtered) and the second combobox2 is the criteria to be filtered( for example Language= “EN”) and the output of this query has to be located in tablex.
The problen what I have is that i cant find a solution for passing the value of the combobox1 to the sql statement. The second one is just like: where [language] = forms!form!combo2.value, but the part where i cant find a solution for is: select * from (combobox1 value)? How can i pass the combobox value as table name to be filtered? Can anyone please help me?
You can't have the table name in the WHERE clause of your query (there might be a hacky way to do it, but it should be discouraged at any case).
If you want to select data from 1 of a number of tables, your best bet is to generate SQL dynamically using VBA. One way to do this (especially if you want/need your query to open in Datasheet View for the end user) is to have a "dummy" query whose SQL you can populate using the form selections.
For example, let's say we have 2 tables: tTable1 and tTable2. Both of these tables have a single column named Language. You want the user to select data from either the first or second table, with an optional filter.
Create a form with 2 combo boxes: One for the tables, and one with the criteria selections. It sounds like you've already done this step.
Have a button on this form that opens the query. The code for this button's press event should look something like this:
Private Sub cmdRunQuery_Click()
Dim sSQL As String
Dim db As DAO.Database
Dim qdf As DAO.QueryDef
If Not IsNull(Me.cboTableName.Value) Then
sSQL = "SELECT * FROM " & Me.cboTableName.Value
If Not IsNull(Me.cboFilter.Value) Then
sSQL = sSQL & vbNewLine & _
"WHERE Language=""" & Me.cboFilter & """"
End If
Set db = CurrentDb
Set qdf = db.QueryDefs("qDummyQuery")
qdf.SQL = sSQL
DoCmd.OpenQuery qdf.Name
End If
End Sub
Note how the SQL is being generated. Of course, using this method, you need to protect yourself from SQL injection: You should only allow predefined values in the combo box. But this serves as a proof of concept.
If you don't need to show the query results, you don't need to use the dummy query: You could just open a recordset based on the SQL and process that.
If you run the code in the afterupdate event of the combobox you can set an SQL statement like this:
Private Sub combobox2_AfterUpdate()
someGlobalVar = "Select * FROM " & me.combobox1.value & " WHERE language = " & _
me.combobox2.value
End Sub
And then call the global with the SQL string wherever you need it.

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 do I access multiple records from the same table using SQLDataAdapter?

This almost works. I get an error at the last line that looks like it's complaining about the C1 reference. Is there a simple way around this? There is nothing wrong with the query or connection.
Dim CmdString As String
Dim con As New SqlConnection
Try
con.ConnectionString = PubConn
CmdString = "select * from " & PubDB & ".dbo.Suppliers as S " & _
" join " & PubDB & ".dbo.Address as A" & _
" on S.Supplier_Address_Code = A.Address_IDX" & _
" join " & PubDB & ".dbo.Contacts as C1" & _
" on S.Supplier_Contact1 = C1.Contact_IDX" &
" join " & PubDB & ".dbo.Contacts as C2" & _
" on S.Supplier_Contact2 = C2.Contact_IDX" &
" WHERE S.Supplier_IDX = " & LookupIDX
Dim cmd As New SqlCommand(CmdString)
cmd.Connection = con
con.Open()
Dim DAdapt As New SqlClient.SqlDataAdapter(cmd)
Dim Dset As New DataSet
DAdapt.Fill(Dset)
con.Close()
With Dset.Tables(0).Rows(0)
txtAddress1.Text = .Item("Address1").ToString
txtAddress2.Text = .Item("Address2").ToString
txtSupplierName.Text = .Item("Address_Title").ToString
txtAttn.Text = .Item("Attn").ToString
txtBusinessPhone1.Text = .Item("C1.Contact_Business_Phone").ToString
You would not include the "C1" table alias as part of your column name. It will be returned from your query as Contact_Business_Phone.
For accessing multiple rows you could use the indexer as you are in the example above "Rows(0)" by placing your With block into a For loop and accessing the "Rows(i)" with your loop variable. However, this would not help much as your are assigning this to individual text boxes, so you'd only see the last value on your page/screen.
The alias C1 is used by SQL Server and is not persisted to the result set. Have you taken this query into SQL Management Studio to see the results?
Since you requested all columns (*) and joined to the Contacts table twice, you'll end up with duplicate column names in the result. For example, if the Contacts table has a LastName field, you'll end up with TWO LastName columns in your result.
I haven't tried to duplicate this in my local environment, but I can't imagine the data adapter is going to like having duplicate column names.
I recommend specifically including the columns you want to return instead of using the *. That's where you'll use the alias of C1, then you can rename the duplicate columns using the AS keyword:
SELECT C1.LastName AS [Supplier1_LastName],
C2.LastName AS [Supplier2_LastName],
...
This should solve your problem.
Good Luck!
You should only be pulling back the columns that you're in fact interested in, as opposed to *. It's sort of hard to tell exactly what data exists in which tables since you're pulling the full set, but at a quick guess, you'll want in your select statement to pull back A.Address1, A.Address2, A.AddressTitle, ?.Attn (not sure which table this actually derives from) and C1.Contact_Business_Phone. Unless you actually NEED the other fields, you're much better off specifying the individual fields in your query, besides having the possible duplicate field issue that you're running into here, it can also be a significant performance hit pulling everything in. After you clean up the query and only pull in the results you want, you can safely just reference them the way you are for the other fields, without needing a table alias (which as others have pointed out, isn't persisted to the result set anyways).

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

Update table while reading

I'm writing a piece of code (VB.NET) to cleanse a (quite big) table of data.
I am connecting to my SQL database, looping through the table, cleansing the data and adding the cleansed data in a different column.
As i'm currently doing an update to my database for each record in the same loop as where i am cleansing the data, i am wondering if there is a more efficient way of doing this, where i would cleanse the data and afterwards send all the updated records to the database in one go.
Simplified code:
'Connect
SQLConn.ConnectionString = strConnection
SQLConn.Open()
SQLCmd.Connection = SQLConn
SQLConn2.ConnectionString = strConnection
SQLConn2.Open()
SQLCmd2.Connection = SQLConn2
'Set query
strSQL = "SELECT Column1 FROM Table1"
SQLCmd.CommandText = strSQL
'Load Query
SQLdr = SQLCmd.ExecuteReader
'Start Cleansing
While SQLdr.Read
Cleansing()
'Add to database
strSQL2 = "UPDATE Table1 SET Clean_data = '" & strClean & "' WHERE Dirty_Data = '" & SQLdr(0).ToString & "'"
SQLCmd2.CommandText = strSQL2
SQLCmd2.ExecuteNonQuery()
End While
'Close Connections
SQLdr.Close()
SQLConn.Close()
SQLConn2.Close()
I'm guessing (from searching for a solution) that it is possible to do the update outside of my loop, but i can't seem to find how to do it specifically.
Many thanks!
Your code is taking a long time because the update is doing a full table scan for every record. You can speed it up by adding an index on the column "Dirty Data".
Essentially, you are reading the data in the select statement. Cleaning one row, and then updating it. The preferred "set-based" approach is more like:
Ideally, you would like to do:
update table1
set column1 = <fix the dirty data>
where column1 <is dirty>
And you have some options in SQL, in terms of replace() and case and like (for instance) that can help with this process.
But you already have the cleaning code external to the database. For this, you want to create and open a cursor, process the record, and then write back. Cursors are relatively slow, compared to in-database operations. But, this is exactly the situation they were designed for -- external code to be applied to individual records.