SQL Insert along with matching audit at same time from app - sql-server

I've got an app that will insert lines to table [P_R], which has first field being [PrimaryKey].
Now I have to add another table, [Actions] with fields [PrimaryKey],[P_R_PK],[User],[ActionTime].
When the app inserts a line to [P_R], I don't know what the PrimaryKey will be, but I have to simultaneously insert to [Actions] with the value in [P_R_PK] being the PrimaryKey I just added to [P_R]. How do I get this PrimaryKey value to [P_R_PK]?
For reference, I'm using a vb.net windows form with a SQL Server database.

If you're using a stored procedure to add the records to [P_R], you can call another stored procedure in the first that includes the primary key. For example:
CREATE PROC AddToP_R
#field1 varchar(10),
#field2...10
AS
BEGIN
declare #pk int --primary key that's created upon inserting
--insert into [P_R]
INSERT INTO [P_R]
VALUES (#field1,...)
--set the var we created to be the primary key
SET #pk = SCOPE_IDENTITY()
--call second proc
EXEC Second_Proc #pk
END
If you need other fields in the second stored procedure, include them in the first procedure parameter list.
Another way would be to a wrapper stored procedure that calls both the other two. For this to work, you would need an output variable in the first procedure to return the primary key. For example:
CREATE PROC AddWrapper
#fieldsforfirstproc...,
#fieldsforsecondproc...
AS
BEGIN
declare #outputVar int --primary key
EXEC firstproc #fieldsforfirstproc..., #outputvar output --adds the record to the first table and returns #outputvar as the primary key
EXEC secondproc #fieldsforsecondproc..., #outputvar --adds the record to the second table using #output var
END
I prefer the second option because it removes logic from the first procedure that doesn't need to be there. However, the first procedure would be slightly different to how I showed earlier.
CREATE PROC AddToP_R
#field1 varchar(10),
#field2...10,
#pk int OUTPUT --primary key that's created upon inserting
AS
BEGIN
--insert into [P_R]
INSERT INTO [P_R]
VALUES (#field1,...)
--set the var we created to be the primary key
SET #pk = SCOPE_IDENTITY()
END

You can retrieve it by using SELECT SCOPE_IDENTITY() after the INSERT.
For example:
DECLARE #T table (id int PRIMARY KEY IDENTITY, value int)
INSERT INTO #T (value) VALUES (2)
SELECT SCOPE_IDENTITY() new_pk
I would also consider doing it all in one stored procedure within a transaction. Given that you are inserting into more than one table, a transaction would allow you to roll back should anything go wrong.

Related

How do find the last added ID in a non auto incremented table?

How do I get the ID of the last added element in the? From my searching on the web I found that you can use ##IDENTITY and IDENTITY_SCOPE() but only while adding them/being in the. I tried the following but it isn't working
CREATE TABLE Products
(PT_ID int PRIMARY KEY, Name nvarchar(20))
CREATE TABLE Storage(ST_ID int Primary key,Info nvarchar(20))
CREATE TABLE ProductStorageMM
(ST_ID int CONSTRAINT S_FK FOREIGN KEY REFERENCES Storage(ST_ID ),
PT_ID int CONSTRAINT P_FK FOREIGN KEY REFERENCES Products(PT_ID ),
Status int not null,
PRIMARY KEY (ST_ID ,PT_ID )
)
The tables above are just for the experiments sake.I am trying to, when adding a value into the Product table automatically set all the values of the given product in The storages to zero.
CREATE PROCEDURE AddingProduct
(#PID int ,#NAME nvarchar(20))
AS BEGIN
INSERT INTO Products(PT_ID,Name)
VALUES(#PID,#NAME)
INSERT INTO PSKT (PT_ID,ST_ID,Status)
SELECT PTT.PT_ID ,STT.ST_ID,0
FROM (SELECT * FROM Storage) AS STT,
(SELECT * FROM Products WHERE PT_ID=SCOPE_IDENTITY()) AS PTT;
END
The Procedure doesn't work.What am I doing wrong?
Am I missing something? Isn't #PID the last identity used? ##IDENTITY and SCOPE_IDENTITY are used for auto-increment identities. You're not showing auto-increment here, you're passing the identity in to the procedure. This inserts a row into PSKT for the #PID passed in with every row in STORAGE.
--> Given code where identity is passed in through a parameter
CREATE PROCEDURE AddingProduct
(#PID int ,#NAME nvarchar(20))
AS BEGIN
INSERT INTO Products(PT_ID,Name)
VALUES(#PID, #NAME)
INSERT INTO PSKT (PT_ID,ST_ID,Status)
SELECT #PID ,STT.ST_ID,0
FROM Storage STT
END
--> Example using auto-increment and ##identity
CREATE PROCEDURE AddingProduct
(#NAME nvarchar(20))
AS BEGIN
INSERT INTO Products(Name) --< identity is inserted through auto-increment
VALUES(#NAME)
set #pid = ##identity --< we need to get the identity created in the previous step for the next step.
INSERT INTO PSKT (PT_ID,ST_ID,Status)
SELECT #PID ,STT.ST_ID,0
FROM Storage STT
END

Altering stored procedure that ALTERs a table

I have a procedure that does the following in order
Create table with a single column
Inserts data in the table
Adds more columns to the table
After the first execution of the SP, the table already exists. Hence, if I make changes in the SP and try to save it, it throws an error at the Insert step saying
"Column name or number of supplied values does not match table definition."
Is there any way to disable this 'check' and somehow update the SP without having to drop the table?
EDIT: This SP is supposed to run only once a year, to generate a report. Nobody looks at it until next year. Actually, there is a set of 5-6 stored procedures that generates all the tables (about 25) which are then used to make a report. So, when a change is made in the SP, the tables are dropped and all the SP are run once again.
EXAMPLE:
CREATE proc sp_temp
AS
BEGIN
BEGIN TRY DROP TABLE TX END TRY BEGIN CATCH END CATCH
CREATE TABLE TX (ID INT)
DECLARE #I INT =0
WHILE (#I<=10)
BEGIN
INSERT INTO TX VALUES (#I)
SET #I += 1
END
ALTER TABLE TX ADD COL1 VARCHAR(10)
INSERT INTO TX VALUES (11, 'SOME TEXT');
END
EDIT2: Table is dropped before creation.
But now my question is: SSMS identifies the error ie. mismatch in number of columns and number of values supplied but why can't it see that the table is dropped?
The answer is "no". If you really must do it this way, then I suggest using dynamic SQL, and executing it.
This definitely sounds like poor design.
ALTER proc [dbo].[sp_temp]
AS
BEGIN
CREATE TABLE TX (ID INT)
DECLARE #I INT =0
WHILE (#I<=10)
BEGIN
INSERT INTO TX(ID) VALUES (#I)
SET #I += 1
END
ALTER TABLE TX ADD COL1 VARCHAR(10)
INSERT INTO TX VALUES (11, 'SOME TEXT');
END

Create Unique ID in Stored Procedure to Match Legacy Data

I'm creating CRUD procedures that duplicate a legacy program that generates a unique ID based on a 'Next ID' field in a separate table. Rather than duplicate the use of a separate table I have written a stored procedure that reads the number of rows in the table.
CREATE PROCEDURE [TLA_CreateItem]
#SiteReference varchar(50)
,#ItemID varchar(4)
,#NewUniqueID varchar(68) OUTPUT
AS
BEGIN
DECLARE #Rows varchar(12)
SET #Rows = (CONVERT(varchar(12), (SELECT Count(UniqueID) FROM [TLA_Items]) + 1))
SET #NewUniqueID = #ItemID + #SiteReference + #Rows
INSERT INTO [TLA_Items] ([ItemID], [UniqueID])
VALUES (#ItemID, #NewUniqueID)
SELECT #NewUniqueID
END
I've simplified the code above but what's not shown is that the TLA_Items table also has an IDENTITY column and that it needs to work with SQL Server 2008.
The UniqueID field has to match the pattern of the legacy program: ItemID + SiteReference + (integer representing number of previous records)
However when testing this I've found a flaw in my logic. If rows are deleted then it's possible to create a unique Id which matches an existing row. This doesn't happen in the legacy system as rows are rarely deleted and the separate table stores the next number in the sequence.
Other than store the next ID value in a separate table, is there a better technique, to create a unique ID that matches the legacy pattern?
You could have your procedure store only the prefix (#ItemID + #SiteReference) into UniqueID and use a FOR INSERT trigger to append the IDENTITY value as the rows component immediately after the row is inserted, something like this:
CREATE TRIGGER TLA_Items_Adjust
ON dbo.TLA_Items
FOR INSERT
AS
BEGIN
UPDATE t
SET t.UniqueID = i.UniqueID + CAST(t.IdentityColumn AS varchar(10))
FROM dbo.TLA_Items AS t
INNER JOIN inserted AS i
ON t.IdentityColumn = i.IdentityColumn
;
END
To read and return the newly generated UniqueID value as the OUTPUT parameter as well as a row, you could use a table variable and the OUTPUT clause in the INSERT statement, like this:
CREATE PROCEDURE [TLA_CreateItem]
#SiteReference varchar(50)
,#ItemID varchar(4)
,#NewUniqueID varchar(68) OUTPUT
AS
BEGIN
DECLARE #GeneratedUniqueID TABLE (UniqueID varchar(68));
INSERT INTO dbo.[TLA_Items] ([ItemID], [UniqueID])
OUTPUT inserted.UniqueID INTO #GeneratedUniqueID (UniqueID)
VALUES (#ItemID, #ItemID + #SiteReference);
SELECT #NewUniqueID = UniqueID FROM #GeneratedUniqueID;
SELECT #NewUniqueID;
END
Although instead of using OUTPUT you could probably just read the value from the row matching the SCOPE_IDENTITY() result:
CREATE PROCEDURE [TLA_CreateItem]
#SiteReference varchar(50)
,#ItemID varchar(4)
,#NewUniqueID varchar(68) OUTPUT
AS
BEGIN
INSERT INTO dbo.[TLA_Items] ([ItemID], [UniqueID])
VALUES (#ItemID, #ItemID + #SiteReference);
SELECT #NewUniqueID = UniqueID
FROM dbo.TLA_Items
WHERE IdentityColumn = SCOPE_IDENTITY();
SELECT #NewUniqueID;
END
Here is another option, but please bear in mind that it would affect existing UniqueID values.
If you can afford a slight change to the table schema, you could add a column called something like UniqueIDPrefix:
ALTER TABLE dbo.TLA_Items
ADD UniqueIDPrefix varchar(56) NOT NULL;
and redefine the UniqueID column to be a computed column:
ALTER TABLE dbo.TLA_Items
DROP COLUMN UniqueID;
GO
ALTER TABLE dbo.TLA_Items
ADD UniqueID AS UniqueIDPrefix + CAST(IdentiyColumn AS varchar(12));
In your stored procedure, you would then need to populate UniqueIDPrefix instead of UniqueID (with just the result of #ItemID + #SiteReference)
INSERT INTO dbo.[TLA_Items] ([ItemID], [UniqueIDPrefix])
VALUES (#ItemID, #ItemID + #SiteReference);
and read the value of UniqueID using either OUTPUT or SCOPE_IDENTITY(), as in my other answer.
It sounds like you are on SQL 2008, but if you were on 2012, you could use a sequence to store an incrementing value.
How about never delete? You could add a flag to the table for logical deletes.

fail to insert into two tables via stored procedures

I'm trying to insert into two tables at the same time via stored procedure but it writes to only one table and fail to the other.
ALTER PROCEDURE [dbo].[insert_emp_pics]
#EmpName nvarchar(100),
#Nationality nvarchar(30),
#PassportPic nvarchar(100),
#Pic nvarchar(100)
AS
Begin
set nocount on;
DECLARE #ID int,
#Emp_ID int
insert into Employee (EmpName,Nationality)
values (#EmpName,#Nationality)
select #ID = ##IDENTITY
insert into DatePics
(PassportPic,Pic)
values
(#PassportPic ,#Pic)
select #Emp_ID = ##IDENTITY
end
There is relation between two tables
first table [Employee] PK ID
second table [DatePics] FK Emp_ID
this is the error message after executing this statement.
Cannot insert the value NULL into column 'Emp_ID', table 'QTecTest.dbo.DatePics';
column does not allow nulls. INSERT fails.
You need to insert the new Emp_Id as a Foreign Key to DatePics (and assuming both tables have identity columns):
insert into Employee (EmpName,Nationality)
values (#EmpName,#Nationality);
set #EMP_ID = SCOPE_IDENTITY();
insert into DatePics (PassportPic,Pic, Emp_ID)
values (#PassportPic ,#Pic, #EmpID);
set #DatePicsID = SCOPE_IDENTITY();
Also, please use SCOPE_IDENTITY over ##IDENTITY - ##Identity is vulnerable to issues where a Trigger also creates an new (unrelated) identity.
A column declared as primary key cannot have NULL values.
In your stored procedure you are not supplying value to Emp_ID column and so Insert fails.
If you want to automatically insert values in that column make it as IDENTITY column also

Using ##identity or output when inserting into SQL Server view?

(forgive me - I'm new to both StackOverflow & SQL)
Tl;dr - When using ##identity (or any other option such as scope_identity or output variable), is it possible to also use a view? Here is an example of a stored procedure using ##identity:
--SNIP--
DECLARE #AID INT
DECLARE #BID INT
INSERT INTO dbo.A (oct1)
VALUES
(#oct1)
SELECT #AID = ##IDENTITY;
INSERT INTO dbo.B (duo1)
VALUES
(#duo2)
SELECT #BID = ##IDENTITY
INSERT INTO dbo.tblAB (AID, BID)
VALUES
(#AID, #BID)
GO
Longer:
When inserting into a table, you can capture the current value of the identity seed using ##identity. This is useful if you want to insert into table A and B, capture the identity value, then insert into table AB relating A to B. Obviously this is for purposes of data normalization.
Let's say you were to abstract the DB Schema with a few that performs inner joins on your tables to make the data easier to work with. How would you populate the cross reference tables properly in that case? Can it be done the same way, if so, how?
Avoid using ##IDENTITY or SCOPE_IDENTITY() if your system is using Parallel plans as there is a nasty bug. Please refer -
http://connect.microsoft.com/SQL/feedback/ViewFeedback.aspx?FeedbackID=328811
Better way to fetch the inserted Identity ID would be to use OUTPUT clause.
CREATE TABLE tblTest
(
Sno INT IDENTITY(1,1) NOT NULL,
FirstName VARCHAR(20)
)
DECLARE #pk TABLE (ID INT)
INSERT INTO tblTest(FirstName)
OUTPUT INSERTED.Sno INTO #pk
SELECT 'sample'
SELECT * FROM #pk
EDIT:
It would work with Views as well. Please see the sample below. Hope this is what you were looking for.
CREATE VIEW v1
AS
SELECT sno, firstname FROM tbltest
GO
DECLARE #pk TABLE (ID INT)
INSERT INTO v1(FirstName)
OUTPUT INSERTED.Sno INTO #pk
SELECT 'sample'
SELECT ID FROM #pk
##IDENTITY returns the last IDENTITY value produced on a connection, regardless of the table that produced the value, and regardless of the scope of the statement that produced the value.
SCOPE_IDENTITY() returns the last IDENTITY value produced on a connection and by a statement in the same scope, regardless of the table that produced the value. SCOPE_IDENTITY(), like ##IDENTITY, will return the last identity value created in the current session, but it will also limit it to your current scope as well
Although the issue with either of these is fixed by microsoft , I would suggest you should go with "OUTPUT", and yes, it can be used with view as well

Resources