Multiple inserts in a single stored procedure? - c#

TableName: Information
Stored procedure that inserts data into the above table.
CREATE PROCEDURE sp_insert_information
(
#profileID as int,
#profileName as varchar(8)
#profileDescription as varchar(100)
)
AS
BEGIN
INSERT INTO information(profileid, profilename, profiledescription)
VALUES (#profileID, #profileName, #profileDescription);
END
I call this procedure from .NET, is there a way to do multiple inserts if I pass profileID's as a comma separated parameter? (can I use split function?)
I can either loop through the profileID's and send 1 by 1 to procedure, however my data is going to be the same except different profileID.
Table data (with 3 columns):
1 profileUnavailable User Error
2 profileUnavailable User Error
3 profileUnavailable User Error
4 profileUnavailable User Error
5 profileUnavailable User Error
Any other approaches that I can try to do this in a single shot?

You have a couple options:
SqlBulkInsert - You can create a dataset that you can dump to the table. This is useful for many inserts. This will bypass the procedure altogether.
Table Valued Parameters - You can use a table value parameter as a parameter of the stored procedure, again manipulating data using a dataset.
The CSV Parameter with string split IS an option, but I would recommend one of the above over it.

Nope. That sproc does one insert at a time as it is written presently. You have to invoke it separately.
You might also consider wrapping that up into a transaction so if one fails, all of them won't be committed.

My favourite technique up to some years ago was to have an arsenal of splitting functions, that could split a delimited list of homogeneous values (e.g. all integers, all booleans, all datetimes, etc.) into a table variable. Here is an example of such a function.
CREATE FUNCTION [dbo].[fn_SplitInt](#text varchar(8000),
#delimiter varchar(20) = '|')
RETURNS #Values TABLE
(
pos int IDENTITY PRIMARY KEY,
val INT
)
AS
BEGIN
DECLARE #index int
SET #index = -1
-- while the list is not over...
WHILE (LEN(#text) > 0)
BEGIN
-- search the next delimiter
SET #index = CHARINDEX(#delimiter , #text)
IF (#index = 0) -- if no more delimiters (hence this field is the last one)
BEGIN
IF (LEN(#text) > 0) -- and if this last field is not empty
INSERT INTO #Values VALUES (CAST (#text AS INT)) -- then insert it
ELSE -- otherwise, if this last field is empty
INSERT INTO #Values VALUES (NULL) -- then insert NULL
BREAK -- in both cases exit, since it was the last field
END
ELSE -- otherwise, if there is another delimiter
BEGIN
IF #index>1 -- and this field is not empty
INSERT INTO #Values VALUES (CAST(LEFT(#text, #index - 1) AS INT)) -- then insert it
ELSE -- otherwise, if this last field is empty
INSERT INTO #Values VALUES (NULL) -- then insert NULL
SET #text = RIGHT(#text, (LEN(#text) - #index)) -- in both cases move forward the read pointer,
-- since the list was not over
END
END
RETURN
END
When you have a set of functions like these, then your problem has a solution as simple as this one:
CREATE PROCEDURE sp_insert_information
(
#profileID as varchar(2000),
#profileName as varchar(8)
#profileDescription as varchar(100)
)
AS
BEGIN
DECLARE #T TABLE (Id int)
INSERT INTO #T (Id)
SELECT val FROM dbo.fn_SplitInt(#profileID)
INSERT INTO information(profileid, profilename,profiledescription)
SELECT Id, #profileName, #profileDescription
FROM #T
END
But today it might be quicker to execute, and even require less coding, to generate an XML representation of the data to insert, then pass the XML to the stored procedure and have it INSERT INTO table SELECT FROM xml, if you know what I mean.

WHILE len(#ProfileId) > 0
BEGIN
DECLARE #comm int= charindex(',',#ProfileId)
IF #comm = 0 set #comm = len(#ProfileId)+1
DECLARE #Profile varchar(1000) = substring(#ProfileId, 1, #comm-1)
INSERT INTO Information(ProfileId,ProfileName,ProfileDescription)
VALUES (#ProfileId,#ProfileName,#ProfileDescription)
SET #ProfileId= substring(#ProfileId, #comm+1, len(#ProfileId))
END

Related

Is it possible to define table type input parameter of stored procedure in Firebird?

I need to define stored procedure with table type input parameter, so for example, if I am calling stored procedure from C# and pass datatable as argument to stored procedure, automatically will be mapped to the columns and fields?
I read about creating a global temporary table, but how to use as input parameter in Firebird? I don't know if it is even possible.
Firebird stored procedures only accept scalar values, so you cannot pass a table type input parameter. The workaround is to define a global temporary table (GTT) as a companion to the stored procedure, and fill that global temporary table before calling the stored procedure. The stored procedure then reads from the global temporary table.
To be clear, global temporary tables are persistent definitions (you create them once, and they can then be reused by all connections and users (assuming the user has been given the right privileges)), while the data in a GTT is visible only to the current transaction (on commit delete rows; the default), or the current connection (on commit preserve rows). They are not like (local) temporary tables like in SQL Server.
In other words, the definition of this type of temporary table is global and persistent, but the data is specific to a transaction or connection.
So, what you do is define a global temporary table:
create global temporary table input_sp_example (
col1 integer,
col2 varchar(100)
) on commit delete rows
and an example stored procedure
create procedure sp_example
returns (result1 integer, result2 varchar(8192))
as
declare col1_value integer;
declare col2_value varchar(100);
begin
for select col1, col2 from input_sp_example order by col1, col2 into col1_value, col2_value do
begin
if (col1_value is distinct from result1) then
begin
result1 = col1_value;
result2 = col2_value;
end
else
begin
result2 = result2 || col2_value;
end
suspend;
end
end
You can then call it like:
insert into input_sp_example (col1, col2) values (1, 'a');
insert into input_sp_example (col1, col2) values (1, 'b');
insert into input_sp_example (col1, col2) values (1, 'c');
insert into input_sp_example (col1, col2) values (2, 'a');
select * from sp_example;
This basic example will produce a result of
RESULT1
RESULT2
1
a
1
ab
1
abc
2
a
If you execute the stored procedure once in a transaction, you don't need to do anything else (the data is automatically deleted on commit). If there is a chance that you need to execute the stored procedure multiple times in a single transaction, you will need to empty the table before populating with fresh data using:
delete from input_sp_example

How to simplify batched SQL Inserts with too many parameters

So I am trying to insert data that looks like this:
INSERT INTO RELATIONSHIP_CONFIG (USERID, WORKGROUPID, PRIORITY) VALUES
(#userId, #WorkgroupId10, #SmartFeedPriority10),
(#userId, #WorkgroupId11, #SmartFeedPriority11),
(#userId, #WorkgroupId12, #SmartFeedPriority12),
(#userId, #WorkgroupId13, #SmartFeedPriority13);
Which generally is very simple and linear as all inserts happen one after the other and performs fine (I think).
The issue is that there is a hard limit with the number of SQL Parameters I am allowed to use- 2100.
The upper limit edge case accounts for an insert that is quite a bit above that.
I was thinking about passing the data for WorkgroupId and SmartFeedPriority as a csvs and using a split function to create tables or something like that...
What is the best approach for dealing with data like this?
Maybe creating a stored procedure, passing the #UserId, #WorkgroupId (CSV), and #SmartFeedPriority (CSV) and having linear, one by one inserts done this way, but I am not too sure how the logic for this will look...
Looking at your question it's a bit difficult to suggest a good approach. I'm unable to see how and where the source of the data is.
I see you mentioned a CSV file. You can import data from a CSV file using the below script. Once the data is in a table, you can try one of the below examples.
IF OBJECT_ID('tempdb.dbo.#TempTable', 'U') IS NOT NULL
DROP TABLE #TempTable ;
CREATE TABLE #TempTable
(
[UserID] NVARCHAR(MAX) NULL,
[WorkGroup] NVARCHAR(MAX) NULL,
[SmartFeedPriority] NVARCHAR(MAX) NULL,
)
BULK INSERT #TempTable
FROM ' put your csv file path here '
WITH
(
FIELDTERMINATOR = ',', -- comma delimited
ROWTERMINATOR = '\n',
CODEPAGE = 65001 --'65001'
)
If the data you're trying to insert is from a table, you could try selecting the data into the table you need it to be.
Example :
SELECT [UserID], [WorkgroupID], [SmartFeedPriority]
INTO [dbo].[RELATIONSHIP_CONFIG]
FROM [dbo].[SorceTable]
If you would like to take the procedural route you can try the below. The below sample would work if the source of your data is in a table and you would like to individually insert each record.
Example :
procedure for insert
CREATE PROCEDURE [dbo].[CREATE_RELATIONSHIP_CONFIG](#UserId INT, #WorkgroupId INT, #SmartFeedPriority INT)
AS
BEGIN
INSERT INTO RELATIONSHIP_CONFIG (UserId, WorkgroupId, Priority) VALUES
(#userId, #WorkgroupId, #SmartFeedPriority)
END
You can wrap the above procedure in a while loop.
I've added a example for it below.
declare #UserId int;
declare #WorkgroupId int;
declare #Priority int;
WHILE EXISTS (SELECT [UserID] FROM #TempTable)
BEGIN
SELECT TOP 1 #UserId=[UserID], #WorkgroupId=[WorkGroup] , #Priority=[SmartFeedPriority] FROM #TempTable
EXEC [dbo].[CREATE_RELATIONSHIP_CONFIG] #UserId, #WorkgroupId, #Priority
DELETE TOP (1)FROM #TempTable
END

How to tell if a record is already exists in the database before executing stored procedure?

I want to create a stored procedure to insert data into a table in the database, but it should be unique so I check first for the incoming parameter:
create procedure SP_Insert
#name varchar(50)
AS
if not exists (select Name from Employees where Name = #name)
begin
insert into Employess (Name)
values (#name)
end
My question is, how to tell in my code if the passing parameter hasn't been accepted as a unique value after the execution of the stored procedure?
In my form I have a button (Insert) and a textbox (name), when the user click insert the text value is passed to the stored procedure, and I want to spring a message box warning the user of duplicated entry
Use ##ROWCOUNT to determine that a row was affected and return the value as a parameter. See this answer: How can I get the number of records affected by a stored procedure?
You can do this:
insert into Employess (Name)
select #name
where not exists (select * from Employees where Name = #name)
select ##rowcount
Now the ##rowcount (returned to the caller) is either zero or one depending on whether there was an insert.
var recordsUpdated = command.ExecuteScalar();
Actually you could skip select ##rowocount and not explicitly return anything.
var recordsUpdated = command.ExecuteNonQuery();
That returns the number of affected records. I prefer to be more explicit. Someone could come behind and alter the procedure so that it does something else that changes ##rowcount. (Why? But they could.) And they might not know that something downstream is depending on that affected record count. But if it's explicit, whether a selected value or an output parameter, then someone can tell that something else depends on that value.
create procedure SP_Insert
#name varchar(50), #result bit output
AS
if not exists (select Name from Employees where Name=#name)
begin
insert into Employess (Name) Values (#name)
set #result = 1
End
else set #result = 0
Stored procedure can return a value.
You can change your SP into something like this:
create procedure SP_Insert
#name varchar(50)
AS
BEGIN
if not exists (select Name from Employees where Name=#name)
begin
insert into Employees (Name) Values (#name)
Return 0
end
else begin
Return 1
end
END
Here is the link to MSDN article with more details and examples:
[https://msdn.microsoft.com/en-us/library/ms188655.aspx]

Execute SQL arithmetic stored as string (stored procedure? c#?)

I'm trying to figure out the best way of doing this....SQL SERVER stored procedure? also contemplating doing it in c#, but either way I'm sortof at a stand still.
Basically what I have is a parts table and a column with qty. This qty column could be a fixed number but it could also be dependent on other parameters. (length, width, size etc...). This was originally a very basic project and I cheated with a few if statements, however an increasing number of parts have a calculated quantity. I want to be able to execute a function stored as a string when a certain part should be selected.
so then based on the parts needed, a table would be created with part and its corresponding numeric quantity.
I was reading into sp_executesql, EXEC, but they still aren't making sense (havent found an comparable example)
Table:
PART QTY
==========
X 'CASE WHEN #FinWidth >=124 THEN ROUND(1.5 + (#FinHeight-#FinWidth)/2.2,0) ELSE 10 END'
Y '2'
Query:
DECLARE #sqlCommand nvarchar(1000)
DECLARE #qty decimal(18,3)
DECLARE #finHeight decimal(18,3)
DECLARE #finWidth decimal(18,3)
DECLARE #part varchar(80)
SET #finHeight = 120
SET #finWidth = 100
sp_executesql....something??
EXEC(something)??
Something like this can get you to use string from table and calculate it. Since it's dynamic SQL you can't use it as function, so I am not sure how useful it can be... you can maybe try with stored procedure:
DECLARE #sqlCommand nvarchar(MAX)
DECLARE #finHeight decimal(18,3)
DECLARE #finWidth decimal(18,3)
DECLARE #part varchar(80)
SET #part = 'X'
SET #finHeight = 124
SET #finWidth = 400
SELECT #sqlCommand= 'SELECT ' + QTY FROM dbo.Table1 WHERE PART = #part
SET #sqlCommand = REPLACE(#sqlCommand, '#finHeight', #finHeight)
SET #sqlCommand = REPLACE(#sqlCommand, '#finWidth', #finWidth)
EXEC (#sqlCommand)
SQLFiddle DEMO
I would create a function to return quantity based on the business logic related to misc fields. Example function would be
create dbo.fn_GetQuantity(#Qty int, #width decimal(18, 3), #Height decimal(18,3))
returns int
as
begin
-- TODO Apply all business logic related to #width & #height here and calculate new #Qty
return #Qty
end
then in the proc I would just call this new function with needed parameters.
create proc dbo.sGetParts()
as
begin
select Part, dbo.fn_GetQuantity(Qty, finWidth, finWidth)
from parts
end

Creating a SQL table from a comma concatenated list

I am running SQL Server and I have a stored procedure. I want do a select statement with a WHERE IN clause. I don't know how long the list will be so right now I have tried something as follows
SELECT * FROM table1 WHERE id IN (#idList)
in this solution #idList is a varChar(max). but this doesn't work. I heard about passing in table values, but I am confused about how to do that. Any help would be great
I would suggest using a function to split the incoming list (use the link that Martin put in his comment).
Store the results of the split function in a temporary table or table variable and join it in your query instead of the WHERE clause
select * into #ids from dbo.Split(',', #idList)
select t.*
from table1 t
join #ids i
on t.id = i.s
The most efficient way would be to pass in a table valued parameter (if you're on SQL Server 2008), or an XML parameter (if you're on SQL Server 2005/2000). If your list is small (and you're on SQL Server 2005/2000), passing in your list as a comma (or otherwise) delimited list and using a split function to divide the values out into rows in a temporary table is also an option.
Whichever option you use, you would then join this table (either the table parameter, the table resulting from the XML select, or the temporary table created by the values from the split) to your main query.
Here is a table valued function that takes a nvarchar and returns a table to join on:
Create function [ReturnValues]
(
#Values nvarchar(4000)
)
Returns #ValueTable table(Value nvarchar(2000))
As
Begin
Declare #Start int
Declare #End int
Set #Start = 1
Set #End = 1
While #Start <= len(#Values)
Begin
Set #End = charindex(',', #Values, #Start)
If #End = 0
Set #End = len(#Values) + 1
Insert into #ValueTable
Select rtrim(ltrim(substring(#Values, #Start, #End - #Start)))
Set #Start = #End + 1
End
Return
End
GO
Binding an #idList parameter as you suggested is not possible with SQL.
The best would be bulk inserting the ids into a separated table and than query that table by using an subselect, or joining the IDs.
e.g.
INSERT INTO idTable (id, context) values (#idValue, 1);
INSERT INTO idTable (id, context) values (#idValue, 1);
INSERT INTO idTable (id, context) values (#idValue, 1); // as often as you like
SELECT * FROM table1, idTable WHERE table1.id == idTable.id and idTable.context = 1
The context must be a unique value that identifies the Id Range. That is important for running the stored proc parallel. Without the context information, running the stored procecure in parallel would mix the values from different selections.
If the number of parameters are reasonably small (< 100) you can use several parameters
SELECT * FROM table1 WHERE IN id IN (#id1, #id2, #id3)
If it is longer, look for a split function.

Categories