CURSOR with a stored procedure with OUTPUT parameters - sql-server

I have to insert one record at a time into a table and then execute a stored procedure by passing output variables.
The insert part actually does do the this and I see two different records into the destination table. But the stored procedure seems to use the same out parameters that were passed for the very first record inserted into destination table. So basically when a stored procedures is being called in loops over the same output parameters over and over again for each distinct ID that is being inserted into the destination table.
In my pseudocode below it prints 3 times the following 5, 10, 15 . Which is correct since it takes each new ID in the dbo.Table_Test. But in my actual code actually it does take only only very first ID that repeats looping over the same ID three times.
-------- CREATING STORED PROCEDURE --------
USE MyDB;
GO
DROP PROCEDURE IF EXISTS dbo.sp_Testing
USE MyDB;
GO
CREATE PROCEDURE dbo.sp_Testing
#QueueId INT,
-- response
#MainId INT OUT, -- this allows null
#MessageTx VARCHAR(500) OUT,
#SuccessIn BIT OUT
AS
BEGIN
SET NOCOUNT ON;
IF NOT EXISTS (SELECT * FROM MyDB.sys.tables WHERE name = 'Table_Test') --print 1
CREATE TABLE dbo.Table_Test
(
ID INT NOT NULL PRIMARY KEY IDENTITY(5,5),
name VARCHAR(10) NULL,
Phone INT NULL,
category VARCHAR(10) NULL
)
INSERT INTO dbo.Table_Test (name)
VALUES ('Andrew')
SET #MainId = SCOPE_IDENTITY()
PRINT #MainId
END
-------- END OR STORED PROCEDURE --------
GO
-------- INSERT STATEMENTS ---------
USE MyDB;
IF OBJECT_ID('tempdb..#MainTable') IS NOT NULL
DROP TABLE #MainTable
IF OBJECT_ID('tempdb..#Queue') IS NOT NULL
DROP TABLE #Queue
DECLARE #MessageTx VARCHAR(30)
DECLARE #SuccessIn BIT
DECLARE #QueueId INT
DECLARE #MainId INT
DECLARE #ParentId INT
SET #MainId = NULL
SET #SuccessIn = 1
CREATE TABLE #MainTable
(
ID INT NOT NULL PRIMARY KEY IDENTITY(1,1),
name VARCHAR(10) NULL,
Phone INT NOT NULL,
category VARCHAR(10) NULL
)
INSERT INTO #MainTable (name, Phone, category)
VALUES ('Adam', 123433, 'new'),
('John', 222222, 'new'),
('Samuel', 123123313, 'new')
-- SELECT * FROM #MainTable
-- SELECT * FROM #Queue
-- SELECT * FROM #Test
DECLARE Cursor_test CURSOR LOCAL FOR
SELECT id
FROM #MainTable
-- get relationships for next level
OPEN Cursor_test
FETCH NEXT FROM Cursor_test INTO #ParentId
WHILE ##FETCH_STATUS = 0
BEGIN
IF OBJECT_ID('tempdb..#Queue') IS NOT NULL
DROP TABLE #Queue
CREATE TABLE #Queue
(
PK INT NOT NULL PRIMARY KEY identity (3,2),
ID INT NOT NULL
)
INSERT INTO #Queue (id)
SELECT id
FROM #MainTable
SET #QueueId = SCOPE_IDENTITY()
-- real-time creation
EXEC dbo.sp_Testing #QueueId, #MainId, #MessageTx OUT, #SuccessIn OUT
FETCH NEXT FROM Cursor_test INTO #ParentId
END
CLOSE Cursor_test
DEALLOCATE Cursor_test

This is a bit too long to be in the comment.
Firstly you must understand that the temp table #test only exists within the stored procedure. It is created in your stored procedure and dropped once the stored procedure exits.
So every time you execute the stored procedure, it creates the temp table, when you insert the row, it return the same identity seed value which is 5.

Related

Adding column with default values to temp table when using two stored procedures to populate the temp table

I have a stored procedure that executes two stored procedures and gets the correct data, but now I need to add a column into the temp table with default values.
e.g I want to add a Location type column to the temp table when the first stored procedure executes i want to add 1 to each record and when the second stored procedure is added to the temp table I want to add 2 to this column for each record. Can this be achieved?
Nothing much more
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[uspStockAdjustmentGetStorageLocationTESTDELETEAFTER]
#CategoryId INT,
#ReasonId INT
AS
SET NOCOUNT ON
DECLARE #LocationType INT
SELECT #LocationType = LocationType
FROM [dbo].[StockAdjustmentReasonGLAccount]
WHERE CategoryId = #CategoryId AND ReasonId = #ReasonId
IF(#LocationType = 1)
BEGIN
CREATE TABLE #tmp
(
ID INT IDENTITY(1, 1),
CODE NVARCHAR(50),
Description NVARCHAR(50)
)
INSERT INTO #tmp
EXECUTE dbo.uspStockAdjustmentWorkCentreSelectAll
INSERT INTO #tmp
EXECUTE dbo.uspStockAdjustmentGetSAPStorageType
SELECT
Code, MIN(id) AS Id
FROM
#tmp
GROUP BY
CODE
ORDER BY
Id
DROP TABLE #tmp
END
I am not sure on how to add this column with the default values
create the temp table with the new column with default value as 2
CREATE TABLE #tmp
(
ID INT IDENTITY(1,1) ,
CODE nvarchar(50),
Description nvarchar(50),
LocationType INT default 2
)
specify the column explicitly when insert into the temp table.
INSERT INTO #tmp (CODE, Description)
EXECUTE dbo.uspStockAdjustmentWorkCentreSelectAll
change the LocationType to 1 after first stored procedure executes
UPDATE #tmp
SET LocationType = 1
INSERT INTO #tmp (CODE, Description)
EXECUTE dbo.uspStockAdjustmentGetSAPStorageType
result from second stored procedure will have the value 2

Historical Table in SQL Server

I have two tables; the first named PAYMENT and the second is a historical table named RecordPay.
I have two triggers, the first one is for insert in order to insert into the historical tables records from Payment table.
Here is the code:
ALTER TRIGGER [dbo].[INSERT_HIST]
ON [dbo].[PAYMENT]
FOR INSERT
AS
BEGIN
DECLARE #User_op varchar(50)
DECLARE #RGNO varchar(50)
DECLARE #PAYEUR varchar(50)
DECLARE #DATESYS SMALLDATETIME
DECLARE #RG_DATE SMALLDATETIME
DECLARE #RG_Montant varchar(50)
SELECT #User_op = cbUserName
FROM cbUserSession
WHERE cbSession = ##SPID
SELECT #PAYEUR = CT_NumPayeur FROM INSERTED
SELECT #DATESYS = GETDATE()
SELECT #RG_Montant = RG_Montant FROM INSERTED
SELECT #RG_DATE = RG_DATE FROM INSERTED
SELECT #RGNO = RG_No FROM INSERTED
INSERT INTO RecordPay (RG_NO, PAYEUR, CAISSIER, Montant, DATESYS, DATECAI)
VALUES (#RGNO, #PAYEUR, #user_op, #RG_Montant, #DATESYS, #RG_DATE)
This works well, my problem when I delete a row from PAYMENT, in RecordPay the record exists, and then when I insert another row in PAYMENT I had two RG_NO whith the same number.
For example I insert a row in PAYMENT with RG_NO=1 then I deleted, and I create another row with RG_NO=2, in the recordPay (historical table) i get two lines with RG_NO=1.
Here is the trigger for delete but it does not work
ALTER TRIGGER [dbo].[DEL_HIST]
ON [dbo].[PAYMENT]
AFTER DELETE
AS
BEGIN
DECLARE #User_op varchar(50)
DECLARE #RGNO varchar(50)
DECLARE #PAYEUR varchar(50)
DECLARE #DATESYS SMALLDATETIME
DECLARE #RG_DATE SMALLDATETIME
DECLARE #RG_Montant varchar(50)
SELECT #PAYEUR = CT_NumPayeur FROM DELETED
SELECT #RG_Montant = RG_Montant FROM DELETED
SELECT #RG_DATE = RG_DATE FROM DELETED
SELECT #RGNO = RG_No FROM DELETED
DELETE FROM RECORDPAY WHERE
RG_NO=#RGNO and PAYEUR= #PAYEUR and CAISSIER=#user_op and Montant=#RG_Montant
END
Your trigger will BREAK as soon as an INSERT statement inserts more than 1 row at a time - because in that case, your trigger gets called once for the INSERT statement, and Inserted will contain multiple rows.
Which one of those 10 rows are you selecting from here??
SELECT #PAYEUR = CT_NumPayeur FROM INSERTED
SELECT #RG_Montant = RG_Montant FROM INSERTED
SELECT #RG_DATE = RG_DATE FROM INSERTED
SELECT #RGNO = RG_No FROM INSERTED
It's arbitrary and non-deterministic - and you will simply ignore all other rows in Inserted.
You need to rewrite your trigger to take this into account:
ALTER TRIGGER [dbo].[INSERT_HIST]
ON [dbo].[PAYMENT]
FOR INSERT
AS
BEGIN
DECLARE #User_op varchar(50)
SELECT #User_op = cbUserName
FROM cbUserSession
WHERE cbSession = ##SPID
-- insert a record for ALL the rows that were inserted into
-- your history table in a single, elegant, set-based statement
INSERT INTO RecordPay (RG_NO, PAYEUR, CAISSIER, Montant, DATESYS, DATECAI)
SELECT
RG_No, CT_NumPayeur, #User_op, RG_Montant, SYSDATETIME(), RG_Date
FROM
Inserted

Stored procedure inserting values into a table using Table-Valued Parameters

Given the following simple structure:
TABLE: Product (ProductId, ProductName)
TABLE: Category (CategoryId, CategoryName)
LINK TABLE: ProductId,CategoryId
I have a table type which I want to pass to a stored procedure to insert the values into another table if they don't exist.
CREATE TYPE StringList_TBLType AS TABLE (s NVARCHAR(255) NOT NULL PRIMARY KEY)
I want to do the following in a stored procedure where I pass in a ProductName, and the StringList_TBLType of Category Names
select all the strings from my StringList_TBLType
Insert the string into the Category TABLE if it does not exist
Get the ID of the inserted or already existing Category
Insert the ProductId, and CategoryId into the LINK TABLE.
I could probably struggle along and get something working, but I have little experience with MS SQL, and stored procedures in general, and am scared that I would end up writing a very inefficient way of doing this.
You can use the MERGE statement to capture the category IDs.
DECLARE #changes TABLE (ChangeID VARCHAR(10), Id INTEGER);
DECLARE #JustSomeRandomVariable INT;
MERGE Category AS TARGET
USING #data AS SOURCE
ON TARGET.Category = SOURCE.s
WHEN NOT MATCHED THEN
INSERT ([Category])
VALUES (SOURCE.s)
WHEN MATCHED THEN
UPDATE SET #JustSomeRandomVariable = 1
OUTPUT $action, inserted.Id INTO #changes;
The random variable in the merge statement makes sure that updates get logged into the #changes table variable.
Now you can use the #changes to update your link table.
INSERT INTO Link SELECT ProductID, ChangeID FROM #changes
Just retrieve the required ProductID with a simple select query.
EDIT:
This could potentially result in double records in the Link table. You might need to tweak it a bit, perhaps use the MERGE statement for inserting into the Link table aswell.
#data is the StringList_TBLType paramter of your procedure.
This is what I would suggest (not tested)
CREATE PROCEDURE AddProductToCategories
#productname nvarchar(255),
#categories StringList_TBLType READONLY
AS
BEGIN
DECLARE #productId bigint --change to datatype of Product.ProductId
SET #productId = (SELECT TOP 1 ProductId FROM Product WHERE ProductName = #productname) --What to do if your product names are not unique?
IF #productId is not NULL
BEGIN
DECLARE cur CURSOR FOR (
SELECT cat.CategoryName, cat.CategoryId, c.s
FROM #categories c
RIGHT OUTER JOIN Category cat on c.s = cat.CategoryName
)
DECLARE #id as bigint --change to datatype of Category.CategoryId
DECLARE #name as nvarchar(255) --change to datatype of Category.CategoryName
DECLARE #categoryNameToAdd as nvarchar(255)
OPEN cur
FETCH NEXT FROM cur INTO #name, #id, #categoryNameToAdd
WHILE ##FETCH_STATUS = 0
BEGIN
IF #id is NULL
BEGIN
--category does not exist yet in table Category
INSERT INTO Category (CategoryName) VALUES (#categoryNameToAdd)
SET #id = SCOPE_IDENTITY()
END
INSERT INTO ProductsCategories --your link table
(ProductId, CategoryId)
VALUES
(#productId, #id)
FETCH NEXT FROM cur INTO #name, #id, #categoryNameToAdd
END --cursor
CLOSE cur
DEALLOCATE cur
END --IF #productId
END --sproc

SQL Server understand SCOPE_IDENTITY()

I have this piece of code in a stored procedure:
BEGIN
SET #UserId = NULL;
IF (#Username IS NOT NULL)
BEGIN
EXECUTE SP_ADD_USER #Username, #UserId OUTPUT;
END
EXECUTE SP_ADD_ALERT #Name, #AlertType, #AlertId OUTPUT;
INSERT INTO AlertLogs (Datastamp, AlertID, UserID, NotificationMessage)
VALUES (#Datastamp, #AlertId, #UserId, #EmailMessage);
SET #AlertLogId = SCOPE_IDENTITY();
END
#AlertLogId is an output parameter that I want to be assigned to the result of the last insert in AlertLogs table. Do I have to include
INSERT INTO AlertLogs (Datastamp, AlertID, UserID, NotificationMessage)
VALUES (#Datastamp, #AlertId, #UserId, #EmailMessage);
in a new block (a new begin/end scope) in order for SCOPE_IDENTITY() to work correctly ?
(and not report for example the last ID of an inserted record done in SP_ADD_ALERT for example ?)
In your query, SCOPE_IDENTITY() is going to return the last entered identity value into the database, for this scope.
In this instance, it will be the identity for the AlertLogs table, if this has an identity.
A scope is a module: a stored procedure, trigger, function, or batch.
Therefore, two statements are in the same scope if they are in the
same stored procedure, function, or batch.
http://msdn.microsoft.com/en-us/library/ms190315.aspx
You can also use an OUTPUT clause in your insert statement. This means you don't need to worry about scope and you make other (non-identity) information available from the inserted table.
Consider this simple table:
CREATE TABLE [dbo].[SampleTable](
[ID] [int] IDENTITY(1,1) NOT NULL,
[InsertDate] [datetime] NOT NULL,
[Name] [nvarchar](100) NULL
) ON [PRIMARY]
With this default added:
ALTER TABLE [dbo].[SampleTable]
ADD CONSTRAINT [DF_SampleTable_Inserted]
DEFAULT (getdate()) FOR [InsertDate]
You can get values for both the default and the identity from the insert operation.
DECLARE #InsertedDetails TABLE (ID int, InsertDate DateTime);
INSERT INTO SampleTable ([Name])
OUTPUT inserted.ID, inserted.InsertDate
INTO #InsertedDetails
VALUES ('Fred');
DECLARE #ID int;
DECLARE #InsertDate datetime;
SELECT #ID = ID, #InsertDate = InsertDate FROM #InsertedDetails;
PRINT #ID;
PRINT #InsertDate;
Here I've just pulled the values out of the table variable and printed them.

How to insert values in to 2 tables, where the 2nd table need an Id from the 1st table?

I have an ordering system where when a new order is placed it is inserted into my table Orders. From there I want to insert the new id into another table Importance which also needs an id from a third table called ImportanceRating.
Table structures:
Order
OrderId uniqueidentifier
TimeOrderPlaced datetime
ProductId uniqueidentifier
EstimatedDeliveryTime datetime
Importance
FK_OrderId uniqueidentifier
FK_ImpRatingId uniqueidentifier
ImportanceRating
ImpRatingId uniqueidentifier
RatingTitle varchar(50)
All of this I want merged in 1 stored procedure. How would I go about with this?
Links to good guides on the subject is more than welcome.
I'm a SPROC newbie
Could you try this?:
CREATE PROCEDURE AddOrderAndRatingSample
-- These are the values you want to insert
#paramTimeOrderPlaced DATETIME
, #paramProductId INT
, #paramEstimatedDeliveryTime DATETIME
, #paramRatingTitle VARCHAR(50)
AS
BEGIN
DECLARE #siOrderId INT
DECLARE #siImpRatingId INT
-- Assuming that `OrderId` in table `Order` is an `identity column`:
INSERT INTO Order (TimeOrderPlaced, ProductId, EstimatedDeliveryTime)
VALUES(#paramTimeOrderPlaced, #paramProductId, #paramEstimatedDeliveryTime)
SET #siOrderId = SCOPE_IDENTITY()
-- Assuming `ImpRatingId` in table `ImportanceRating` is an `identity column`:
INSERT INTO ImportanceRating (RatingTitle)
VALUES(#paramRatingTitle)
SET #siImpRatingId = SCOPE_IDENTITY()
-- And that both `FK_OrderId` and `FK_ImpRatingId`
-- in table `Importance` are not `identity columns`:
INSERT INTO Importance (FK_OrderId, FK_ImpRatingId)
SELECT #siOrderId, #siImpRatingId
END
Could you please try this way:
DECLARE #OrderId INT
INSERT INTO Order (TimeOrderPlaced, ProductId, EstimatedDeliveryTime)
VALUES(#paramTimeOrderPlaced, #paramProductId, #paramEstimatedDeliveryTime)
SET #OrderId = ##IDENTITY -- Last orderId
INSERT INTO ImportanceRating (RatingTitle)
VALUES(#paramRatingTitle)
INSERT INTO Importance (FK_OrderId, FK_ImpRatingId)
SELECT #OrderId, ##IDENTITY -- Here ##IDENTITY returns last ID of ImportanceRating
-- Each inserting time the global variable ##IDENTITY is set with last IDENTITY value

Resources