SQL Server XML queries - sql

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;

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;

Delete and Insert Into

I'm looking to execute a stored procedure everyday where it will firstly delete the data within a specified table and then insert the new data in.
My stored procedure looks roughly like this
ALTER PROCEDURE [dbo].[SP_Name]
AS BEGIN
WITH CTE_Name as
( select
Title,
First_Name,
Surname
From table
)
DELETE [dbo].[NEW_TABLE]
INSERT INTO [dbo].[NEW_TABLE]
Select * from CTE_NAME
END
When I execute the query I get the error invalid object name 'CTE_NAME'
I have tried removing the 'DELETE [dbo].[NEW_TABLE]' line and upon doing this the stored procedure does run and does insert the data into the table.
I'm using SQl Management Studio 2012
I think you need to separate your With by adding a semicolon in front of it
below code should work
DECLARE #TABLE TABLE (
id VARCHAR(100)
)
DELETE FROM #TABLE
;WITH CTE_Name AS
(
SELECT id FROM OtherTable
)
INSERT INTO #TABLE
SELECT id FROM CTE_Name
You could try this code:
ALTER PROCEDURE [dbo].[SP_Name] AS
BEGIN
DECLARE #helperTbl TABLE (Title varchar(100), First_Name varchar(100), Surname varchar(100))
INSERT INTO #helperTbl
SELECT Title,
First_Name,
Surname
FROM [table]
DELETE FROM [dbo].[NEW_TABLE]
INSERT INTO [dbo].[NEW_TABLE]
SELECT * FROM #helperTbl
END

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

Get SQL Insert to work when PK is supplied or NOT

I have the following stored procedure:
ALTER Procedure dbo.APPL_ServerEnvironmentInsert
(
#ServerEnvironmentName varchar(50),
#ServerEnvironmentDescription varchar(1000),
#UserCreatedId uniqueidentifier,
#ServerEnvironmentId uniqueidentifier OUTPUT
)
WITH RECOMPILE
AS
-- Stores the ServerEnvironmentId.
DECLARE #APPL_ServerEnvironment TABLE (ServerEnvironmentId uniqueidentifier)
-- Insert the data into the table.
INSERT INTO APPL_ServerEnvironment WITH(TABLOCKX)
(
ServerEnvironmentName,
ServerEnvironmentDescription,
DateCreated,
UserCreatedId
)
OUTPUT Inserted.ServerEnvironmentId INTO #APPL_ServerEnvironment
VALUES
(
#ServerEnvironmentName,
#ServerEnvironmentDescription,
GETDATE(),
#UserCreatedId
)
-- If #ServerEnvironmentId was not supplied.
IF (#ServerEnvironmentId IS NULL)
BEGIN
-- Get the ServerEnvironmentId.
SELECT #ServerEnvironmentId = ServerEnvironmentId
FROM #APPL_ServerEnvironment
END
The ServerEnvironmentId column is a primary key with a default set on it, which is (newsequentialid()).
I need this stored procedure to work for 2 scenarios:
Value supplied for ServerEnvironmentId - WORKS.
Value not supplied for ServerEnvironmentId - DOES NOT WORK - CANNOT INSERT NULL VALUE. I thought by setting a default on this column this would be fine.
Someone please help to ammend this procedure so that it may work for both scenarios. Solution needs to have minimal changes as all sp's currently following this trend.
Default values are only applied on inserts if the column is not included in the INSERT list. I'd recommend the following not entirely trivial change (I've commented out the lines to be removed):
ALTER Procedure dbo.APPL_ServerEnvironmentInsert
(
#ServerEnvironmentName varchar(50),
#ServerEnvironmentDescription varchar(1000),
#UserCreatedId uniqueidentifier,
#ServerEnvironmentId uniqueidentifier OUTPUT
)
WITH RECOMPILE
AS
---- Stores the ServerEnvironmentId.
--DECLARE #APPL_ServerEnvironment TABLE (ServerEnvironmentId uniqueidentifier)
IF #ServerEnvironmentName is null
SET #ServerEnvironmentName = newid()
-- Insert the data into the table.
INSERT INTO APPL_ServerEnvironment WITH(TABLOCKX)
(
ServerEnvironmentName,
ServerEnvironmentDescription,
DateCreated,
UserCreatedId
)
--OUTPUT Inserted.ServerEnvironmentId INTO #APPL_ServerEnvironment
VALUES
(
#ServerEnvironmentName,
#ServerEnvironmentDescription,
GETDATE(),
#UserCreatedId
)
---- If #ServerEnvironmentId was not supplied.
--IF (#ServerEnvironmentId IS NULL)
--BEGIN
-- -- Get the ServerEnvironmentId.
-- SELECT #ServerEnvironmentId = ServerEnvironmentId
-- FROM #APPL_ServerEnvironment
--END
The default constraint will not be used by this procedure, but you can leave it in place if there are other places where rows may be added to the table.
(My first answer was long and so it this one, so I'm posting a second answer.)
I missed that you were using NewSequentialId. Again, if a column is specified within the insert statement, any DEFAULT values assigned to that column will not be used [unless you use the DEFAULT keyword in the INSERT statement, but that's still all or nothing--you can't say "if #Var is null then DEFAULT"]. I think you are stuck with simple branching and semi-redundant code, along the lines of:
ALTER Procedure dbo.APPL_ServerEnvironmentInsert
(
#ServerEnvironmentName varchar(50),
#ServerEnvironmentDescription varchar(1000),
#UserCreatedId uniqueidentifier,
#ServerEnvironmentId uniqueidentifier OUTPUT
)
WITH RECOMPILE
AS
-- Stores the ServerEnvironmentId.
DECLARE #APPL_ServerEnvironment TABLE (ServerEnvironmentId uniqueidentifier)
IF #ServerEnvironmentId is null
BEGIN
-- ServerEnvironmentId not provided by user, generate during the insert
INSERT INTO APPL_ServerEnvironment WITH(TABLOCKX)
(
ServerEnvironmentName,
ServerEnvironmentDescription,
DateCreated,
UserCreatedId
)
OUTPUT Inserted.ServerEnvironmentId INTO #APPL_ServerEnvironment
VALUES
(
#ServerEnvironmentName,
#ServerEnvironmentDescription,
GETDATE(),
#UserCreatedId
)
-- Get the new ServerEnvironmentId
SELECT #ServerEnvironmentId = ServerEnvironmentId
FROM #APPL_ServerEnvironment
END
ELSE
BEGIN
-- ServerEnvironmentId is provided by user
INSERT INTO APPL_ServerEnvironment WITH(TABLOCKX)
(
ServerEnvironmentName,
ServerEnvironmentDescription,
DateCreated,
UserCreatedId,
ServerEnvironmentId
)
OUTPUT Inserted.ServerEnvironmentId INTO #APPL_ServerEnvironment
VALUES
(
#ServerEnvironmentName,
#ServerEnvironmentDescription,
GETDATE(),
#UserCreatedId,
#ServerEnvironmentId
)
END
(Why lock the entire table during the insert?)
Help to Simplify SQL Insert which uses NEWSEQUNETIALID() column default