Requesting verification of this arguably complex SQL script - sql

I'd appreciate it if some SQL experts can take a look at this script and verify if it will work or if it can be improved in anyway (performance, ease, etc.). So, considering the following:
Leads {
LeadId INT,
SourceId TINYINT,
PersonId INT,
PhoneId INT,
EmailId INT,
AddressId INT,
ImporterId SMALLINT,
ImportedDateTime DATETIME2(7)
}
Duplicates {
DuplicateId INT,
SourceId TINYINT,
LeadId INT,
ImporterId SMALLINT,
DuplicatedDateTime DATETIME2(7)
}
How will this script perform:
-- Outside variables provided as part of a stored procedure
DECLARE #SourceId TINYINT;
DECLARE #ImporterId SMALLINT;
PRINT 'Deleting the CSVTemp table if it exists';
IF ((SELECT CASE WHEN OBJECT_ID('CSVTemp') IS NOT NULL THEN 1 ELSE 0 END) = 1)
BEGIN
DROP TABLE [CSVTemp];
END
PRINT 'Creating the CSVTemp table';
CREATE TABLE [CSVTemp](
[FirstName] NVARCHAR(48),
[LastName] NVARCHAR(48),
[Phone] BIGINT,
[Email] VARCHAR(96),
[Street] VARCHAR(64),
[Zip] INT
);
PRINT 'Performing a BULK insert into CSVTemp';
BULK INSERT [CSVTemp] FROM '{File}.csv' WITH (FIELDTERMINATOR = ',', ROWTERMINATOR = '\n');
PRINT 'Adding IDENTITY column to CSVTemp';
ALTER TABLE [CSVTemp] ADD [Id] INT IDENTITY(1,1) NOT NULL;
PRINT 'Adding PK constraint to CSVTemp';
ALTER TABLE [CSVTemp] ADD CONSTRAINT [PK_CSVTemp] PRIMARY KEY CLUSTERED(
[Id] ASC
) WITH(
PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON
) ON [PRIMARY];
PRINT 'Counting CSVTemp rows';
DECLARE #Count INT = (SELECT COUNT(1) FROM [CSVTemp]);
PRINT 'Declaring internal variables';
DECLARE #I INT = 0;
PRINT 'Looping through rows in CSVTemp';
WHILE (#I < (#Count + 1))
BEGIN
BEGIN TRANSACTION
DECLARE #FirstName NVARCHAR(48);
DECLARE #LastName NVARCHAR(48);
DECLARE #Phone BIGINT;
DECLARE #Email VARCHAR(96);
DECLARE #Street VARCHAR(64);
DECLARE #Zip INT;
SELECT #FirstName = [FirstName],
#LastName = [LastName],
#Phone = [Phone],
#Email = [Email],
#Street = [Street],
#Zip = [Zip] FROM [CSVTemp] WHERE ([Id] = #I);
DECLARE #LeadId INT = (
SELECT [Leads].[LeadId]
FROM [People].[Person]
JOIN [Management].[Leads] ON ([Leads].[PersonId] = [Person].[PersonId])
JOIN [Communication].[Phones] ON ([Leads].[PhoneId] = [Phones].[PhoneId])
WHERE (([Person].[FirstName] LIKE #FirstName) OR ([Person].[LastName] LIKE #LastName))
AND ([Phones].[PhoneId] = #Phone)
);
IF (#LeadId IS NOT NULL)
BEGIN
INSERT INTO [Management].[Duplicates]([SourceId], [LeadId], [ImporterId]) VALUES(#SourceId, #LeadId, #ImporterId);
END
ELSE
BEGIN
INSERT INTO [People].[Person]([FirstName], [LastName]) VALUES(#FirstName, #LastName);
DECLARE #PersonId INT = ##IDENTITY;
INSERT INTO [Communication].[Phones]([PhoneTypeId], [Number]) VALUES(6, #Phone);
DECLARE #PhoneId INT = ##IDENTITY;
INSERT INTO [Communication].[Emails]([Address]) VALUES(#Email);
DECLARE #EmailId INT = ##IDENTITY;
INSERT INTO [Location].[Addresses]([PostalCode], [Street]) VALUES(#Zip, #Street);
DECLARE #AddressId INT = ##IDENTITY;
INSERT INTO [Management].[Leads]([SourceId], [PersonId], [PhoneId], [EmailId], [AddressId], [ImporterId]) VALUES(#SourceId, #PersonId, #PhoneId, #EmailId, #AddressId, #ImporterId);
END
COMMIT
SET #I = (#I + 1);
END
PRINT 'Deleting CSVTemp table';
DROP TABLE [CSVTemp];
UPDATE
#Will/#Mitch, I don't know if you guys are still around, but I ended up finalizing the script by converting the WHILE loop to work with a CURSOR. I tested the script by looping through 10.5k rows and it took 3-5 seconds, which is fine with me. Sadly, I thought I fully understood what #Will was saying about the sets, but I couldn't come up with a way to improve it, so I'll leave it as is. If anyone is willing to give me an example script about what #Will was talking about, I would appreciate it, if not, then thank you #Will and #Mitch for the help so far.
Anyway, here's the updated WHILE loop that uses a CURSOR now.
DECLARE #Id INT = 0;
DECLARE C1 CURSOR READ_ONLY FOR (SELECT [Id] FROM [CSVTemp]);
OPEN C1;
FETCH NEXT FROM C1 INTO #Id;
WHILE (##FETCH_STATUS = 0)
BEGIN
BEGIN TRANSACTION
DECLARE #FirstName NVARCHAR(48);
DECLARE #LastName NVARCHAR(48);
DECLARE #Phone BIGINT;
DECLARE #Email VARCHAR(96);
DECLARE #Street VARCHAR(64);
DECLARE #Zip INT;
SELECT #FirstName = [FirstName],
#LastName = [LastName],
#Phone = [Phone],
#Email = [Email],
#Street = [Street],
#Zip = [Zip] FROM [CSVTemp] WHERE ([Id] = #Id);
DECLARE #LeadId INT = (
SELECT [Leads].[LeadId]
FROM [People].[Person]
JOIN [Management].[Leads] ON ([Leads].[PersonId] = [Person].[PersonId])
JOIN [Communication].[Phones] ON ([Leads].[PhoneId] = [Phones].[PhoneId])
WHERE (([Person].[FirstName] LIKE #FirstName) AND ([Person].[LastName] LIKE #LastName))
AND ([Phones].[Number] = #Phone)
);
IF (#LeadId IS NOT NULL)
BEGIN
INSERT INTO [Management].[Duplicates]([SourceId], [LeadId], [ImporterId]) VALUES (#SourceId, #LeadId, #ImporterId);
END
ELSE
BEGIN
INSERT INTO [People].[Person]([FirstName], [LastName]) VALUES(#FirstName, #LastName);
DECLARE #PersonId INT = SCOPE_IDENTITY();
INSERT INTO [Communication].[Phones]([PhoneTypeId], [Number]) VALUES(6, #Phone);
DECLARE #PhoneId INT = SCOPE_IDENTITY();
INSERT INTO [Communication].[Emails]([Address]) VALUES(#Email);
DECLARE #EmailId INT = SCOPE_IDENTITY();
INSERT INTO [Location].[Addresses]([PostalCode], [Street]) VALUES(#Zip, #Street);
DECLARE #AddressId INT = SCOPE_IDENTITY();
INSERT INTO [Management].[Leads]([SourceId], [PersonId], [PhoneId], [EmailId], [AddressId], [ImporterId]) VALUES(#SourceId, #PersonId, #PhoneId, #EmailId, #AddressId, #ImporterId);
END
COMMIT
FETCH NEXT FROM C1 INTO #Id;
END
CLOSE C1;
DEALLOCATE C1;

Don't use ##IDENTITY. Use SCOPE_IDENTITY():
SCOPE_IDENTITY and ##IDENTITY return
the last identity values that are
generated in any table in the current
session. However, SCOPE_IDENTITY
returns values inserted only within
the current scope; ##IDENTITY is not
limited to a specific scope.
For example, there are two tables, T1 and
T2, and an INSERT trigger is defined
on T1. When a row is inserted to T1,
the trigger fires and inserts a row in
T2. This scenario illustrates two
scopes: the insert on T1, and the
insert on T2 by the trigger.
Assuming that both T1 and T2 have
identity columns, ##IDENTITY and
SCOPE_IDENTITY will return different
values at the end of an INSERT
statement on T1. ##IDENTITY will
return the last identity column value
inserted across any scope in the
current session. This is the value
inserted in T2. SCOPE_IDENTITY() will
return the IDENTITY value inserted in
T1. This was the last insert that
occurred in the same scope. The
SCOPE_IDENTITY() function will return
the null value if the function is
invoked before any INSERT statements
into an identity column occur in the
scope.

Related

getting the last inserted id from previous insert statement

I am having a stored procedure with two insert statement, where I want to insert the ID of the first insert statement into the second one.
CREATE PROC [dbo].[Log_Action]
#action_description VARCHAR(MAX),
#creator_id INT,
#entity VARCHAR(50),
#entity_identifier UNIQUEIDENTIFIER
AS
BEGIN
DECLARE #return_value BIT;
BEGIN TRY
INSERT INTO Action_Lookup (action_description)
VALUES (#action_description);
INSERT INTO Audit ([user_id], action_id, CREATED, [guid], entity, entity_identifier)
VALUES (#creator_id, SCOPE_IDENTITY(), GETDATE(), NEWID(), #entity, #entity_identifier);
SET #return_value = 1;
RETURN #return_value;
END TRY
BEGIN CATCH
SET #return_value = 0;
RETURN #return_value;
END CATCH
END
the problem that SCOPE_IDENTITY() returns null, I also tried ##IDENTITY and IDENT_CURRENT but non works.
Try output clause:
CREATE PROC [dbo].[Log_Action]
#action_description VARCHAR(MAX),
#creator_id INT,
#entity varchar(50),
#entity_identifier uniqueidentifier
AS
BEGIN
DECLARE #return_value bit;
BEGIN TRY
INSERT INTO Action_Lookup (action_description)
OUTPUT
#creator_id,
inserted.[id], -- in [] there should be actual name of identity column
GETDATE(),
NEWID(),
#entity,
#entity_identifier
INTO Audit ([user_id], action_id, created, [guid], entity, entity_identifier)
VALUES (#action_description);
set #return_value = 1;
return #return_value;
END TRY
BEGIN CATCH
set #return_value = 0;
return #return_value;
END CATCH
END

Trigger to update table according to user rank

I'm a stranger to SQL Server Triggers.
I ended up having a problem like this. Please have a look.
I have two tables 'users' & 'test'
CREATE TABLE users(
email VARCHAR(250),
rank FLOAT
);
CREATE TABLE test(
score INT,
total INT
);
I need to create a trigger to;
2.1 Update users rank by the value of avg ( avg = test.score / test.total)
2.2 Here's What I tried so far:
CREATE TRIGGER auto_rank ON dbo.test FOR INSERT
BEGIN
DECLARE #sc INT
DECLARE #tot INT
DECLARE #avg FLOAT
#tot = SELECT inserted.total FROM dbo.test
#sc = SELECT inserted.score FROM dbo.test
SET #avg=#sc/#tot
UPDATE dbo.users SET rank=#avg WHERE email=inserted.email
END
You missing the email in test from your table design, but it should have such column per your code:
UPDATE dbo.users SET rank=#avg WHERE email=inserted.email
Then you need a view instead of trigger in this case:
Create view user as (select email, score/total as rank from test group by email);
Hope this help.
Try this :
CREATE TRIGGER auto_rank ON dbo.test FOR INSERT
BEGIN
UPDATE a SET a.rank=b.rn
from
users a
inner join
(select email,inserted.score/inserted.total rn from inserted)b
on a.email=b.email
END
I have not tested this, but this should work fine.
You need to modify your tables so that the test table contains the email column:
CREATE TABLE test(score INT,
total INT,
email varchar(250)
);
Then you can create the trgiger like this:
CREATE TRIGGER [dbo].[auto_rank] ON [dbo].[test]
FOR INSERT
AS
BEGIN
DECLARE MyCursor CURSOR FOR
SELECT score, total, email FROM Inserted
DECLARE #sc INT
DECLARE #tot INT
DECLARE #email VARCHAR(30)
DECLARE #avg FLOAT
DECLARE #MSG VARCHAR(50)
OPEN MyCursor;
FETCH NEXT FROM MyCursor INTO #sc, #tot, #email
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #avg=#sc/#tot
UPDATE users SET rank=#avg WHERE users.email=#email
SELECT #MSG = 'email Updated ' + #email + '. New Rank is ' + Str(#avg, 25, 5);
PRINT #MSG
FETCH NEXT FROM MyCursor
END;
CLOSE MyCursor;
DEALLOCATE MyCursor;
END
Sorry for being so late to continue this thread but, I'm happy to say that I found the answer. it's because of you all.
So, here's what i did;
first;
CREATE TABLE users(
email VARCHAR(250),
rank FLOAT,
constraint pk_users PRIMARY KEY(email)
);
CREATE TABLE test(
email VARCHAR(250),
score INT,
total INT,
constraint pk_test PRIMARY KEY(email),
constraint fk_from_users FOREIGN KEY(email) references users(email)
);
create trigger trig_ex02 on dbo.test
after insert
as
begin
declare #score FLOAT
declare #total FLOAT
declare #average FLOAT
declare #msg varchar(100)
declare #email varchar(250)
set #email = (select email from inserted)
set #score = (select score from inserted)
set #total = (select total from inserted)
set #average =(#score/#total)
select #msg = 'SCORE IS'+ str(#score)+'TOTAL IS'+str(#total)+' AVERAGE IS ' +str(#average,25,5)+' END '
print #msg
UPDATE users SET rank=#average WHERE users.email=#email
end;

Update a Table by Passing Table Name, ColumnName as a Variable Using Cursor

I want to Update a Table. I am getting Table Name and Column Name as a XML DataSet from the Front End. I have written One Cursor for that. But it throws error. Below is my Cursor
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[PMT_Formstatus_Update_Test]
(
#XMLTableNames VARCHAR(MAX)
)
AS
BEGIN
DECLARE #docHandle INT, #ErrorMessage VARCHAR(200), #doc VARCHAR(200)
SET #doc = '<?xml version="1.0" encoding="iso-8859-1" ?>'
DECLARE #Names TABLE
(
TName VARCHAR(50),
CName VARCHAR(50),
IDField VARCHAR(50),
FunctionID INT,
ID INT,
StatusID INT
)
SET #XMLTableNames = #doc + #XMLTableNames
EXEC sp_xml_preparedocument #docHandle OUTPUT, #XMLTableNames
INSERT INTO #Names
SELECT * FROM
OPENXML(#docHandle,'NewDataSet/NameDetails',2)
WITH
(
TName VARCHAR(50),
CName VARCHAR(50),
IDField VARCHAR(50),
FunctionID INT,
ID INT,
StatusID INT
)
DECLARE #FunctionID INT
DECLARE #StatusID INT
DECLARE #ID INT
DECLARE #TableName VARCHAR(50)
DECLARE #ColumnName VARCHAR(50)
DECLARE #IDField VARCHAR(50)
DECLARE #getTables CURSOR
SET #getTables = CURSOR FOR
SELECT FunctionID, TName, CName, StatusID, IDField, ID FROM #Names
OPEN #getTables
FETCH NEXT
FROM #getTables INTO #FunctionID, #TableName, #ColumnName, #StatusID, #IDField, #ID
WHILE ##FETCH_STATUS = 0
BEGIN
UPDATE #TableName SET #ColumnName = 3 WHERE #IDField = #ID
FETCH NEXT
FROM #getTables INTO #FunctionID, #TableName, #ColumnName, #StatusID, #IDField, #ID
END
CLOSE #getTables
DEALLOCATE #getTables
END
How to write Update Query in this case? Please I need all your suggestions...
You need to concatenate them as string then call EXEC('')
EXEC ('UPDATE TableName SET ColumnName1 = 3 WHERE ColumnName2= Value')
Simple Dynamic SQL Example:
DECLARE #SQL NVARCHAR(max),#TableName VARCHAR(128),#ColumnName1 VARCHAR(128),#ColumnName2 VARCHAR(128),#Value NVARCHAR(MAX),#NewValue NVARCHAR(MAX)
SET #TableName='User'
SET #ColumnName1='Session_Id'
SET #ColumnName2='Session_Id_Old'
SET #Value=''''+CAST(NEWID() as NVARCHAR(50))+''''
SET #NewValue=''''+CAST(NEWID() as NVARCHAR(50))+''''
SET #SQL=
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
'UPDATE [#TableName] SET [#ColumnName1] = #NewValue WHERE [#ColumnName2]= #Value','#TableName',#TableName),
'#ColumnName1',#ColumnName1),'#Value',#Value),'#NewValue',#NewValue),'#ColumnName2',#ColumnName2)
EXECUTE(#SQL)
You should add apostrophes to both sides of old/new values if data types are strings
You can hopefully see why what you've written doesn't work, since it's an update statement to execute against a table variable, which affects no actual columns in that table:
declare #TableName table (ID int not null)
declare #ColumnName1 int
declare #ColumnName2 int
declare #Value int
UPDATE #TableName SET #ColumnName1 = 3 WHERE #ColumnName2= #Value
One means to try and achieve what you're doing are dynamic SQL. It's ugly (and will be more so if the data types in the columns vary). There's no real "meta-programming" system built into T-SQL.

How can I iterate over a recordset within a stored procedure?

I need to iterate over a recordset from a stored procedure and execute another stored procedure using each fields as arguments. I can't complete this iteration in the code. I have found samples on the internets, but they all seem to deal with a counter. I'm not sure if my problem involved a counter. I need the T-SQL equivalent of a foreach
Currently, my first stored procedure stores its recordset in a temp table, #mytemp. I assume I will call the secondary stored procedure like this:
while (something)
execute nameofstoredprocedure arg1, arg2, arg3
end
You need to create a cursor to loop through the record set.
Example Table:
CREATE TABLE Customers
(
CustomerId INT NOT NULL PRIMARY KEY IDENTITY(1,1)
,FirstName Varchar(50)
,LastName VARCHAR(40)
)
INSERT INTO Customers VALUES('jane', 'doe')
INSERT INTO Customers VALUES('bob', 'smith')
Cursor:
DECLARE #CustomerId INT, #FirstName VARCHAR(30), #LastName VARCHAR(50)
DECLARE #MessageOutput VARCHAR(100)
DECLARE Customer_Cursor CURSOR FOR
SELECT CustomerId, FirstName, LastName FROM Customers
OPEN Customer_Cursor
FETCH NEXT FROM Customer_Cursor INTO
#CustomerId, #FirstName, #LastName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #MessageOutput = #FirstName + ' ' + #LastName
RAISERROR(#MessageOutput,0,1) WITH NOWAIT
FETCH NEXT FROM Customer_Cursor INTO
#CustomerId, #FirstName, #LastName
END
CLOSE Customer_Cursor
DEALLOCATE Customer_Cursor
Here is a link to MSDN on how to create them.
http://msdn.microsoft.com/en-us/library/ms180169.aspx
This is why I used Raise Error instead of PRINT for output.
http://structuredsight.com/2014/11/24/wait-wait-dont-tell-me-on-second-thought/
It's very easy to loop through the rows in SQL procedure. You just need to use a cursor. Here is an example:
Let us consider a table Employee with column NAME and AGE with 50 records into it and you have to execute a stored procedure say TESTPROC which will take name and age parameters of each row.
create procedure CursorProc
as
begin
declare #count bigint;
declare #age varchar(500)
declare #name varchar(500)
select #count = (select count(*) from employee)
declare FirstCursor cursor for select name, age from employee
open FirstCursor
while #count > 0
begin
fetch FirstCursor into #name, #age
Exec TestProc #name, #age
set #count = #count - 1
end
close FirstCursor
deallocate FirstCursor
end
Make sure you deallocate the cursor to avoid errors.
try this (cursor free looping):
CREATE TABLE #Results (RowID int identity(1,1), Col1 varchar(5), Col2 int, ... )
DECLARE #Current int
,#End int
DECLARE #Col1 varchar(5)
,#Col2 int
,...
--you need to capture the result set from the primary stored procedure
INSERT INTO #Results
(Col1, COl2,...)
EXEC nameofstoredprocedure_1 arg1, arg2, arg3
SELECT #End=##ROWCOUNT,#Current=0
--process each row in the result set
WHILE #Current<#End
BEGIN
SET #Current=#Current+1
SELECT
#Col1=COl1, #Col2=Col2
FROM #Results
WHERE RowID=#Current
--call the secondary procedure for each row
EXEC nameofstoredprocedure_2 #Col1, #Col2,...
END
working example:
CREATE PROCEDURE nameofstoredprocedure_1
(#arg1 int, #arg2 int, #arg3 int)
AS
SELECT 'AAA',#arg1 UNION SELECT 'BBB',#arg2 UNION SELECT 'CCC',#arg3
GO
CREATE PROCEDURE nameofstoredprocedure_2
(#P1 varchar(5), #P2 int)
AS
PRINT '>>'+ISNULL(#P1,'')+','+ISNULL(CONVERT(varchar(10),#P2),'')
GO
CREATE TABLE #Results (RowID int identity(1,1), Col1 varchar(5), Col2 int)
DECLARE #Current int
,#End int
DECLARE #Col1 varchar(5)
,#Col2 int
INSERT INTO #Results
(Col1, COl2)
EXEC nameofstoredprocedure_1 111, 222, 333
SELECT #End=##ROWCOUNT,#Current=0
WHILE #Current<#End
BEGIN
SET #Current=#Current+1
SELECT
#Col1=COl1, #Col2=Col2
FROM #Results
WHERE RowID=#Current
EXEC nameofstoredprocedure_2 #Col1, #Col2
END
OUTPUT:
(3 row(s) affected)
>>AAA,111
>>BBB,222
>>CCC,333

SQL Server: Return uniqueidentifier from stored procedure

Can I return UNIQUEIDENTIFIER from a stored procedure using the RETURN statement or is it only by using the OUTPUT statement?
i.e to return the PersonID UNIQUEIDENTIFIER:
CREATE PROCEDURE CreatePerson
#Name NVARCHAR(255),
#Desc TEXT
AS
DECLARE #Count INT
DECLARE #JobFileGUID UNIQUEIDENTIFIER
-- Check if job exists?
SET #Count = (SELECT COUNT(Name) AS Name FROM Person WHERE Name=#Name)
IF #Count < 1
BEGIN
SET #PersonGUID = NEWID();
INSERT INTO Person
(PersonID, Name, [Desc])
VALUES (#PersonGUID, #Name, #Desc)
END
SELECT #PersonGUID = Person.PersonID
FROM Person
WHERE Name = #Name
RETURN #PersonGUID
GO
Thanks
In stored procedure - only using the OUTPUT statement. In function - return.
Use:
CREATE PROCEDURE CreatePerson
#Name NVARCHAR(255),
#Desc TEXT,
#PersonGUID UNIQUEIDENTIFIER OUTPUT
AS
BEGIN
SET #PersonGUID = ...
END
How to call:
DECLARE
#name NVARCHAR(255),
#desc TEXT,
#personGUID UNIQUEIDENTIFIER
SET #name = 'Bob'
SET #desc = 'One handsome man.'
EXEC [Database].[schema].CreatePerson #name, #desc, #personGUID OUTPUT
From the documentation you can actually see that a return in a stored procedure is actually used as a response code, hence you get the exception when trying to return a uniqueidentifier.
https://learn.microsoft.com/en-us/sql/relational-databases/stored-procedures/return-data-from-a-stored-procedure?view=sql-server-ver16#return-data-using-a-return-code
How I solved it, is by just performing a SELECT after the insert of the generated unique identifier.
DECLARE #ReportId UNIQUEIDENTIFIER;
SET #ReportId = NEWID();
INSERT INTO [dbo].[Report]
([ReportId]
,[ReportName])
VALUES
(#ReportId
,#ReportName)
SELECT #ReportId as ReportIdInternal
You'll have to see how to perform that with multiple selects though.
CREATE TABLE [dbo].[tbl_Clients]( [ClientID] [uniqueidentifier] NULL, [ClientName] varchar NULL, [ClientEnabled] [bit] NULL ) ON [PRIMARY]
GO
CREATE PROCEDURE [dbo].[sp_ClientCreate] #in_ClientName varchar(250) = "New Client 123", #in_ClientEnabled bit, #out_ClientId uniqueidentifier OUTPUT AS
SET #out_ClientId = NEWID();
INSERT INTO tbl_Clients(ClientId, ClientName, ClientEnabled) VALUES( #out_ClientId, #in_ClientName, #in_ClientEnabled)
DECLARE #return_value int, #out_ClientId uniqueidentifier
EXEC #return_value = [dbo].[sp_ClientCreate] #in_ClientName = N'111', #in_ClientEnabled = 1, #out_ClientId = #out_ClientId OUTPUT
SELECT #out_ClientId as N'#out_ClientId'
SELECT 'Return Value' = #return_value
GO
Result:-59A6D7FE-8C9A-4ED3-8FC6-31A989CCC8DB