Multiple table updates - avoiding multiple WHERE clauses - c#

I looked around for a similar question (I'm sure there is one somewhere) but could not find one.
I have a list of IDs that for each of the IDs I need to update another column of that IDs row to the same string.
Essentially, I want something like this:
List<int> uniqueIDs;
UPDATE my_table
SET certainColumn = "foo bar"
WHERE uniqueID = uniqueIDs[0]
OR uniqueID = uniqueIDs[1]
...
OR uniqueID = uniqueID[uniqueIDs.Length-1]
I know this could be achieved by surrounding this in a for/foreach-loop, but I was wondering if there is a better way to get this done, possibly in one database connection?
Any help is greatly appreciated.

Well, you could use a TVP. First, create this on your server:
CREATE TYPE dbo.UniqueIDs AS TABLE(ID INT PRIMARY KEY);
Then a stored procedure:
CREATE PROCEDURE dbo.UpdateByID
#tvp dbo.UniqueIDs READONLY
AS
BEGIN
SET NOCOUNT ON;
UPDATE t SET certainColumn = 'foo bar'
FROM dbo.my_table AS t
INNER JOIN #tvp AS tvp
ON t.uniqueID = tvp.ID;
END
Or:
CREATE PROCEDURE dbo.UpdateByID
#tvp dbo.UniqueIDs READONLY
AS
BEGIN
SET NOCOUNT ON;
UPDATE t SET certainColumn = 'foo bar'
FROM dbo.my_table AS t
WHERE EXISTS (SELECT 1 FROM #tvp
WHERE ID = t.uniqueID);
END
Then instead of a List use a DataTable to hold your IDs in your C# application, and call the stored procedure, passing #tvp as a Structured parameter. I have simple examples of the C# side posted all over this site:
How to pass an array into a SQL Server stored procedure
Insert entire DataTable into database at once instead of row by row?
Parameters to the EXISTS clause in a stored procedure

var query = "UPDATE my_table
SET certainColumn = 'foo bar'
WHERE uniqueID in (" + String.Join(",", uniqueIDs) + ")"

I guess you could do something like this. Get your List of Ids in some TempTable or table variable and use IN operator in your update statement. something like this .....
UPDATE my_table
SET certainColumn = 'foo bar'
WHERE uniqueID IN (SELECT uniqueID
FROM #List_Table)

Related

Use only one petition to server instead multiple

I have a list of CheckBoxes:
List<CheckBox> checkBoxes = new List<CheckBox>();
I want to update it via a stored procedure, so I have:
private void btnSave_Click(object sender, EventArgs e)
{
SQLConnMgr db = new SQLConnMgr();
foreach (var c in checkBoxes)
{
db.ExeSQL($"exec test #CheckBoxName = {c.Name}, #CheckBoxValue = {c.Checked} ");
}
}
Stored procedure:
CREATE OR ALTER PROCEDURE test
-- Add the parameters for the stored procedure here
#CheckBoxName VARCHAR(255),
#CheckBoxValue BIT
AS
BEGIN
SET NOCOUNT ON;
-- Insert statements for procedure here
UPDATE MyTable SET #CheckBoxName = #CheckBoxValue
END
My question is: is there another way to do this? Like sending multiple petitions in the foreach statement instead of only one at a time?
UPDATE
So to be more clear every bool is a column so I need something like:
DECLARE #CurrentCheckboxName VARCHAR(255) = (SELECT
[CheckBoxName]
FROM #CheckBoxList)
UPDATE [m]
SET
#CurrentCheckboxName = [c].[CheckBoxValue]
FROM [RedMarkItems] [m]
JOIN #CheckBoxList [c] ON [c].[CheckBoxName] = #CurrentCheckboxName
but how can iterate on each checkboxName in my DECLARE?
Depending on the version of SQL Server you are using, you could use a TABLE parameter type for your stored proc and call it only once.
CREATE TYPE dbo.MyCheckBoxValues AS TABLE(
CheckBoxName VARCHAR(255) NOT NULL,
CheckBoxValue BIT NOT NULL )
Then you modify your stored proc to use the type.
CREATE OR ALTER PROCEDURE test
-- Add the parameters for the stored procedure here
#CheckBoxList MyCheckBoxValues READONLY
AS
BEGIN
SET NOCOUNT ON;
-- Insert statements for procedure here
UPDATE m SET CheckBoxValue=c.CheckBoxValue
FROM MyTable m
JOIN #CheckBoxList c ON c.CheckBoxName=m.CheckBoxName
END
You can also use Dynamic SQL in your stored proc. For Each checkboxValues :
DECLARE #Query nvarchar(max);
SET #Query = 'UPDATE Table SET ' + #CheckboxName + '='+ #CheckBoxValue;
exec sp_executeSql #Query
Then you only have to get the values in your code.
Something like this should do it.
StringBuilder builder = new StringBuilder();
builder.Append("DECLARE #MyCheckboxes MyCheckBoxValues; ");
foreach (Guid id in _equipmentToMerge)
{
builder.Append(String.Format("INSERT INTO #MyCheckboxes (CheckBoxName, CheckBoxValue) VALUES ('{0}',{1}); ", name, ischecked));
}
builder.Append("exec dbo.test #MyCheckboxes ");
I think there is a problem with your Update statement in your stored procedure. Could you change it thusly?
Update MyCheckBoxValues SET CheckBoxValue = #CheckBoxValue Where CheckBoxName = #CheckBoxName
I don't know why you want to complicate things. Use a single connection for the loop and I can't imagine that you could have a prohibitive number of check boxes on your form. If it is still too slow, get rid of entity framework or whatever orm you are using and try dealing with the server directly.

sql create string variable with column names

i'm trying to create a dynamic sql statement either thru sql code or c# code. For example, I'd like to generate a result set based on a string containing a table name. Currently, I am doing this thru c# code.
My current issue is I'd like to generate a search similar to following
select * from customers
where ContactName+City like '%Berlin%'
so I'm thinking given a table name as a string parameter I need to somehow produce a string variable 'ContactName+City+etc' to build part of the search
I'm open to any other ideas as well.
var sql = string.Format(#"
select * from {0}
where {1} like '%criteria%'"
, variable_table
, "column1+column2+columnX"); //need function here to produce this string based on variable table?
Basically, how would I create a string that concatenates a variable number of columns together ('ContactName+City+etc') based on a variable_table?
Why not simply this:
select * from variable_table_name
WHERE column1+column2+columnX like '%criteria%'
You can do this purely in SQL as well. But as you have already done this in C# and you need only to get the list of columns based on the table name, try this.
Create a SQL udf as below.
CREATE FUNCTION funcReturnAllColumns
(
#tableName VARCHAR(50)
)
RETURNS VARCHAR(500)
BEGIN
DECLARE #ID INT
DECLARE #ALLColumns VARCHAR(500)
SET #ALLColumns = ''
SELECT #ID = id
FROM sys.sysobjects
WHERE name = #tableName
SELECT #ALLColumns = #ALLColumns + '+' + name
FROM sys.syscolumns
WHERE id = #ID
RETURN SUBSTRING(#ALLColumns,2,LEN(#ALLColumns))
END
SELECT dbo.funcReturnAllColumns('table_name')
OUTPUT:
Column1 + Column2 + ..... + ColumN
You may have to adjust varchar limits, validations as required.

Merge tables(add, update and delete records from different sources)

I have three tables tb1,tb2 and tbTotal. They have the same schemas. The tables have three columns, MetricID, Descr and EntryDE.
What I want is to merge tb1 with tbTotal. I have done this and it works fines.
My stored procedure is:
CREATE PROCEDURE [dbo].[Admin_Fill]
-- Add the parameters for the stored procedure here
#MetricId INT,
#Descr VARCHAR(100),
#EntryDE VARCHAR(20)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
--SET IDENTITY_INSERT dbo.tbTotal ON
-- Insert statements for procedure here
;WITH cte AS (SELECT MetricId=#MetricId,Descr=#Descr,EntryDE=#EntryDE)
MERGE tbTotal d
USING cte s
ON s.EntryDE = d.EntryDE
AND s.MetricId=d.MetricId
WHEN matched THEN UPDATE
set MetricId=s.MetricId,
Descr=s.Descr,
EntryDE=s.EntryDE
WHEN not matched BY TARGET THEN
INSERT(MetricId,Descr,EntryDE)
VALUES (s.MetricId,s.Descr,s.EntryDE);
END
My C# code:
foreach (DataRow row in dt.Rows) // pass datatable dt1
{
MetricId = Convert.ToInt32(row["MetricId"]);
Descr = row["Descr"].ToString();
EntryDE = row["EntryDE"].ToString();
parameters.Add("#MetricId", MetricId);
parameters.Add("#Descr", Descr);
parameters.Add("#EntryDE", EntryDE);
dbaccess.ExecuteNonQuery(strStoredProcedure, parameters); //cmd.ExecuteNonQuery();
parameters.Clear();
}
Also I want to remove all records in dt2 from dtTotal. I am not sure how to modify the stored procedure.
Thanks for help.
If i have understood what you are trying to do correctly, then this is potentially how I would prefer to implement the solution.
I would pass the 2 datatables as TABLE variables to an SP - similar to below and then use JOINs to both UPDATE and DELETE as required using SET operations - thus affecting multiple rows in one query and avoid looping through each row separately.
As mentioned by AdaTheDev in the related answer, you will end up creating a "TABLE" type but there is no drawback to having one extra type and this solution will scale a lot better than a looping approach would.
DISCLAIMER :- Code below may not be syntactically correct but i hope you get the picture of what I am proposing.
CREATE TYPE TableType AS TABLE
(
MetricId INT,
Descr VARCHAR(300) --or whatever length is appropriate,
EntryDE INT
);
GO
CREATE PROCEDURE [dbo].[Admin_Fill]
#RowsForUpdate TableType READONLY,
#RowsForDelete TableType READONLY
AS
BEGIN
-- Update all the Descriptions for all the rows
UPDATE
t
SET
t.Descr = u.Descr
FROM
tbTotal t
INNER JOIN #RowsForUpdate u
ON t.EntryDE = u.EntryDE AND t.MetricId = u.MetricId
-- Delete the rows to be deleted
DELETE t
FROM tbTotal t
INNER JOIN #RowsForDelete d
ON t.EntryDE = u.EntryDE AND t.MetricId = u.MetricId
END

SQL: Update a row and returning a column value with 1 query

I need to update a row in a table, and get a column value from it. I can do this with
UPDATE Items SET Clicks = Clicks + 1 WHERE Id = #Id;
SELECT Name FROM Items WHERE Id = #Id
This generates 2 plans/accesses to the table. Is possibile in T-SQL to modify the UPDATE statement in order to update and return the Name column with 1 plan/access only?
I'm using C#, ADO.NET ExecuteScalar() or ExecuteReader() methods.
You want the OUTPUT clause
UPDATE Items SET Clicks = Clicks + 1
OUTPUT INSERTED.Name
WHERE Id = #Id
Accesses table only once :
DECLARE #Name varchar(MAX);
UPDATE Items SET Clicks = Clicks + 1 , #Name = Name WHERE Id = #Id;
SELECT #Name;
If you're using SQL Server 2005 onwards, the OUTPUT clause is ideal for this
Use a Stored procedure for this.
Create a stored procedure that takes the #id as a parameter and does both of those things. You then use a DbDataAdapter to call the stored procedure.
I could not manage to update and return one row inside a select statement. I.e you can not use the selected value from the other answers.
In my case, I wanted to use the selected value in a query. The solution I came up with was:
declare #NextId int
set #NextId = (select Setting from Settings where key = 'NextId')
select #NextId + ROW_NUMBER() over (order by SomeColumnOfYourTable) from YourTable
update Settings set Setting = Setting + ##ROWCOUNT
where key = 'NextId'

Getting autonumber primary key from MS SQL Server

I am currently working in C#, and I need to insert a new record into one table, get the new primary key value, and then use that as a foreign key reference in inserting several more records. The Database is MS SQL Server 2003. All help is appreciated!
The way to get the identity of the inserted row is with the SCOPE_IDENTITY() function. If you're using stored procedures then this would look something like the following to return the row identity as an output parameter.
CREATE PROCEDURE dbo.MyProcedure
(
#RowId INT = NULL OUTPUT
)
AS
INSERT INTO MyTable
(
Column1
,Column2
,...
)
VALUES
(
#Param1
,#Param2
,...
);
SET #RowId = SCOPE_IDENTITY();
You can then use this value for any subsequent inserts (alternatively, if you can pass the data all into the stored procedure, then you can use it in the remainder of the procedure body).
If you're passing the SQL in dynamically then you use much the same technique, but with a single string with statement delimiters (also ; in SQL), e.g.:
var sql = "INSERT INTO MyTable (Column1, Column2, ...) VALUES (#P1, #P2, ...);" +
"SELECT SCOPE_IDENTITY();";
Then if you execute this using ExecuteScalar you'll be able to get the identity back as the scalar result and cast it to the right type. Alternatively you could build up the whole batch in one go, e.g.
var sql = "DECLARE #RowId INT;" +
"INSERT INTO MyTable (Column1, Column2, ...) VALUES (#P1, #P2, ...);" +
"SET #RowId = SCOPE_IDENTITY();" +
"INSERT INTO MyOtherTable (Column1, ...) VALUES (#P3, #P4, ...);";
This may not be exactly the right syntax, and you may need to use SET NOCOUNT ON; at the start (my mind is rusty as I rarely use dynamic SQL) but it should get you on the right track.
The best way of doing this is the use SCOPE_IDENTITY() function in TSQL. This should be executed as part of the insert i.e.
SqlCommand cmd = new SqlCommand(#"
INSERT INTO T (Name) VALUES(#Name)
SELECT SCOPE_IDENTITY() As TheId", conn);
cmd.AddParameter("#Name", SqlDbType.VarChar, 50).Value = "Test";
int tId = (int)cmd.ExecuteScalar();
Alternatively you can assign SCOPE_IDENTITY() to a variable to be used in successive statements. e.g.
DECLARE #T1 int
INSERT INTO T (Name) VALUES('Test')
SELECT #T1 = SCOPE_IDENTITY()
INSERT INTO T2 (Name, TId) VALUES('Test', #T1)
If you are just using SQL then check Duncan's answer. If however you are using LINQ then you can create the entity, save it to the DB and the ID parameter will be populated automatically.
Given a user entity and a user table it might look like this:
using(var db = new DataContext()) {
var user = new User { Name = "Jhon" };
db.Users.InsertOnSubmit(user);
db.SubmitChanges();
/* At this point the user.ID field will have the primary key from the database */
}

Categories

Resources