Inserting data into table from table-valued parameter - sql

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;

Related

SQL Server XML queries

I'm trying to find a solution for this task: write a procedure that accepts XML document in the following format:
<Buyer N="John" S="Doe" CTY="Madrid" CTR="Spain">
<E>John.Doe#gmail.com</E>
<T>123456789</T>
</Buyer>
The procedure must verify if your database already contains the country, city or buyer (check buyer by email).
If any data is missing in your database, your procedure has to insert it into the table.
Call the functions that show the mentioned procedure functionalities.
This is how I started to separate all the important things:
CREATE PROCEDURE pXML
#var XML
AS
SELECT
X.U.value('#N', 'nvarchar(20)') AS pName,
X.U.value('#S', 'nvarchar(20)') AS pSurename,
X.U.value('#CTY', 'nvarchar(20)') AS pCity,
X.U.value('#CTR', 'nvarchar(20)') AS pCountry
FROM
#var.nodes('/Buyer') AS X(U)
SELECT X.U.value('.', 'nvarchar(50)' ) AS pEmail
FROM #var.nodes('/Buyer/E') as X(U)
What I don't know is how to put IF statement, or whatever condition is necessary INSIDE that procedure.
My pseudo code would be:
declare #temp nvarchar(30)
set #temp = pEmail
IF NOT EXISTS(select * from Buyers where Buyer.Email = pEmail)
INSERT INTO Buyer values (pName, pSurename, pCity, pCountry)
(more IF NOT EXISTS statements for city and for country)
Is anyone willing to give me a hand here?
Thanks in advance! Tell me if didn't clarify enough, I'll do my best to add any needed info.
Please try the following solution.
It is using MERGE statement to handle INSERT or UPDATE based on the email.
SQL
-- DDL and sample data population, start
DECLARE #Buyer TABLE (
ID INT IDENTITY PRIMARY KEY,
pEmail NVARCHAR(100),
pName nvarchar(20),
pSurename nvarchar(20),
pCity nvarchar(20),
pCountry nvarchar(20)
);
INSERT INTO #Buyer (pEmail, pName, pSurename, pCity, pCountry) VALUES
(N'John.Doe#gmail.com', N'John', N'Doe', N'Madrid', N'Spain');
-- DDL and sample data population, end
-- before
SELECT * FROM #Buyer;
DECLARE #var XML =
N'<Buyer N="Johnny" S="Doe" CTY="Barcelona" CTR="Spain">
<E>John.Doe#gmail.com</E>
<T>123456789</T>
</Buyer>';
MERGE INTO #Buyer as Trg
USING (
SELECT c.value('(E/text())[1]', 'nvarchar(100)') as pEmail
, c.value('#N', 'nvarchar(20)') as pName
, c.value('#S', 'nvarchar(20)') as pSurename
, c.value('#CTY', 'nvarchar(20)') as pCity
, c.value('#CTR', 'nvarchar(20)') as pCountry
FROM #var.nodes('/Buyer') AS t(c)
) as Src
ON Trg.pEmail = Src.pEmail
WHEN Matched /*AND Src.pName IS NOT NULL*/ THEN -- if needed to add additional conditions
UPDATE
SET Trg.pName = Src.pName
, Trg.pSurename = Src.pSurename
, Trg.pCity = Src.pCity
, Trg.pCountry = Src.pCountry
WHEN NOT MATCHED THEN
INSERT (pEmail, pName, pSurename, pCity, pCountry) VALUES
(Src.pEmail, Src.pName, Src.pSurename, Src.pCity, Src.pCountry)
OUTPUT
$action ,
inserted.*;
-- after
SELECT * FROM #Buyer;

INSERT to a table using SELECT and VALUES at the same time

how can I insert to a table using values in different way? preferably no temp table. below is my stored procedure code, but i get errors on insert
CREATE PROCEDURE setBARS
-- Add the parameters for the stored procedure here
#BUSINESSAREANAME nvarchar(50),
#STAFFNAME nvarchar(50),
#ROLENAME nvarchar(50),
#BARSSTARTDATE date,
#BARSENDDATE date
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
WITH t1 (BUSINESSAREAID) AS (SELECT BUSINESSAREAID FROM BUSINESSAREA WHERE BUSINESSAREANAME = #BUSINESSAREANAME),
t2 (STAFFID) AS (SELECT STAFFID FROM STAFF WHERE STAFFNAME = #STAFFNAME),
t3 (ROLEID) AS (SELECT ROLEID FROM ROLE WHERE ROLENAME = #ROLENAME)
INSERT INTO BARS ([BUSINESSAREAID],[STAFFID],[ROLEID],[BARSSTARTDATE],[BARSENDDATE])
VALUES ((SELECT t1.BUSINESSAREAID, t2.STAFFID, t3.ROLEID FROM t1,t2,t3), #BARSSTARTDATE, #BARSENDDATE)
END
GO
You should be able to include the values directly in the SELECT statement:
INSERT INTO [BARS]
(
[BUSINESSAREAID]
, [STAFFID]
, [ROLEID]
, [BARSSTARTDATE]
, [BARSENDDATE]
)
SELECT
[t1].[BUSINESSAREAID]
, [t2].[STAFFID]
, [t3].[ROLEID]
, #BARSSTARTDATE
, #BARSENDDATE
FROM [t1]
, [t2]
, [t3];
Try This
insert into bars([BUSINESSAREAID],[STAFFID],[ROLEID],[BARSSTARTDATE][BARSENDDATE])
SELECT t1.BUSINESSAREAID, t2.STAFFID, t3.ROLEID FROM t1,t2,t3, #BARSSTARTDATE, #BARSENDDATE
why not put all the values on a variable for example;
declare #val1
set #val1 = (select top 1 businessareaid from t1)
declare #val2
set #val1 = (select top 1 staffid from t2)
insert into bars (val1,val2)
select #val1 , #val2

Passing a list into a stored procedure

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

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