Trying to change some stores procedures and query's for better performance. For some part, it's rewriting cursor syntax. To do this,I must fully understand how they work. I tried this simple ETL example, but it does not give me the expected result. Basically, doing an UPSERT here with a cursor.
Example code:
CREATE TABLE #Destination
(PersonID INT, FirstName VARCHAR(10), LastName VARCHAR (10))
INSERT INTO #Destination VALUES (101, 'M', 'Donalds')
INSERT INTO #Destination VALUES (102, NULL, 'Richards')
INSERT INTO #Destination VALUES (103, 'Rianna', 'Lock')
INSERT INTO #Destination VALUES (104, 'Leo', 'Svensson')
CREATE TABLE #SourceTable
(PersonID INT, FirstName VARCHAR(10), LastName VARCHAR (10))
INSERT INTO #Destination VALUES (102, 'Diana', 'Richards')
INSERT INTO #SourceTable VALUES (103, 'Rianna', 'Locks')
INSERT INTO #SourceTable VALUES (106, 'Cleo', 'Davung')
DECLARE #PersonID INT
DECLARE #Firstname VARCHAR (10)
DECLARE #Lastname VARCHAR (10)
DECLARE SimpleCursor CURSOR FOR
SELECT PersonID, FirstName, LastName
FROM #SourceTable
Open SimpleCursor
FETCH NEXT FROM SimpleCursor INTO #PersonID, #Firstname, #Lastname
WHILE ##FETCH_STATUS = 0
BEGIN
IF EXISTS ( SELECT PersonID FROM #Destination
WHERE PersonID = #PersonID )
UPDATE #Destination
SET #Destination.FirstName = #SourceTable.FirstName,
#Destination.LastName = #SourceTable.LastName
FROM #SourceTable
WHERE #Destination.PersonID = #SourceTable.PersonID
ELSE
INSERT INTO #Destination
SELECT PersonID, Firstname, Lastname FROM #SourceTable
FETCH NEXT FROM SimpleCursor INTO #PersonID, #Firstname, #Lastname
END
CLOSE SimpleCursor
DEALLOCATE SimpleCursor
SELECT * FROM #Destination
What am I missing here? I am not updating anything, while PersonID 102 and 103 do exist.
Thanks a lot.
You're not using the variables you fetched the values into in your UPDATE or INSERT statements. Try:
...
IF EXISTS (SELECT *
FROM #Destination
WHERE PersonID = #PersonID)
BEGIN
UPDATE #Destination
SET FirstName = #FirstName,
LastName = #LastName
WHERE PersonID = #PersonID;
END
ELSE
BEGIN
INSERT INTO #Destination
(PersonID,
FirstName,
LastName)
VALUES (#PersonID,
#FirstName,
#LastName);
END;
...
Related
I am using SQL Server 2017. It has SP which inserts data into table passed via parameters.
create table employee(
id int,
first_name varchar(100),
last_name varchar(100),
salary int,
city varchar(100)
)
Create PROCEDURE MasterInsertUpdateDelete
(
#id INTEGER,
#first_name VARCHAR(10),
#last_name VARCHAR(10),
#salary INTEGER,
#city VARCHAR(20),
#StatementType nvarchar(20) = ''
)
AS
BEGIN
IF #StatementType = 'Insert'
BEGIN
insert into employee (id,first_name,last_name,salary,city) values( #id, #first_name, #last_name, #salary, #city)
END
IF #StatementType = 'Select'
BEGIN
select * from employee
END
END
Suppose I execute SP using below statement
exec MasterInsertUpdateDelete
#id = 1,
#first_name = 'Vijay',
#last_name = 'Kumar',
#salary = 100,
#city = 'Pune',
#StatementType = 'Insert'
Now I want to capture original query (replace with parameter values) which is being executed on DB server
i.e. insert into employee (id,first_name,last_name,salary,city) values( 1, 'Vijay', 'Kumar', 100, 'Test')
I used SQL profiler and exended events but it gives me SQL statement along with parameters
i.e.
insert into employee (id,first_name,last_name,salary,city) values( #id, #first_name, #last_name, #salary, #city)
Practically I have more complex store procedure as Dynamic SQL. I would like to capture the executing statement with replace variable values. How can we achieve it in MS SQL Server?
I created a stored procedure but when I tried to use it I got error.
"Msg 8114, Level 16, State 5, Procedure SP_Customer_Add, Line 0 [Batch
Start Line 36] Error converting data type varchar to
uniqueidentifier."
alter proc SP_Customer_Add (
#UserID uniqueidentifier,
#Firstname nvarchar(30),
#Surname nvarchar(30),
#CardNumber nvarchar(16),
#Password nvarchar(4),
#City nvarchar(13),
#Birthdate smalldatetime,
#Email nvarchar(30),
#PhoneNumber nvarchar (10),
#Balance money
)
as
begin
set nocount on;
insert into Customers (
[UserID],
[Firstname],
[Surname],
[CardNumber],
[Password],
[City],
[Birthdate],
[Email],
[PhoneNumber],
[Balance]
) values (
#UserID,
#Firstname,
#Surname,
#CardNumber,
#Password,
#City,
#Birthdate,
#Email ,
#PhoneNumber ,
#Balance
)
select #UserID = newID();
END
I call the procedure like:
exec SP_Customer_Add
'Cem',
'Yücel',
'1234567891234567',
'1111','Malatya',
'1999/12/5',
'Cemyucel#yahoo.com',
'5421234312',
10000
If you're inserting rows with a GUID, then let the RDBMS handle the generation. I assume that the ID is also the CLUSTERED INDEX so change it so that it has a default value:
ALTER TABLE dbo.Customers ADD CONSTRAINT DF_UserID DEFAULT NEWSEQUENTIALID() FOR UserID;
Then DROP your SP and recreate it with the name Customer_Add (because having the prefix is a problem, as my comment says) and recreate the SP without any references to UserID as a variable or to INSERT.
Your are not passing a value for the parameter (#UserID) when calling the procedure SP_Customer_Add
exec SP_Customer_Add
'Cem',
'Yücel',
'1234567891234567',
'1111','Malatya',
'1999/12/5',
'Cemyucel#yahoo.com',
'5421234312',
10000
Below is the altered procedure:
alter proc SP_Customer_Add
(
#Firstname nvarchar(30),
#Surname nvarchar(30),
#CardNumber nvarchar(16),
#Password nvarchar(4),
#City nvarchar(13),
#Birthdate smalldatetime,
#Email nvarchar(30),
#PhoneNumber nvarchar (10),
#Balance money
)
as
begin
set nocount on;
DECLARE #UserID uniqueidentifier
SET #UserID = NEWID()
insert into Customers ([UserID],[Firstname], [Surname], [CardNumber], [Password], [City], [Birthdate], [Email], [PhoneNumber], [Balance] )
values
(
#UserID,
#Firstname,
#Surname,
#CardNumber,
#Password,
#City,
#Birthdate,
#Email ,
#PhoneNumber ,
#Balance
)
END
I am trying to insert a record into a table using MERGE.
If EmployeeCode doesn't exist in the table it should insert the a new record.
My Code is like this
DECLARE #EmployeeCode BIGINT = 1234,
#FirstName VARCHAR(250) = 'FirstName',
#MiddleName VARCHAR(250) = 'LastName',
#LastName VARCHAR(250) = 'LastName',
#Nationality VARCHAR(250) = 'Nationality',
#BloodGroup VARCHAR(4) = 'A+',
#Gender VARCHAR(7) = 'Male'
MERGE dbo.tbTableName AS tb1
USING ( SELECT tbTableName.EmployeeCode
FROM dbo.tbTableName tbTableName
WHERE tbTableName.EmployeeCode = #EmployeeCode) AS tb2 (EmployeeCode)
ON (tb1.EmployeeCode = tb2.EmployeeCode)
WHEN NOT MATCHED THEN
INSERT ( EmployeeCode, FirstName, MiddleName, LastName,Nationality, BloodGroup, Gender )
VALUES ( #EmployeeCode, #FirstName, #MiddleName, #LastName, #Nationality, #BloodGroup, #Gender );
The issue I am having is that even when the EmployeeCode doesn't match any records in the table its not inserting.
I have used an alternative way to solve the problem using by using IF statement like this:
DECLARE #EmployeeCode BIGINT = 1234,
#FirstName VARCHAR(250) = 'FirstName',
#MiddleName VARCHAR(250) = 'LastName',
#LastName VARCHAR(250) = 'LastName',
#Nationality VARCHAR(250) = 'Nationality',
#BloodGroup VARCHAR(4) = 'A+',
#Gender VARCHAR(7) = 'Male'
IF NOT EXISTS ( SELECT tbTableName.EmployeeCode
FROM dbo.tbTableName tbTableName
WHERE tbTableName.EmployeeCode = #EmployeeCode)
BEGIN
INSERT INTO dbo.tbTableName (EmployeeCode, FirstName, MiddleName, LastName,Nationality, BloodGroup, Gender )
VALUES (#EmployeeCode, #FirstName, #MiddleName, #LastName, #Nationality, #BloodGroup, #Gender )
END
Now is the time to go read the documentation and study the first example. That example shows the conversion from a tradition if/else update/insert code block to a single merge statement. Notice how the table source is defined in the example. You are selecting a row from a table (which you know does not exist) - so there are effectively no rows to be used for either insert or update. That is why nothing is inserted. The source of your merge information should be your set of variables just like the documentation example. An abbreviated version:
set nocount on;
DECLARE #EmployeeCode BIGINT = 1234,
#FirstName VARCHAR(250) = 'FirstName',
#MiddleName VARCHAR(250) = 'LastName',
#LastName VARCHAR(250) = 'LastName',
#Nationality VARCHAR(250) = 'Nationality',
#BloodGroup VARCHAR(4) = 'A+',
#Gender VARCHAR(7) = 'Male';
declare #tbl table (EmployeeCode bigint, FirstName varchar(250));
--insert #tbl(EmployeeCode, FirstName) values (1234, 'zork');
MERGE #tbl AS tb1
USING ( SELECT #EmployeeCode, #FirstName) as src(EmployeeCode, FirstName)
ON (tb1.EmployeeCode = src.EmployeeCode)
WHEN NOT MATCHED THEN
INSERT ( EmployeeCode, FirstName )
VALUES ( #EmployeeCode, #FirstName );
select ##ROWCOUNT, * from #tbl;
I have this stored procedure:
CREATE PROCEDURE [dbo].[AddEmployee]
(
#employeeId int OUTPUT,
#firstName varchar(50),
#lastName varchar(50),
#password varchar(100)
)
AS
BEGIN
INSERT INTO Employees(FirstName, LastName)
VALUES(#firstName, #lastName)
INSERT INTO Logins(Password, EmployeeId)
VALUES(#password, #employeeId)
SELECT ##Identity
END
GO
Let,
#employeeId = EmployeeId
generated during insert in Employees
After that, I was trying to insert the #employeeId into the Logins table.
Also, the #employeeId is used as OUTPUT parameter.
How should I do it?
May be we can use INSERTED.EmployeeId, but I don't know how to use it.
CREATE PROCEDURE [dbo].[AddEmployee] (
#firstName VARCHAR(50)
, #lastName VARCHAR(50)
, #password VARCHAR(100)
, #employeeId INT OUTPUT
)
AS
BEGIN
INSERT INTO Employees (
FirstName
, LastName
)
VALUES (
#firstName
, #lastName
)
SELECT #employeeId = SCOPE_IDENTITY()
INSERT INTO Logins (
Password
, EmployeeId
)
VALUES (
#password
, #employeeId
)
END
GO
EXEC [dbo].[AddEmployee] #firstname = 'test'
, #lastname = 'tester'
, #password = '321321'
, #employeeId = ''
Assuming your EmployeeId column is an IDENTITY column, you should use the OUTPUT clause to fetch the value (you output it into a table variable):
DECLARE #empId TABLE (empId int)
INSERT INTO Employees
(
FirstName, LastName
)
OUTPUT INSERTED.EmployeeId INTO #empId
VALUES
(
#firstName,
#lastName
)
Then to get the value from the table variable into a scalar variable, do:
SET #employeeId = (SELECT TOP 1 empId FROM #empId)
The ##Identity value is the last identity inserted in the current session regardless of scope. Hence, it could be the identity of a row inserted by a trigger. It's always better to use scope_identity() instead. For more details, see MSDN.
You can assign an output parameter like:
set #employeeId = scope_identity()
I am trying to create procedure, which is generating an error stating
An explicit value for the identity column in table tblRegisterUser can only be specified when a column list is used and IDENTITY_INSERT is ON.
I tried to surround insert statement with INDENTITY_INSERT to ON,but that too doesn't work. am I missing anything or is it an error with the sub query which i included?
Following is the stored procedure
CREATE PROCEDURE dbo.spInsertUserRegister
(
#FirstName nvarchar(50),
#LastName nvarchar(50),
#Username nvarchar(50),
#Password nvarchar(50),
#Designation nvarchar(50),
#Department nvarchar(50),
#IsAdmin bit
)
AS
BEGIN
INSERT INTO tblRegisterUser Values
(
#FirstName, #LastName, #Username, #Password,#Designation,#Department,#IsAdmin
)
DECLARE #UID INT
SET #UID = ##IDENTITY
INSERT INTO tblLogin(Username,Password,UID,IsAdmin)
Values(#Username, #Password, #UID,(SELECT IsAdmin FROM tblRegisterUser WHERE Username=#Username AND Password=#Password))
END
If the structure of the tblRegisterUser table is something like
ID int primary_key autoincrement
FirstName varchar
LastName varchar
Username varchar
Password varchar
Designation varchar
Department varchar
IsAdmin bit
than this statement is wrong:
INSERT INTO tblRegisterUser Values
(
#FirstName, #LastName, #Username, #Password,
#Designation,#Department,#IsAdmin
)
You should use an explicit column list to specify the columns:
INSERT INTO tblRegisterUser
( FirstName, LastName, Username, Password,
Designation, Department, IsAdmin)
VALUES
(
#FirstName, #LastName, #Username, #Password,
#Designation,#Department,#IsAdmin
)
This way the ID field is automatically populated, and the ##Identity statement should return it correctly.
That said, SQL Server has a few functions that return the generated ID for the last rows, each with it's own specific strengths and weaknesses.
Basically:
##IDENTITY works if you do not use triggers
SCOPE_IDENTITY() works for the code you explicitly called.
IDENT_CURRENT(‘tablename’) works for a specific table, across all scopes.
In almost all scenarios SCOPE_IDENTITY() is what you need, and it's a good habit to use it, opposed to the other options.
A good discussion on the pros and cons of the approaches is also available here.
copied from this answer
Try this one -
CREATE PROCEDURE dbo.spInsertUserRegister
(
#FirstName NVARCHAR(50),
#LastName NVARCHAR(50),
#Username NVARCHAR(50),
#Password NVARCHAR(50),
#Designation NVARCHAR(50),
#Department NVARCHAR(50),
#IsAdmin BIT
)
AS BEGIN
INSERT INTO dbo.tblRegisterUser (FirstName, LastName, Username, [Password], Designation, Department, IsAdmin)
SELECT #FirstName, #LastName, #Username, #Password, #Designation, #Department, #IsAdmin
DECLARE #ID BIGINT
SELECT #ID = SCOPE_IDENTITY()
--SET IDENTITY_INSERT dbo.tblLogin ON;
INSERT INTO dbo.tblLogin (UserName, [password], [uid], IsAdmin)
SELECT #Username, #Password, #ID, IsAdmin
FROM tblRegisterUser
WHERE UserName = #Username
AND [password] = #Password
--SET IDENTITY_INSERT dbo.tblLogin OFF;
END