SQL Nesting iteration to update one table from another ASP.NET VB - asp.net

I'm looking for a suggestion for the best way to accomplish the following task. I need to run an SQL query that will pull up to 200 rows. With each row, I need to take the data from that row and insert it into another table. At first I thought I would nest database connections like the code example I've listed below. However I was wondering if this was the incorrect way of doing it and what would be a better way.
Any ideas?
Dim dbconn As New SqlConnection
Dim dbconn2 As New SqlConnection
dbconn = New SqlConnection(ConfigurationManager.ConnectionStrings("databaseName").ConnectionString
dbconn2 = New SqlConnection(ConfigurationManager.ConnectionStrings("databaseName").ConnectionString
dbconn.Open()
Dim cmd As New SQLCommand
Dim cmd2 As New SQLCommand
Dim UserReader As SqlDataReader
Dim cmdStringSource As String = "SELECT approx 25 fields WHERE fldRandomField = 26"
Dim cmdStringUpdate As String
cmd = New SQLCommnd(cmdStringSource, dbconn)
UserReader = cmdExecuteReader()
If UserReader.HasRows Then
While UserReader.Read()
cmdStringUpdate = "UPDATE tblUpdate SET a whole bunch of stuff from the cmdStringSource current record"
dbconn2.Open()
cmd2=New SQLCommand(cmdStringUpdate, dbconn2)
cmd2.ExecuteNonQuery()
dbconn2.close()
End While
End If
UserReader.Close()
dbconn.close()

Instead of doing this in the loop, with multiple connections and datareaders - do it entirely in the back end.
SQL Server supports Insert from select e.g.
INSERT INTO Table1 (field1, field2, field3 ...)
SELECT field1, field2, field3 FROM FROM Table2
WHERE <some condition>
Same goes for UPPDATE e.g.
UPDATE Table1
SET Table1.Field1 = Table2.Field1,
Table1.Field2 = Table2.Field2
Table1.Field3 = Table2.Field3
FROM Table1 INNER JOIN Table2
ON Table1.CommonField = Table2.CommonField
WHERE <some condition>
Just build this statement and call it once with a single connection and ExecuteNonQuery command.

Related

Pass datatable as parameter to a stored procedure to perform update

I need pass the whole table to a stored procedure to run a update query.
This is my code:
CREATE TYPE [dbo].[UploadData] AS TABLE
(
[CODE] [varchar](4) NULL,
[SERIALNUMBER] [varchar](20) NULL,
[PRODUCTCODE] [varchar](20) NULL,
[THRESHOLD] [int] NULL
)
This is my stored procedure:
CREATE PROCEDURE sp_UpdateExchangeThreshold
#TableType dbo.UploadData READONLY
UPDATE [dbo].[ExchangeData_20221213]
SET t.Threshold = tbl.THRESHOLD
FROM [dbo].[ExchangeData_20221213] t
INNER JOIN #TableType AS tbl ON t.ProductCode = tbl.PRODUCTCODE
AND t.SerialNumber = tbl.SERIALNUMBER
In my VB.Net code, I have this:
Using sqlCommand As SqlCommand = New SqlCommand()
sqlCommand.Connection = connection
sqlCommand.CommandText = "sp_UpdateExchangeThreshold"
sqlCommand.CommandTimeout = 180
sqlCommand.CommandType = CommandType.StoredProcedure
Dim parameterList As SqlParameter = New SqlParameter("#TableType", SqlDbType.Structured)
parameterList.Value = dataTable
parameterList.Direction = ParameterDirection.Input
sqlCommand.Parameters.Add(parameterList)
sqlCommand.ExecuteNonQuery()
End Using
I'm not able to update the records. Any mistake I made? Please help
Unfortunately, a .NET ADO datatable is NOT a SQL Server table type. They have NOHTING to do with each other. You can use a datatable with Oracle, MySQL, or any server based system. You can even use a access database with a data table.
So, datatable object is platform gnostic (neutral), and thus can't be passed, used as a SQL table type.
However, you don't really have to pass a table back. The REAL question is what do you want to do with this table?
Do you want to edit, or update or insert rows from that table back into the database?
If yes, then use a table adaptor (that's what they are for).
Not only will they do all the dirty work for you, but that table can have updates, deletes, edits and insert/additions.
And with ONE command you can send that WHOLE table + all edit/update/delete/adds in one shot.
Say like this:
Dim rstHotels As New DataTable
Using conn As New SqlConnection(My.Settings.TEST4)
Dim strSQL As String = "SELECT * FROM tblHotelsD"
Using cmdSQL As New SqlCommand(strSQL, conn)
conn.Open()
rstData.Load(cmdSQL.ExecuteReader)
' delete first row
rstData.Rows(0).Delete() ' delete first row
rstData.Rows(2).Item("City") = "Zoo" ' edit/modify the 3rd row
Dim OneNewRow As DataRow = rstData.NewRow
OneNewRow("FirstName") = "First Name"
OneNewRow("LastName") = "last Name"
OneNewRow("HotelName") = "My Hotel Name"
OneNewRow("Active") = True
rstData.Rows.Add(OneNewRow)
' now, lets send edits/delete/inserts back to database.
Dim da As New SqlDataAdapter(cmdSQL)
Dim daU As New SqlCommandBuilder(da)
da.Update(rstData)
End Using
End Using
Now, I have the load the table and the update as per above, but they can be done separately.
Edit: Sending the datatable to SQL Server, but not all rows.
' so, lets "send" to the sql database some data
Dim rstData2 As New DataTable
rstData2.Columns.Add("HotelName", GetType(String))
rstData2.Columns.Add("City") ' FYI -- defualt is string
rstData2.Columns.Add("Active", GetType(Boolean))
' lets create 5 test rows of data
For i = 1 To 5
Dim MyNewRow As DataRow = rstData2.NewRow
MyNewRow("HotelName") = "Hotel #" & i
MyNewRow("City") = "City #" & i
MyNewRow("Active") = True ' you MUST include columnes that require deffault value
rstData2.Rows.Add(MyNewRow)
Next
' now send in one shot to database
Using conn As New SqlConnection(My.Settings.TEST4)
Dim strSQL As String = "SELECT ID, HotelName, City, Active FROM tblHotelsD"
Using cmdSQL As New SqlCommand(strSQL, conn)
conn.Open()
Dim da As New SqlDataAdapter(cmdSQL)
Dim daU As New SqlCommandBuilder(da)
da.Update(rstData2)
End Using
End Using

insert multiple rows with one query using ms access db

I am trying to get multiple rows into a table hence my attempt to get the row number and put it into a for loop, the countC is exactly the same number of rows as the select statement, so the issue is not there
I'm using an oledb connection as my code is in vb asp.net but my database is in ms access 2003
For c As Integer = 1 To countC
Dim cmdstring As String
cmdstring = " INSERT INTO [KN - ProductionMachineAllocation] (BatchNo, ComponentID)
SELECT POH.BatchNo, SSCDD.ComponentID
FROM (
SELECT ROW_NUMBER() OVER (ORDER BY BatchNo ASC) AS rownumber
([KN - ProductionOrderHeader] AS POH
INNER JOIN [FG - End Product Codes] AS EPC
ON POH.ProductID = EPC.ProductID)
INNER JOIN ([KN - ProductionOrderDetails] AS POD
INNER JOIN [FG - Style Size Comp Def Details] AS SSCDD
ON POD.SizeID = SSCDD.SizeID)
ON (POH.BatchNo = POD.BatchNo)
AND (EPC.StyleID = SSCDD.StyleID)
WHERE POH.BatchNo = '" & BatchNo & "'
) AS temptablename
WHERE rownumber IN (" & c & ");"
Dim con As New OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=|DataDirectory|\Shantara Production IT.mdb")
Dim cmd As New OleDbCommand(cmdstring)
cmd.CommandType = CommandType.Text
cmd.Connection = con
cmd.Connection.Open()
cmd.ExecuteNonQuery()
cmd.Connection.Close()
Next
I found out that ms access doesn't support ROW_NUMBER() so I need to find another going through each row since ms access doesn't support multi row insert by insert into select statement such as mine, any suggestions around my problem?
Most databases are able to do all this work much more efficiently entirely in the database. Certainly in SQL Server I could get entire thing down to a single query. Access is a little different, since vbscript is its procedural language, rather than something more like t-sql. There's still probably a way to do it, but since what you have works, we can at least focus on making that better.
GridViews are visual constructs that will use up extra memory and resources. If Access won't do a real INSERT/SELECT, you can at least read direct from the previous result set into your insert. You can also improve on this significantly by using parameters and re-using a single open connection for all the inserts:
Dim cnString As String = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=|DataDirectory|\Shantara Production IT.mdb"
Dim SQLDown As String = _
"SELECT DISTINCT POH.BatchNo, SSCDD.ComponentID
FROM ([KN - ProductionOrderHeader] AS POH
INNER Join [FG - End Product Codes] AS EPC
On POH.ProductID = EPC.ProductID)
INNER Join([KN - ProductionOrderDetails] AS POD
INNER Join [FG - Style Size Comp Def Details] AS SSCDD
On POD.SizeID = SSCDD.SizeID)
On (POH.BatchNo = POD.BatchNo)
And (EPC.StyleID = SSCDD.StyleID)
WHERE POH.BatchNo = ? "
Dim SQLUp As String = _
" INSERT INTO [KN - ProductionMachineAllocation]
(BatchNo, ComponentID)
VALUES( ?, ? )"
Dim dt As New DataTable
Using con As New OleDbConnection(cnString), _
cmd As New OleDbCommand(SQLDown, con)
'Guessing at parameter type/length here.
'Use the actual column type and size from your DB
cmd.Parameters.Add("#BatchNo", OleDbType.VarWChar, 10).Value = BatchNo
con.Open()
dt.Load(cmd.ExecuteReader())
End Using
Using con As New OleDbConnection(cnString), _
cmd As New OleDbCommand(SqlUp, con)
'Guessing at parameter types/lengths again
cmd.Parameters.Add("#BatchNo", OleDbType.VarWChar, 10)
cmd.Parameters.Add("#ComponentID", OleDbType.Integer)
'Connection is managed *outside of the loop*. Only one object created, only one negotiation with DB
con.Open()
For Each row As DataRow In dt.Rows
cmd.Parameters(0).Value = row(0)
cmd.Parameters(1).Value = row(1)
cmd.ExecuteNonQuery()
Next
End Using
Normally, with any ADO.Net provider you do not re-use your connection or command objects. You want a new connection object for every query sent to the DB to allow connection pooling to work correctly. Using the connection in a tight loop like this for the same query is one of the few exceptions.
I might be able to improve further by sticking with the active DataReader, rather than first loading it into a DataTable. That would allow us to avoid loading the entire result set into memory. You would only ever need one record in memory at a time. Certainly this would work for Sql Server. However, Access was designed mainly as a single-user database. It doesn't really like multiple active connections at once, and I'm not sure how it would respond.
It would also be nice to be able to do all of this work in a transactional way, where there's never any risk of it failing part way through the loop and getting stuck with half the updates. Sql Server would handle this via a single INSERT/SELECT query or with an explicit transaction. But, again, this isn't the kind of the Access is designed for. It probably does have a way to do this, but I'm not familiar with it.
OK SO I finally found a way around it, it's a bit of a long process but basically I loaded the SELECT statement(with multiple rows) into a gridview table and the used a for loop to insert it into my insert into statement. bellow is my code:
Displaying into a table
Dim Adapter As New OleDbDataAdapter
Dim Data As New DataTable
Dim SQL As String
Dim con As New OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=|DataDirectory|\Shantara Production IT.mdb")
Dim cmd As New OleDbCommand()
grdvmachincomp.Visible = false
SQL = "SELECT DISTINCT POH.BatchNo, SSCDD.ComponentID
FROM ([KN - ProductionOrderHeader] AS POH
INNER Join [FG - End Product Codes] AS EPC
On POH.ProductID = EPC.ProductID)
INNER Join([KN - ProductionOrderDetails] AS POD
INNER Join [FG - Style Size Comp Def Details] AS SSCDD
On POD.SizeID = SSCDD.SizeID)
On (POH.BatchNo = POD.BatchNo)
And (EPC.StyleID = SSCDD.StyleID)
WHERE POH.BatchNo = '" & BatchNo & "'"
con.Open()
cmd.Connection = con
cmd.CommandText = SQL
Adapter.SelectCommand = cmd
Adapter.Fill(Data)
grdvmachincomp.DataSource = Data
grdvmachincomp.DataBind()
cmd.Connection.Close()
Insert into through for loop
For c As Integer = 0 To grdvmachincomp.Rows.Count - 1
Dim cmdstring As String
cmdstring = " INSERT INTO [KN - ProductionMachineAllocation] (BatchNo, ComponentID) VALUES('" & grdvmachincomp.Rows(c).Cells(0).Text & "', " & grdvmachincomp.Rows(c).Cells(1).Text & ");"
Dim con As New OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=|DataDirectory|\Shantara Production IT.mdb")
Dim cmd As New OleDbCommand(cmdstring)
cmd.CommandType = CommandType.Text
cmd.Connection = con
cmd.Connection.Open()
cmd.ExecuteNonQuery()
cmd.Connection.Close()
Next

Return Identity Value From Table After Inserting Row

I am trying to store the value of an identity value from table after inserting it into a row.
I want to store the value in currentID so I can store the value in another table which could be added more than once.
Any help or advice would be great!
Dim currentID As Integer
Dim cmd As New System.Data.SqlClient.SqlCommand
cmd.CommandType = System.Data.CommandType.Text
cmd.CommandText = "INSERT INTO table (Databasevalue) VALUES ('" + formvalue + "');"
cmd.Connection = sqlconninsertDB
sqlconninsertDB.Open()
cmd.ExecuteNonQuery()
**currentID = [get and store value]**
sqlconninsertDB.Close()
You can try adding this SELECT to your command:
cmd.CommandText = "INSERT INTO table (Databasevalue) VALUES ('" + formvalue + "'); SELECT SCOPE_IDENTITY()"
And execute query like this:
Dim currentID as Integer = cmd.ExecuteScalar()
Instead of .ExecuteNonQuery()
Side note: Try not to use string concatenations when constructing inline-commands. Rather look into parametrized queries.
With Sql Server you could pass two sql commands separating them with a semicolon.
The second command is a SELECT SCOPE_IDENTITY() and this returns the last ID inserted in this scope.
You could execute both commands and retrieve the result with ExecuteScalar
Please, take note to use a parameterized query instead of a string concatenation to avoid Sql Injection attacks and parsing problems.
Dim currentID As Integer
Dim cmd As New System.Data.SqlClient.SqlCommand
cmd.CommandType = System.Data.CommandType.Text
cmd.CommandText = "INSERT INTO table (Databasevalue) VALUES (#formval); " +
"SELECT SCOPE_IDENTITY()"
cmd.Connection = sqlconninsertDB
cmd.Parameters.AddWithValue("#formval", formValue)
sqlconninsertDB.Open()
currentid = cmd.ExecuteScalar()
sqlconninsertDB.Close()

SQL - INSERT with Scope_Identity() - getting the record id

I have an ASP.NET page written in VB.NET that gets the items into a GridView by using a SELECT statement with INNER JOIN and also allows you to add an item to the invoice.
INNER JOIN that gets data from items and project_items.
SELECT Items.item_id, Items.item_name, Items.item_cost, project_items.item_quantity
FROM Items
INNER JOIN project_items
ON items.item_id = project_items.item_id
WHERE project_items.project_id = #parameter
#parameter is Session("ProjectID")
(There is a foreign key project_items.item_id -> items.item_id.)
I have an trying to use an SQL statement in VB.NET to try and INSERT into two tables simultaneously. What I tried is I tried to get the item_id of the last record created and insert into another table (project_items) by using that data. However, data is only being entered into the first table.
Any idea what I can do?
This is the code:
Protected Sub btnAddItem_Click(sender As Object, e As EventArgs) Handles btnAddItem.Click
Dim conn As New SqlConnection("Data Source=BRIAN-PC\SQLEXPRESS;Initial Catalog=master_db;Integrated Security=True")
Dim addItemComm As String = "SELECT item_id FROM project_items WHERE project_id=#ProjectID"
Dim user_id_select As New Integer
Dim addItemSQL As New SqlCommand
conn.Open()
addItemSQL = New SqlCommand(addItemComm, conn)
addItemSQL.Parameters.AddWithValue("#ProjectID", Convert.ToInt32(Session("ProjectID")))
Dim datareader As SqlDataReader = addItemSQL.ExecuteReader()
datareader.Close()
conn.Close()
Dim AddNewItemComm As String = "INSERT INTO Items (item_name, item_cost, item_code) VALUES (#ItemName, #ItemCost, #ItemCode); SELECT SCOPE_IDENTITY()"
Dim AddNewItem2Comm As String = "INSERT INTO project_items (item_id, project_id, item_quantity) VALUES (#ItemID, #ProjectID, #ItemQuantity) "
Dim AddNewItemSQL As New SqlCommand
conn.Open()
AddNewItemSQL = New SqlCommand(AddNewItemComm, conn)
AddNewItemSQL.Parameters.AddWithValue("#ItemName", txtItemName.Text.Trim)
AddNewItemSQL.Parameters.AddWithValue("#ItemCost", Convert.ToInt32(txtItemCost.Text))
AddNewItemSQL.Parameters.AddWithValue("#ItemCode", txtItemCost.Text.ToString.ToUpper)
Dim ItemId As Integer
ItemId = AddNewItemSQL.ExecuteScalar()
AddNewItemSQL.ExecuteNonQuery()
conn.Close()
conn.Open()
AddNewItemSQL = New SqlCommand(AddNewItem2Comm, conn)
AddNewItemSQL.Parameters.AddWithValue("#ItemID", ItemId)
AddNewItemSQL.Parameters.AddWithValue("#ProjectID", Convert.ToInt32(Session("ProjectID")))
AddNewItemSQL.Parameters.AddWithValue("#ItemQuantity", Convert.ToInt32(txtItemQuantity.Text))
AddNewItemSQL.ExecuteNonQuery()
conn.Close()
End Sub
Why are you doing this in multiple statements in the first place? Why not:
INSERT dbo.Items (item_name, item_cost, item_code)
OUTPUT inserted.ItemID, #ProjectID, #ItemQuantity
INTO dbo.project_items(item_id, project_id, item_quantity)
VALUES (#ItemName, #ItemCost, #ItemCode);
Now you only have to call one ExecuteNonQuery() and your app doesn't have to care about the actually SCOPE_IDENTITY() value generated. (You can still retrieve SCOPE_IDENTITY() if you want, of course, using ExecuteScalar - but as Nenad rightly points out, pick one instead of calling both.)
Since we now know that there is an explicit foreign key here, we can still reduce your C# code to one call even if we can't use the OUTPUT clause.
DECLARE #i INT;
INSERT dbo.Items (item_name, item_cost, item_code)
SELECT #ItemName, #ItemCost, #ItemCode;
SELECT #i = SCOPE_IDENTITY();
INSERT dbo.project_items(item_id, project_id, item_quantity)
SELECT #i, #ProjectID, #ItemQuantity
SELECT #i; -- if necessary
Would be even cleaner to put this into a stored procedure.
ItemId = AddNewItemSQL.ExecuteScalar()
AddNewItemSQL.ExecuteNonQuery()
These two rows next to each other will execute the command twice. You should remove the second one - ExecuteNonQuery. This will have your data inserted twice in the Items - two same rows but with different IDs.
Since you only retrieve ItemID from the first row, that one should be inserted in project_items, but the other one that was last inserted in items will have no matching row.
Also - complete section from beginning of button click method up before Dim AddNewItemComm As String - where you open and close DataReader and do nothing with it seems completely unnecessary.

how to insert an integer value from a Grid to Sql table?

I have a AdvWebGrid where the 7th column is DynEdit where user will enter the value. Now I have to take the entered value and insert it into the SQL table.
For example I have 7 records in the grid, the user will enter some comments for the first three records and save. Now I want to insert/ update the first three comments in the table.
If you are able to get the info in the 7th column you could use a datable with the rows you need, the use a sqldataadapter to fill the info into the sql server, i feel this is the best way to do it.
Other way is create a stored procedure in your SQL Server then invoque it from your .net program using SqlCommand and SqlDataReader...
Here is an example:
Dim val as integer = value you want to insert
Dim comi As New SqlCommand
Dim dr As SqlDataReader
Dim _con as sqlconnection
_con.ConnectionString = _strcon ' connection string
comi.CommandType = CommandType.StoredProcedure
comi.CommandText = sp_name ' your stored procedure is sp_name this inserts a value into the table x
comi.Connection = _con
comi.Parameters.AddWithValue("val",val)
dr = comi.ExecuteReader
dr.Close()
This should do the trick ...
Greetings !

Resources