Passing a list into a stored procedure - sql

I have two lists of IDs that needs to be inserted into the database. What I am want to do here is, passing these two lists into a stored procedure and trying to add it with a while loop. I tried to use user defined type to define the array, but do not know how to pass it into the insert command
CREATE TYPE [dbo].[Array] AS TABLE(
[Item] [NVARCHAR](MAX) NULL
);
This is the SQL/PL:
ALTER PROCEDURE [dbo].[sp_create_order]
(#productIds AS Array READONLY, #priceIds AS Array READONLY)
AS
BEGIN
DECLARE #i int
DECLARE #n int
set #n = count(#productIds)
set #i = 1;
while (#i <= #n)
BEGIN
INSERT INTO order_detail(product_id,price_id)
VALUES(#productIds[i],#priceIds[i]);
set #i = #i + 1;
END
END
RETURN 0

Consider changing your type to include both pairs.
CREATE TYPE [dbo].[ProductPrices] AS TABLE(
[ProductId] [NVARCHAR](MAX) NULL,
[PriceId] [NVARCHAR](MAX) NULL
);
Then change your procedure to the following:
create procedure [dbo].[sp_create_order] #productPrices dbo.ProductPrices READONLY
as
begin
insert into order_detail
select p.ProductId,
p.PriceId
from #productPrices p
end
Then you can call your procedure as such.
declare #productPrices dbo.ProductPrices;
insert into #productPrices
values('1', '7'),
('2', '12'),
('3', '8');
exec dbo.sp_create_order #productPrices;

Looks like you are learning table valued parameters , you should create a table valued parameter with two columns so that you can directly insert
Here is one way to solve your current problem, where we can generate a sequence number using ROW_NUMBER and get a row value from both the tables
create PROCEDURE [dbo].[sp_create_order]
(#productIds AS Array READONLY, #priceIds AS Array READONLY)
AS
BEGIN
INSERT INTO order_detail(product_id,price_id)
select T1.item, T2.item
FROM
(select item , ROW_NUMBER() over ( order by (select null)) as seq from #productIds) T1
join
(select item , ROW_NUMBER() over ( order by (select null)) as seq from #priceIds) T2
on T1.seq = T2.seq
END

Related

Inserting data into table from table-valued parameter

I am trying to create a stored procedure to which is passed a TVP and then some data from the TVP is inserted into two tables.
I have already implemented the stored procedure, but only the second insert (the only one that does not read from the TVP) is working. The other two are not working (do not insert anything) and I can't seem to figure out why.
I have tried to create a dummy TVP in SQL Server and run the procedure there, but that also did not work. Is this being caused by the fact TVPs are readonly? I would assume not, since I am not actually inserting or updating data inside the TVP.
Is there a way to make this work?
Thank you for your assistance!
Table-valued parameter definition:
CREATE TYPE dbo.Ingredients
AS TABLE
(
Quantity int,
Measure nvarchar(50),
Ingredient nvarchar(50),
)
GO
Stored procedure:
ALTER PROCEDURE uspCreateRecipe
(#IDUser int,
#RecipeName nvarchar(50),
#Category nvarchar(50),
#Difficulty nvarchar(50),
#Duration nvarchar(50),
#ING dbo.Ingredients READONLY,
#Execution text)
AS
BEGIN
INSERT INTO dbo.Ingredients
VALUES ((SELECT Ingredient FROM #ING WHERE NOT EXISTS (SELECT Ingredient FROM #ING WHERE Ingredient IN (SELECT IngredientName FROM dbo.Ingredients))), 2)
INSERT INTO dbo.Recipes
VALUES (#IDUser, #RecipeName, NULL,
(SELECT IDDifficulty FROM dbo.Difficulty WHERE Difficulty = #Difficulty),
(SELECT IDDuration FROM dbo.Duration WHERE Duration = #Duration ),
NULL,
(SELECT IDCategory FROM dbo.Category WHERE CategoryName = #Category ),
#Execution , NULL, 2, GETDATE())
INSERT INTO dbo.Recipes_Ingredients
VALUES (SCOPE_IDENTITY(),
(SELECT Quantity FROM #ING),
(SELECT IDMeasure FROM dbo.Measure WHERE Measure IN (SELECT Measure FROM #ING)),
(SELECT IDIngredient FROM dbo.Ingredients WHERE IngredientName IN (SELECT Ingredient FROM #ING)))
END
Rather than using VALUES with sub-queries, just use SELECT.
Always list the columns you are inserting into. Its clearer and will reduce errors especially if you modify the table structure in future,
Your first query appeared to be overcomplicated - if indeed it worked at all.
Your third query should have thrown an error because you have multiple IN sub-queries which should have resulted in a "sub-query returned multiple results" error.
The text datatype is depreciated use varchar(max).
Normally you want to SET NOCOUNT, XACT_ABORT ON.
Always RETURN a status so your calling app knows whether it succeeded or not. 0 will be returned by default by I prefer to be explicit.
Semi-colon terminate all statements.
ALTER PROCEDURE uspCreateRecipe
(
#IDUser int
, #RecipeName nvarchar(50)
, #Category nvarchar(50)
, #Difficulty nvarchar(50)
, #Duration nvarchar(50)
, #ING dbo.Ingredients READONLY
, #Execution nvarchar(max) -- text is depreciated
)
AS
BEGIN
SET NOCOUNT, XACT_ABORT ON;
INSERT INTO dbo.Ingredients ([Name], Col2)
SELECT Ingredient, 2
FROM #ING
WHERE Ingredient NOT IN (SELECT IngredientName FROM dbo.Ingredients);
INSERT INTO dbo.Recipes (IDUser, RecipeName, Col3, IDDifficulty, IDDuration, Col6, IDCategory, Col8, Col9, Col10, Co11)
SELECT #IDUser, #RecipeName, NULL, IDDifficulty
, (SELECT IDDuration FROM dbo.Duration WHERE Duration = #Duration)
, NULL
, (SELECT IDCategory FROM dbo.Category WHERE CategoryName = #Category)
, #Execution, NULL, 2, GETDATE()
FROM dbo.Difficulty
WHERE Difficulty = #Difficulty;
INSERT INTO dbo.Recipes_Ingredients (IDRecipe, Quantity, IDMeasureid, IDIngredient)
SELECT SCOPE_IDENTITY(), Quantity
, (SELECT IDMeasure FROM dbo.Measure WHERE Measure = I.Measure)
, (SELECT IDIngredient FROM dbo.Ingredients WHERE IngredientName = I.Ingredient)
FROM #ING I;
RETURN 0;
END;

Using the identity column to add a value to a computed column

At times I need to store a temporary value to a field. I have a stored procedure that adds it using:
Insert new record first then
SELECT #Record_Value = SCOPE_IDENTITY();
UPDATE ADMIN_Publication_JSON
SET NonPubID = CAST(#Record_Value as nvarchar(20)) + '_tmp'
WHERE RecID = #Record_Value
It simply takes the identity value and adds an '_tmp' to the end. Is there a way that I can create a default value in the table that would do that automatically if I did not insert a value into that field?
The NonPubID column is just a NVARCHAR(50).
Thanks
You could write a trigger, that replaces NULL with that string upon INSERT.
CREATE TRIGGER admin_publication_json_bi
ON admin_publication_json
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
UPDATE apj
SET apj.nonpubid = concat(convert(varchar(20), i.id), '_tmp')
FROM admin_publication_json apj
INNER JOIN inserted i
ON i.id = apj.id
WHERE i.nonpubid IS NULL;
END;
db<>fiddle
Downside: You cannot explicitly insert NULLs for that column, should that be desired.
Check out NewKey col below:
CREATE TABLE #Table
(
ID INT NOT NULL IDENTITY(1,1) PRIMARY KEY CLUSTERED,
IDValue VARCHAR(1) ,
ModifiedDT DATETIME NULL,
NewKey AS ( CONVERT(VARCHAR(100),ID)+'_Tmp' )
)
INSERT #Table( IDValue, ModifiedDT )
SELECT 'A', GETDATE()
UNION ALL
SELECT 'Y', GETDATE() - 1
UNION ALL
SELECT 'N', GETDATE() - 5
SELECT * FROM #Table

Stored Procedure that updates fields with different values

I am using SQL Server.
I need to create a stored procedure that will update the Data field (table bellow) with different value for every ID value. (the values in the Data fields depend on the user input).
ID | Data
---------
1 | NULL
2 | NULL
3 | NULL
For example:
if ID = 1, Data should be "Test1"
The ID and Data pairs should somehow be input parameters to the stored procedures.
Is this possible, or I'll have to call simple update procedure for every ID/Data pair?
You need to use XML for sending data for multiple rows. For your current problem prepare (generate dynamically) an xml like below.
'<NewDataSet><Table><Id>1</Id><Data>test1</Data></Table><Table><Id>2</Id><Data>test2</Data></Table></NewDataSet>'
Then Prepare a procedure like below.
CREATE PROC [dbo].[UpdateMultipleRecords]
(
#XmlString VARCHAR(MAX)
)
AS
BEGIN
SET NOCOUNT ON;
CREATE TABLE #DATA
(
Id int,
Data varchar(50) NULL
)
DECLARE #DocHandle int
EXEC sp_xml_preparedocument #DocHandle OUTPUT, #XmlString
INSERT INTO #DATA
SELECT Id,Data
FROM OPENXML (#DocHandle, '/NewDataSet/Table',2)
WITH
(
Id int,
Data varchar(50)
)
EXEC sp_xml_removedocument #DocHandle
UPDATE [dbo].[Table1] SET DATA=D.Data
FROM [dbo].[Table1] T INNER JOIN #DATA D ON T.ID=D.Id
IF (SELECT OBJECT_ID('TEMPDB..#DATA')) IS NOT NULL DROP TABLE #DATA
END
And call the procedure as
[UpdateMultipleRecords] '<NewDataSet><Table><Id>1</Id><Data>Test1</Data></Table><Table><Id>2</Id><Data>Test2</Data></Table></NewDataSet>'
You need user-defined table types for this:
Try this:
-- test table
create table yourtable(id int not null, data [varchar](256) NULL)
GO
-- test type
CREATE TYPE [dbo].[usertype] AS TABLE(
[id] [int] not null,
[Data] [varchar](256) NULL
)
GO
-- test procedure
create procedure p_test
(
#tbl dbo.[usertype] READONLY
) as
BEGIN
UPDATE yourtable
SET data = t.data
FROM yourtable
JOIN
#tbl t
ON yourtable.id = t.id
END
go
-- test data
insert yourtable(id)
values(1),(2),(3)
go
Test of script:
declare #t [dbo].[usertype]
insert #t values(1,'hello'),(2,'world')
exec p_test #t
select * from yourtable
Result:
id data
1 hello
2 world
3 NULL
You can use another table with your values as a Source for the update
update t
set
Data = src.Data
from tableDestination t
inner join sourceTable src on
t.ID = src.ID

SQL Server: Stored Proc input table variable workaround

I'm trying to find a good work around to not being able to use a table variable as an input to a stored procedure. I want to insert a single record into a base table and multiple records into a pivot table. My initial thought process led me to wanting a stored proc with separate inputs for the base table, and a single list input for the pivot table records, i.e.:
create proc insertNewTask (#taskDesc varchar(100), #sTime datetime, #eTime datetime, #items table(itemID int))
as
begin
declare #newTask table(newID int)
insert into tasks(description, sTimeUTC, eTimeUTC)
output inserted.ID into #newTask
values(#taskDesc, #sTime, #eTime)
insert into taskItems(taskID, itemID)
select newID, itemID
from #newTask cross join #items
end
As already stated, the above code won't work because of the table variable input, #items (I believe primarily due to variable scope issues). So, are there any good workarounds to this?
Original Question
I have three tables:
CREATE TABLE items
(
ID int PRIMARY KEY,
name varchar(20),
description varchar(100)
)
CREATE TABLE tasks
(
ID int identity(1,1) PRIMARY KEY,
description varchar(100),
sTimeUTC datetime,
eTimeUTC datetime
)
CREATE TABLE taskItems
(
taskID int,
itemID int,
CONSTRAINT fk_taskItems_taskID FOREIGN KEY (taskID) on tasks(ID),
CONSTRAINT fk_taskItems_itemID FOREIGN KEY (itemID) on items(ID)
)
With some initial item data:
insert into items (ID, name, description)
select 1, 'nails', 'Short piece of metal, with one flat side and one pointed side' union
select 2, 'hammer', 'Can be used to hit things, like nails' union
select 3, 'screws', 'I''m already tired of writing descriptions for simple tools' union
select 4, 'screwdriver', 'If you can''t tell already, this is all fake data' union
select 5, 'AHHHHHH', 'just for good measure'
And I have some code for creating a new task:
declare #taskDes varchar(100), #sTime datetime, #eTime datetime
select #taskDes = 'Assemble a bird house',
#sTime = '2011-01-05 12:00', #eTime = '2011-01-05 14:00'
declare #usedItems table(itemID int)
insert into #usedItems(itemID)
select 1 union
select 2
declare #newTask table(taskID int)
insert into tasks(description, sTimeUTC, eTimeUTC)
output inserted.ID into #newTask
values(#taskDes, #sTime, #eTime)
insert into taskItems(taskID, itemID)
select taskID, itemID
from #newTask
cross join #usedItems
Now, I want a way of simplifying/streamlining the creation of new tasks. My first thought was to use a stored proc, but table variables can't be used as inputs, so it won't work. I think I can do this with a view with an insert trigger, but I'm not sure... Is that my best (or only) option?
I have had great luck using XML to pass data to procedures. You can use OPENXML (Transact-SQL) to parse the XML.
-- You already had an example of #usedItems
-- declared and populated in the question
declare #usedItems table(itemID int)
insert into #usedItems(itemID)
select 1 union
select 2
-- Build some XML, either directly or from a query
-- Here I demonstrate using a query
declare #itemsXML nvarchar(max);
select #itemsXML =
'<Items>'
+ (select itemID from #usedItems as Item for xml auto)
+ '</Items>'
print #itemsXML
-- Pass #itemsXML to the stored procedure as nvarchar(max)
-- Inside the procedure, use OPENXML to turn the XML
-- back into a rows you can work with easily
DECLARE #idoc int
EXEC sp_xml_preparedocument #idoc OUTPUT, #itemsXML
SELECT *
FROM OPENXML (#idoc, '/Items/Item',1)
WITH (itemID int)
EXEC sp_xml_removedocument #idoc
Results
<Items><Item itemID="1"/><Item itemID="2"/></Items>
itemID
-----------
1
2

insert data into several tables

Let us say I have a table (everything is very much simplified):
create table OriginalData (
ItemName NVARCHAR(255) not null
)
And I would like to insert its data (set based!) into two tables which model inheritance
create table Statements (
Id int IDENTITY NOT NULL,
ProposalDateTime DATETIME null
)
create table Items (
StatementFk INT not null,
ItemName NVARCHAR(255) null,
primary key (StatementFk)
)
Statements is the parent table and Items is the child table. I have no problem doing this with one row which involves the use of IDENT_CURRENT but I have no idea how to do this set based (i.e. enter several rows into both tables).
Thanks.
Best wishes,
Christian
Another possible method that would prevent the use of cursors, which is generally not a best practice for SQL, is listed below... It uses the OUTPUT clause to capture the insert results from the one table to be used in the insert to the second table.
Note this example makes one assumption in the fact that I moved your IDENTITY column to the Items table. I believe that would be acceptable, atleast based on your original table layout, since the primary key of that table is the StatementFK column.
Note this example code was tested via SQL 2005...
IF OBJECT_ID('tempdb..#OriginalData') IS NOT NULL
DROP TABLE #OriginalData
IF OBJECT_ID('tempdb..#Statements') IS NOT NULL
DROP TABLE #Statements
IF OBJECT_ID('tempdb..#Items') IS NOT NULL
DROP TABLE #Items
create table #OriginalData
( ItemName NVARCHAR(255) not null )
create table #Statements
( Id int NOT NULL,
ProposalDateTime DATETIME null )
create table #Items
( StatementFk INT IDENTITY not null,
ItemName NVARCHAR(255) null,
primary key (StatementFk) )
INSERT INTO #OriginalData
( ItemName )
SELECT 'Shirt'
UNION ALL SELECT 'Pants'
UNION ALL SELECT 'Socks'
UNION ALL SELECT 'Shoes'
UNION ALL SELECT 'Hat'
DECLARE #myTableVar table
( StatementFk int,
ItemName nvarchar(255) )
INSERT INTO #Items
( ItemName )
OUTPUT INSERTED.StatementFk, INSERTED.ItemName
INTO #myTableVar
SELECT ItemName
FROM #OriginalData
INSERT INTO #Statements
( ID, ProposalDateTime )
SELECT
StatementFK, getdate()
FROM #myTableVar
You will need to write an ETL process to do this. You may want to look into SSIS.
This also can be done with t-sql and possibly temp tables. You may need to store unique key from OriginalTable in Statements table and then when you are inserting Items - join OriginalTable with Statements on that unique key to get the ID.
I don't think you could do it in one chunk but you could certainly do it with a cursor loop
DECLARE #bla char(10)
DECLARE #ID int
DECLARE c1 CURSOR
FOR
SELECT bla
FROM OriginalData
OPEN c1
FETCH NEXT FROM c1
INTO #bla
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO Statements(ProposalDateTime) VALUES('SomeDate')
SET #ID = SCOPE_IDENTITY()
INSERT INTO Items(StateMentFK,ItemNAme) VALUES(#ID,#bla)
FETCH NEXT FROM c1
INTO #bla
END
CLOSE c1
DEALLOCATE c1