INSERT Auto Incremented Primary Key from TableA into FK of TableB - sql-server

I have two tables: TableA and TableB
CREATE TABLE TableA (
[TableAId] [int] IDENTITY(1,1) NOT NULL...
CREATE TABLE TableB (
[TableBId] [int] IDENTITY(1,1) NOT NULL,
[TableAID] [int] NOT NULL... -- this is the FK
Question: (as a C# developer)
I am writing a function that INSERTs into TableA. I need to grab the newly created primary key in TableA and insert it into the FK of TableB along with other data.
I've done this before, but I didn't like what I did which is lookup the TableA PK value immediately after the insert, store it as a variable and then insert it into TableB.
Can someone show me a more efficient way of doing this? Maybe using scope_identity() in a stored proc? A trigger won't work because I need the new PK back in my C# so I can add additional data before I insert into TableB. Plus, I want to lock both tables while this runs.
Thank you,
Robert

Declare #TableAId int
Insert TableA ( ... )
Values( ... )
Set #TableAId = Scope_Identity();
Insert TableB( TableAId, ... )
Values( #TableAId, ... )
It should be noted that it is possible to send this to SQL Server in a single batch. I.e., you can send this entire command text to SQL Server and execute all at once. Of course, you'll need to use parameters for all non-identity columns of table A and table B. For example:
const string sql = #"Declare #TableAId int
Insert TableA ( ... )
Values( ... )
Set #TableAId = Scope_Identity();
Insert TableB( TableAId, ... )
Values( #TableAId, ... )";
using ( var conn = new SqlConnection( ... ) )
{
using ( var cmd = new SqlCommand( sql, conn ) )
{
cmd.Parameters.AddWithValue( #TableACol, ... );
cmd.Parameters.AddWithValue( #TableACol2, ... );
...
cmd.Parameters.AddWithValue( #TableBCol, ... );
cmd.ExecuteNonQuery();
}
}
Another choice if you are using SQL Server 2005 or later might be to use the OUTPUT clause:
Insert TableA( ... )
Output Inserted.TableAId, #TableBCol1, #TableBCol2, ...
Into TableB
Values( ... )

If you have one stored procedure are inserting both sets of data at the same time then use this code.
DECLARE #id INT
INSERT INTO [dbo].[TableA] VALUES (...)
SET #id = SCOPE_IDENTITY()
INSERT INTO [dbo].[TableB] VALUES (#id ...)
If you are using raw ADO, then set #id as an OUTPUT parameter and use the return value in your second DB call. Refer to here for an example of how to use an output parameter.
As for locking both tables, you can either wrap the call in a transaction (TSQL or ADO.NET) or set the SET TRANSACTION ISOLATION LEVEL SERIALIZABLE for the stored procedure.

This worked...
USE [TestDB01]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[sp_INSERT_INTO_Cars_And_Owners]
-- Add the parameters for the stored procedure here
#PartnerId INT
, #Title NVARCHAR(100)
, #Description TEXT
, #IPAddress NVARCHAR(16)
, #IsCarOwner BIT
, #OwnerTypeTypeID INT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #CarId INT
INSERT INTO [dbo].[Cars] (
PartnerId
, Title
, Description
, IPAddress
, IsCarOwner
)
VALUES (
#PartnerId
, #Title
, #Description
, #IPAddress
, #IsCarOwner
)
SET #CarId = SCOPE_IDENTITY()
INSERT INTO [dbo].[Owners] (
OwnerTypeTypeID
, CarId
)
VALUES (
#OwnerTypeTypeID
, #CarId
)
END

Related

CURSOR with a stored procedure with OUTPUT parameters

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.

Create a SQL trigger to record the literal, textual query itself

I'd like to log all DELETE and UPDATE queries made on a particular table (no need to log INSERT operations), and record them in a different table.
I understand there are numerous tutorials and references demonstrating how to capture and log various fields (eg https://www.w3resource.com/sqlite/sqlite-triggers.php) , but I'm actually interested in the literal query string itself before it is parsed into a tabular format, eg, to record:
|kittenLogID| kittenID| query |
|-----------|---------|--------------------------------------|
|1 |1 |"DELETE from Kittens WHERE kittenID=1"|
I'm interested in doing this in both sqlite3 syntax as well as how it differs in the trigger syntax of MS SQL.
In sqlite3, I expect something like this:
CREATE TRIGGER kittenLog
AFTER DELETE, UPDATE
On Kittens
BEGIN
INSERT INTO kittenLog (kittenID, MY_QUERY) --how do I get this MY_QUERY thing I made up?
SELECT new.kittenID as kittenID, MY_QUERY
FROM new
WHERE new.kittenID = old.kittenID
END
END
In MS SQL, I would expect something like this:
CREATE TRIGGER kittenLog_delete
AFTER DELETE
On Kittens
DECLARE #data XML;
SET #data = EVENTDATA();
BEGIN
INSERT kittenLog (kittenID, query)
VALUES (d.kittenID, #data.value('(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]','nvarchar(max)') as MY_QUERY)
FROM deleted d
END
END
CREATE TRIGGER kittenLog_update
AFTER UPDATE
On Kittens
DECLARE #data XML;
SET #data = EVENTDATA();
BEGIN
INSERT kittenLog (kittenID, query)
VALUES (d.kittenID, #data.value('(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]','nvarchar(max)') as MY_QUERY)
FROM inserted d
END
END
It is not clear to me what is in this EVENTDATA() object and how to rephrase this, but I think it is the key in the latter.
If helpful, my Kitten Tables look like this:
CREATE TABLE Kittens (
kittenID INTEGER PRIMARY KEY AUTOINCREMENT,
human_name text,
);
CREATE TABLE kittenLog (
kittenLogID INTEGER PRIMARY KEY AUTOINCREMENT,
kittenID INTEGER,
query text,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
);
This solution is for MS SQL. Do not use text datatype is deprecated and should not be used. Here are scripts
CREATE TABLE Kittens (
kittenID INT PRIMARY KEY identity(1,1)
, human_name varchar(8000) --use varchar(max) if you think that 8000 is not enough for you
);
CREATE TABLE kittenLog (
kittenLogID INTEGER PRIMARY KEY identity(1,1),
kittenID INT,
query varchar(8000),
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
);
create trigger kittenLog_audit
on Kittens
after update, delete
as
declare #kittenID int
declare #execStr varchar(100)
declare #query varchar(8000)
if exists (select 1 from deleted) --either delete or update
begin
select #kittenID = kittenID from deleted
declare #inputbuffer table (
EventType nvarchar(30)
, Parameters int
, EventInfo nvarchar(255)
)
set #execStr = 'dbcc inputbuffer(' + str(##SPID) + ')'
insert into #inputbuffer
exec (#ExecStr)
set #query = (select EventInfo from #inputbuffer)
insert into kittenLog (kittenID, query)
values (#kittenID, #query)
end

Create procedure that will insert or update data from one temporary table to other 4 tables

I need to
Create procedure that will
insert or update data from the source table into the target1 table
Insert data into target2 table AND will get inserted ID
from target1, target2 tables
Insert them into target table3
Insert target1.ID into target4 table
the source table is a temporary table, and I use bulk copy to insert big data into that table and then move data to target tables for performance.
MSPCompanies - insert or update по признаку INN
MSPDownloads insert
MSPAccepted insert MSPCompanies.ID и MSPDownloads.ID
MSPAcceptedNow insert MSPCompanies.ID
insert or update in MSPCompanies I have done with MERGE
BEGIN
BEGIN TRY
BEGIN TRANSACTION
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
MERGE INTO [MSPCompanies] AS [Target]
USING [MSPTempTable] AS [Source]
ON Target.INN = Source.INN
WHEN MATCHED THEN
UPDATE SET
Target.Name = Source.Name,
Target.ShortName = Source.ShortName,
Target.FIO = Source.FIO,
Target.SubjectType = Source.SubjectType,
Target.SubjectCategory = Source.SubjectCategory,
#MSPCompaniesId = Target.MSP_Companies_ID
WHEN NOT MATCHED THEN
INSERT
(INN, Name, ShortName, FIO, SubjectType, SubjectCategory)
VALUES
(Source.INN, Source.Name, Source.ShortName, Source.FIO, Source.SubjectType, Source.SubjectCategory);
IF #MSPCompaniesId IS NULL
BEGIN
SET #MSPCompaniesId = CAST(SCOPE_IDENTITY() as [int]);
END
SELECT #MSPCompaniesId
--BEGIN
INSERT INTO dbo.MSPAcceptedNow
(MSP_Company_ID)
VALUES(#MSPCompaniesId)
but after it seems that can't do other operations with oterh three tables, and trying to do it with insert - update, but anyway can't do that.
BEGIN TRANSACTION MoveDataFromMSPTempTableT;
UPDATE t
SET
Name = s.Name,
ShortName = s.ShortName,
FIO = s.FIO,
SubjectType = s.SubjectType,
SubjectCategory = s.SubjectCategory
FROM MSPCompanies t
JOIN MSPTempTable s
ON s.INN = t.INN;
INSERT INTO MSPCompanies
SELECT s.INN,
s.Name,
s.ShortName,
s.FIO,
s.SubjectType,
s.SubjectCategory
FROM MSPTempTable s
LEFT JOIN MSPCompanies t
ON s.INN = t.INN
WHERE t.INN IS NULL;
COMMIT TRANSACTION MoveDataFromMSPTempTableT;
Thank you.
You cannot use a variable to track all the changes, but you track all the changes with OUTPUT.
if object_id('dbo.[MSPCompanies]') is not null drop table dbo.[MSPCompanies];
if object_id('dbo.[MSPTempTable]') is not null drop table dbo.[MSPTempTable];
create table dbo.[MSPTempTable]
(
MSP_Companies_ID int identity(1,1),
INN varchar(5) primary key clustered,
[Name] varchar(20),
ShortName varchar(20),
FIO varchar(20),
SubjectType varchar(20),
SubjectCategory varchar(20)
);
insert into dbo.[MSPTempTable] (inn,
[Name]
,ShortName
,FIO
,SubjectType
,SubjectCategory) values ('a','a','a','a','a','a'), ('a2','a2','a2','a2','a2','a2');
create table dbo.[MSPCompanies]
(
MSP_Companies_ID int identity(1,1),
INN varchar(5) primary key clustered,
[Name] varchar(20),
ShortName varchar(20),
FIO varchar(20),
SubjectType varchar(20),
SubjectCategory varchar(20)
);
insert into dbo.[MSPCompanies] (inn,
[Name]
,ShortName
,FIO
,SubjectType
,SubjectCategory) values ('a','a','a','a','a','a');
begin tran
set xact_abort on;
SET NOCOUNT ON;
DECLARE #SummaryOfChanges TABLE(
CompanyID int,
Change VARCHAR(20)
);
MERGE INTO [MSPCompanies] AS [Target]
USING [MSPTempTable] AS [Source]
ON Target.MSP_Companies_ID = Source.MSP_Companies_ID
WHEN MATCHED THEN
UPDATE SET
Target.Name = Source.Name,
Target.ShortName = Source.ShortName,
Target.FIO = Source.FIO,
Target.SubjectType = Source.SubjectType,
Target.SubjectCategory = Source.SubjectCategory
WHEN NOT MATCHED THEN
INSERT (INN, Name, ShortName, FIO, SubjectType, SubjectCategory)
VALUES (Source.INN, Source.Name, Source.ShortName, Source.FIO, Source.SubjectType, Source.SubjectCategory)
OUTPUT inserted.MSP_Companies_ID, $action INTO #SummaryOfChanges;
SELECT * from dbo.[MSPCompanies]
SELECT * from #SummaryOfChanges;
if ##TRANCOUNT > 0
rollback tran;

Multiple Row Param String to Single Stored Procedure

I have a stored procedure that mimics the MYSQL 'UPSERT' command. ie. insert if new / update existing if record exists.
I wish to keep the number of calls to SQL Server to an absolute minimum ie. 1
So can I pass an param string to a stored procedure (SP_MAIN) and in this stored procedure then call my 'UPSERT' stored procedure for every unique table row that is passed as a param to SP_MAIN...?
If so, can anyone illustrate with a simple example please..?
Thank you in advance.
You can use the merge statements. See a sample below: The table to be updated is dbo.Table. We use Table Valued Parameter to update/insert the data. The merge statement is within a stored procedure
CREATE TABLE dbo.[Table]
(
PrimaryKey INT IDENTITY (1, 1) NOT NULL
,Column1 INT NOT NULL
,Column2 INT NOT NULL
)
GO
CREATE TYPE dbo.[TableTVP] AS TABLE (
PrimaryKey INT NULL
,Column1 INT NULL
,Column2 INT NULL
)
GO
CREATE PROCEDURE dbo.CRUD_Table
#TableTVP dbo.TableTVP READONLY
AS
SET NOCOUNT ON
DECLARE #OutPut TABLE (Action VARCHAR(10) NULL,EntityKey INT NULL)
MERGE dbo.[Table] AS TARGET
USING (SELECT
PrimaryKey
,Column1
,Column2
,BINARY_CHECKSUM (Column1, Column2) as DataCheckSum
FROM
#TableTVP) AS SOURCE ON SOURCE.PrimaryKey = TARGET.PrimaryKey
WHEN MATCHED AND SOURCE.DataCheckSum <> BINARY_CHECKSUM (TARGET.Column1, TARGET.Column2) THEN
UPDATE SET
Column1 = SOURCE.Column1
,Column2 = SOURCE.Column2
WHEN NOT MATCHED THEN
INSERT (
Column1
,Column2
)
VALUES (
SOURCE.Column1
,SOURCE.Column2
)
OUTPUT $action as [Action]
,CASE WHEN $action IN ('INSERT', 'UPDATE') THEN Inserted.PrimaryKey ELSE Deleted.PrimaryKey END as [EntityKey] INTO #OutPut;
SELECT Action,EntityKey FROM #OutPut
GO

SQL need to insert into table which only consists of a primary key but no auto increment

I need to insert a value into a table which only consists of one column, that is, the primary key.
Furthermore, NULL is not allowed, Identity is set to FALSE and both Identity Seed and Identity Increment are set to 0.
I try to insert with INSERT INTO table(id) VALUES (null) which obviously does not work. INSERT INTO table(id) default values also does not work.
How can I fill this column with the correctly incremented ID?
Implementing Identity or Sequence would be the best solution, but if you really cannot alter the schema the alternative is to lock the table in a transaction, create the new value, unlock the table. Note this can have performance consequences.
create table dbo.ids ( id int primary key clustered );
GO
insert dbo.ids values ( 1 ), ( 2 ), ( 3 ), ( 4 ) ;
GO
declare #newid int;
begin transaction
set #newid = ( select top( 1 ) id from dbo.ids with ( tablockx, holdlock ) order by id desc ) + 1 ;
insert into dbo.ids values ( #newid );
select #newid;
commit
GO 20
You can use while function in that insert
declare #id int
select #id = max(id) from table
while #id <= (... put here max nuber of your id you want to insert)
begin
insert into table values (#id)
set #id = #id+1 end
end
This can be a solution too.
declare #newid integer
begin tran
select #newid = isnull(max(id), 0) + 1 from table with (xlock,holdlock)
insert into table values(#newid)
select #newid
commit tran

Resources