Stored Procedure that updates fields with different values - sql

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

Related

How to get one data table from Stored procedure that has multiple select statements using sql server

I have two select statements in my stored procedure:
alter proc multiple
select * from table-one
select * from table-two
Now how to get the data of table-one only by executing the stored procedure?
You can pass input variable and use if statment. For example:
ALTER PROCEDURE multiple
#choice INT
AS
BEGIN
IF (#choice = 1)
BEGIN
SELECT * FROM Table1
END
IF (#choice = 2)
BEGIN
SELECT * FROM Table2
END
IF (#choice = 3)
BEGIN
SELECT * FROM Table1
SELECT * FROM Table2
END
END
And execution of procedure:
EXECUTE multiple #choice = 1 -- to use 1st select
EXECUTE multiple #choice = 2 -- to use 2st select
EXECUTE multiple #choice = 3 -- to use both selects
You can use TEMP table to fill all result in the temp table.
if you have 3 table name tab_1,tab_2,tab_3 then create a temp table with column maximum from these table(tab_1,tab_2,tab_3) and add a extra column to temp table to identify data from tables.
tab_1(id bigint,name varchar(50))
tab_2(id bigint,email varchar(50))
tab_3(id bigint,address varchar(50),phone varchar(50))
then your temp table should be like this
#tmp(col1 bigint(),col2 varchar(50),col3 varchar(50),from_table varchar(50))
e.g
create table tab_1
(
id bigint identity(1,1),
name varchar(50),
email varchar(50)
)
insert into tab_1(name,email) values
('a','a#mail.com'), ('b','c#mail.com'),
('a1','a1#mail.com'), ('a2','a2#mail.com'),
('a3','a3#mail.com'), ('a4','a4#mail.com'),
('b1','b1#mail.com'),('b2','b2#mail.com')
create table tab_2
(
id bigint identity(1,1),
name varchar(50),
email varchar(50),
amount decimal(18,2)
)
insert into tab_2(name,email,amount) values
('a','a#mail.com',12.5), ('b','c#mail.com',11.6),
('a1','a1#mail.com',11.7), ('a2','a2#mail.com',88.9),
('a3','a3#mail.com',90), ('a4','a4#mail.com',45),
('b1','b1#mail.com',78),('b2','b2#mail.com',88)
and the Sp should be like
create table #tab(col1 bigint,
col2 varchar(50),
col3 varchar(50),col4 varchar(50),table_from varchar(50))
insert into #tab(col1,col2,col3,table_from)
select id,name,email,'table_1' from tab_1
insert into #tab(col1,col2,col3,col4,table_from)
select id,name,email,amount,'table_2' from tab_2
select * from #tab
FIDDLE DEMO

SQL Server : split column into table in a trigger

I have a table that looks something like this:
UserID Email
-----------------------------------
1 1_0#email.com;1_1#email.com
2 2_0#email.com;2_1#email.com
3 3_0#email.com;3_3#email.com
And I need to create a temp table that will look like this:
UserID Email
-----------------------------------
1 1_0#email.com
1 1_1#email.com
2 2_0#email.com
2 2_1#email.com
3 3_0#email.com
3 3_1#email.com
The temp table will be used in a update trigger and I was wondering if there is a more elegant approach than doing something like this:
-- Create temp table to hold the result table
CREATE TABLE #resultTable(
UserID int,
Email nvarchar(50)
)
-- Create temp table to help iterate through table
CREATE TABLE #tempTable(
ID int IDENTITY(1,1),
UserID int,
Email nvarchar(50)
)
-- Insert data from updated table into temp table
INSERT INTO #tempTable
SELECT [UserId], [Email]
FROM inserted
-- Iterate through temp table
DECLARE #count int = ##ROWCOUNT
DECLARE #index int = 1
WHILE (#index <= #count)
BEGIN
DECLARE #userID int
DECLARE #email nvarchar(50)
-- Get the user ID and email values
SELECT
#userID = [UserID], #email = [Email]
FROM #tempTable
WHERE [ID] = #index
-- Insert the parsed email address into the result table
INSERT INTO #resultTable([UserID], [Email])
SELECT #userID, [Data]
FROM myFunctionThatSplitsAColumnIntoATable(#email, ';')
SET #index = #index + 1
END
-- Do stuff with the result table
You'd better avoid iterative approaches when using T-SQL unless strictly necessary, specially inside triggers.
You can use the APPLY operator.
From MSDN:
The APPLY operator allows you to invoke a table-valued function for each row returned by an outer table expression of a query.
So, you can try to replace all your code with this:
INSERT INTO #resultTable(UserID, Email)
SELECT T1.UserID
,T2.Data
FROM updated T1
CROSS APPLY myFunctionThatSplitsAColumnIntoATable(T1.Email, ';') AS T2

Passing multiple parameters into a Table valued function

I have a table valued function as below. When I am trying to pass more than one parameter at the same time I am getting a error like "Function has too many arguments specified" .
CREATE FUNCTION [dbo].[GetCompanyUsers](#CompanyId BIGINT)
RETURNS #Users TABLE (Id BIGINT,Contact NVarchar(4000))
AS
BEGIN
INSERT INTO #Users(Id,Contact)
SELECT [Id]
,ISNULL([FirstName],'')+' ' +ISNULL([LastName],'') AS [Contact]
FROM [dbo].[CompanyAddressesContacts]
WHERE [CompanyId]=#CompanyId
ORDER BY ISNULL([FirstName],'')+' ' +ISNULL([LastName],'')
RETURN
END
What modifications I require in the above code so that it allows multiple values and I need to use the function in a "WHERE" condition in my dataset.
WHERE(Document_RFIs.CreatedBy IN
(SELECT Id FROM dbo.GetCompanyUsers(#CompanyId)))
This may help (but the fundamental problem is - passing a comma delimited string is something to be avoided unless absolutely necessary - which explains why you have received so few answers):-
--set nocount on
--create table #Document_RFIs (
-- CreatedBy varchar(50),
-- columna varchar(50),
-- columnb varchar(50),
-- columnc varchar(50)
--)
--insert into #Document_RFIs values
-- ('albert einstein','another','value',null),
-- ('marie curie','some',null,'tuna'),
-- ('isaac newton','why','not','provide'),
-- ('kepler','some','test','data'),
-- ('robert boyle','with','your','question'),
-- ('john dalton','it',null,'would'),
-- ('enrico fermi','make','helping','you'),
-- ('peter higgs','so','much','easier')
--create table #CompanyAddressesContacts (
-- companyid int,
-- firstname varchar(50),
-- lastname varchar(50)
--)
--insert into #CompanyAddressesContacts values (22,'albert','einstein')
--insert into #CompanyAddressesContacts values (23,'marie','curie')
--insert into #CompanyAddressesContacts values (23,'isaac','newton')
--insert into #CompanyAddressesContacts values (24,null,'kepler')
--insert into #CompanyAddressesContacts values (25,'robert','boyle')
--insert into #CompanyAddressesContacts values (25,'enrico','fermi')
--insert into #CompanyAddressesContacts values (26,'peter','higgs')
declare #ids varchar(1024)
set #ids='23,24,25'
create table #id (
companyid int
)
declare #pos int
while DATALENGTH(#ids)>0 begin
set #pos=charindex(',',#ids)
if #pos>0 begin
insert into #id values (left(#ids,#pos-1))
set #ids=SUBSTRING(#ids,#pos+1,DATALENGTH(#ids))
end else begin
insert into #id values (#ids)
set #ids=''
end
end
select d.*
from #Document_RFIs d
where exists(
select cac.*
from #CompanyAddressesContacts cac
join #id i on i.companyid=cac.companyid
where isnull(cac.firstname+' ','')+isnull(cac.lastname,'')=d.CreatedBy
)
--drop table #id
--drop table #Document_RFIs
--drop table #CompanyAddressesContacts
I would do something like this:
First convert your #CompanyId to rows
WITH CompanyIds AS (
SELECT Id
FROM CompanyTable -- Same as the source of the #CompanyId
WHERE Id IN (#CompanyId)
)
Then extract all users
,Users AS (
SELECT UserId
FROM CompanyIds
CROSS APPLY (
SELECT Id AS UserId
FROM dbo.GetCompanyUsers(CompanyIds.Id)
) AS CA1
)
And then use it in the where statement
WHERE Document_RFIs.CreatedBy IN (SELECT UserId
FROM Users)

SQL Triggers - how do I get the updated value?

How do I get the value of the updated record in a SQL trigger - something like this:
CREATE TRIGGER TR_UpdateNew
ON Users
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
EXEC UpdateProfile (SELECT UserId FROM updated AS U);
END
GO
Obviously this doesn't work, but you can see what I am trying to get at.
Provide you are certain that only one value will ever be updated, you can do this...
CREATE TRIGGER TR_UpdateNew
ON Users
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #user_id INT
SELECT
#user_id = inserted.UserID
FROM
inserted
INNER JOIN
deleted
ON inserted.PrimaryKey = deleted.PrimaryKey
-- It's an update if the record is in BOTH inserted AND deleted
EXEC UpdateProfile #user_id;
END
GO
If multiple values can be updated at once, only one of them will get processed by this code. (Although it won't error.)
You could use a cursor, or if it's SQL Server 2008+ you can use table variables.
Or, more commonly, just move the StoredProcedure code into the trigger.
Based on my knowledge you would need to create a CURSOR to loop through all the updated values to execute the UpdateProfile procedure. Keep in mind this will slow down your update process.
Declare #UserID int --Assuming
Declare UpdateProfile_Cursor Cursor for Select UserID From inserted;
Open Cursor UpdateProfile_Cursor;
Fetch Next from UpdateProfile_Cursor Into #UserID;
While ##FETCH_STATUS == 0
Begin
Exec UpdateProfile #UserID
Fetch Next from UpdateProfile_Cursor Into #UserID;
End
CLOSE UpdateProfile_Cursor
DEALLOCATE UpdateProfile_Cursor
My syntax may be a little off but this will give you the desired effect. Again, consider revising your logic to handle multiple updates as using cursors is resource intensive.
You can do something like this example where I'm logging changes to a transaction history table:
create table dbo.action
(
id int not null primary key ,
description varchar(32) not null unique ,
)
go
insert dbo.action values( 1 , 'insert' )
insert dbo.action values( 2 , 'update' )
insert dbo.action values( 3 , 'delete' )
go
create table dbo.foo
(
id int not null identity(1,1) primary key ,
value varchar(200) not null unique ,
)
go
create table dbo.foo_history
(
id int not null ,
seq int not null identity(1,1) ,
action_date datetime not null default(current_timestamp) ,
action_id int not null foreign key references dbo.action ( id ),
old_value varchar(200) null ,
new_value varchar(200) null ,
primary key nonclustered ( id , seq ) ,
)
go
create trigger foo_update_01 on dbo.foo for insert, update , delete
as
set nocount on
set xact_abort on
set ansi_nulls on
set concat_null_yields_null on
--
-- record change history
--
insert dbo.foo_history
(
id ,
action_id ,
old_value ,
new_value
)
select id = coalesce( i.id , d.id ) ,
action_id = case
when i.id is not null and d.id is null then 1 -- insert
when i.id is not null and d.id is not null then 2 -- update
when i.id is null and d.id is not null then 3 -- delete
end ,
old_value = d.value ,
new_value = i.value
from inserted i
full join deleted d on d.id = i.id
go
But you can use the same sort of technique, mix it up a bit and pass the entire set of values to a stored procedure, like I do in the following example (using the table schema above).
First, create a stored procedure that expects a particular temp table to exist at runtime, thus:
--
-- temp table must exist or the stored procedure won't compile
--
create table #foo_changes
(
id int not null primary key clustered ,
action_id int not null ,
old_value varchar(200) null ,
new_value varchar(200) null ,
)
go
--
-- create the stored procedure
--
create procedure dbo.foo_changed
as
--
-- do something useful involving the contents of #foo_changes here
--
select * from #foo_changes
return 0
go
--
-- drop the temp table
--
drop table #foo_changes
go
Once you've done that, create a trigger that will create and populate the temp table expected by the stored procedure and then execute the stored procedure:
create trigger foo_trigger_01 on dbo.foo for insert, update , delete
as
set nocount on
set xact_abort on
set ansi_nulls on
set concat_null_yields_null on
--
-- create the temp table. This temp table will be in scope for any stored
-- procedure executed by this trigger. It will be automagickally dropped
-- when trigger execution is complete.
--
-- Any changes made to this table by a stored procedure — inserts,
-- deletes or updates are, of course, visible to the trigger upon return
-- from the stored procedure.
--
create table #foo_changes
(
id int not null primary key clustered ,
action_id int not null ,
old_value varchar(200) null ,
new_value varchar(200) null ,
)
--
-- populate the temp table
--
insert #foo_changes
(
id ,
action_id ,
old_value ,
new_value
)
select id = coalesce( i.id , d.id ) ,
action_id = case
when i.id is not null and d.id is null then 1 -- insert
when i.id is not null and d.id is not null then 2 -- update
when i.id is null and d.id is not null then 3 -- delete
end ,
old_value = d.value ,
new_value = i.value
from inserted i
full join deleted d on d.id = i.id
--
-- execute the stored procedure. The temp table created above is in scope
-- for the stored procedure, so it's able to access the set of changes from
-- the trigger.
--
exec dbo.foo_changed
go
That's about all there is to it. It's simple, it's easy, it works for change sets of any size. And, it's safe, with no race conditions or collisions with other users in the system.

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