SQL Server 2012 Insert a Set and Get all PK Values? - sql-server

I have a sproc that takes a TVP, the input has 6,000 rows. The current code copies the TVP into a temp table, iterates RBAR over the temp table to insert a row on a real table, gets the Identity/PK value from the insert, updates the temp table with the PK (used later in the stored procedure), and repeats.
Is there a cool and/or quick way to insert the whole temp table and then update it after that with the PK values? Nothing straightforward is coming to mind, all ideas appreciated.
I think I can add an additional column to the temp table and put a sequence number on it so the rows are all unique and follow this idea: http://www.sqlteam.com/article/using-the-output-clause-to-capture-identity-values-on-multi-row-inserts but I'm open to other set-based suggestions....
Thanks.

I would suggest that you bulk insert from your TVP into your table, using the OUTPUT clause to populate a temporary table with your results.
Here's an example:
-- Setup
-- Create the data type
CREATE TYPE dbo.TVPtest AS TABLE
(
c1 int NOT NULL,
c2 int NULL
)
GO
CREATE TABLE tableA
(
id int primary key identity,
c1 int not null,
c2 int null
)
-- This is the example
declare #t TVPTest
declare #i int = 1000
-- get a TVP with 1000 rows
WHILE #i >= 0
BEGIN
INSERT INTO #t
VALUES (#i, ABS(CHECKSUM(NewId())) % 99)
SET #i= #i -1
END
-- logic from here is what you would put in your stored procedure
CREATE TABLE #new
(
id int,
c1 int not null,
c2 int null
)
INSERT INTO tableA (c1, c2)
OUTPUT inserted.id, inserted.c1, inserted.c2 into #new
SELECT c1, c2
FROM #t
SELECT *
FROM #new

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

Capture IDENTITY column value during insert and use as value for another column in same transaction

While performing an insert to a table which has an IDENTITY column, is it possible to use the IDENTITY value as the value for another column, in the same transaction?
For example:
DECLARE #TestTable TABLE
(
PrimaryId INT NOT NULL IDENTITY(1, 1),
SecondaryId INT NOT NULL
);
INSERT INTO #TestTable (SecondaryId)
SELECT
SCOPE_IDENTITY() + 1; -- set SecondaryId = PrimaryId + 1
SELECT * FROM #TestTable;
Expected:
| PrimaryId | SecondaryId |
+-----------+-------------+
| 1 | 2 |
I thought I might be able to achieve this with the SCOPE_IDENTITYor ##IDENTITY system functions, but unfortunately this does not work, as it is NULL at the time the transaction is executed.
Cannot insert the value NULL into column 'SecondaryId', table '#TestTable'; column does not allow nulls. INSERT fails.
I know I could use a computed column for this example, but I'm curious if what I'm trying to do is even possible.
Could you change your approach and use a SEQUENCE instead of an IDENTITY column?
CREATE SEQUENCE TestSequence
START WITH 1
INCREMENT BY 1 ;
GO
CREATE TABLE TestTable (PrimaryId INT NOT NULL DEFAULT NEXT VALUE FOR TestSequence, SecondaryId INT NOT NULL);
GO
INSERT INTO TestTable (
SecondaryId
)
SELECT NEXT VALUE FOR TestSequence + 1; -- set SecondaryId = PrimaryId + 1;
GO 3
SELECT * FROM TestTable;
GO
DROP TABLE TestTable;
DROP SEQUENCE TestSequence;
I would go with a trigger, this should also work for multi row inserts, You will need to remove the not null for SecondaryID, not sure if that's acceptable.
create trigger trg_TestTable
on dbo.TestTable
after insert
AS
BEGIN
update TestTable
set SecondaryId = i.PrimaryId
from inserted i
join TestTable a
on i.PrimaryId = a.PrimaryId;
END
GO
One thing you could do is use the OUTPUT INSERTED option of the INSERT COMMAND to capture the IDENTITY.
In this example the IDENTITY field is ScheduleID.
CREATE PROCEDURE dbo.spScheduleInsert
( #CustomerID int,
#ItemID int,
#StartDate Date,
#TimeIn DateTime,
#TimeOut DateTime,
#ReturnIdentityValue int OUTPUT )
AS
BEGIN
DECLARE #TempScheduleIdentity table ( TempScheduleID int )
INSERT INTO Schedule ( CustomerID,ItemID,StartDate,TimeIn,TimeOut )
OUTPUT INSERTED.ScheduleID into #TempScheduleIdentity
VALUES (#CustomerID,#ItemID,#StartDate,#TimeIn,#TimeOut)
SELECT #ReturnIdentityValue = (SELECT TempScheduleID FROM #TempScheduleIdentity)
END
Once you have the #ReturnIdentityValue...you could then update the records other field with the value.

SQL: How do I insert data from one table and output to a temporary table with extra value from first table

I can use the OUTPUT keyword of the insert statement to insert new data to a table and output to a temporary table.
The input table which to be inserted into another table have an Id I need to pass to the temporary table but not the table I going to insert into. This temporary table will later have to use to do extra insertion to the other table.
INSERT INTO table1 (Name, Age)
OUTPUT inserted.Id, User.Id (??) INTO TemporaryTable
SELECT Name, Age FROM User
Is there a way to do it? Because the next insertion will need the new table1.Id with the User.Id, so I can migrate some data.
Instead of using the Temporary table you can use Variable so that it will not occupy more memory.
create table table1
(
id int NOT NULL,
,name varchar(50)
,age int,
PRIMARY KEY (id)
)
insert into table1 (name,age) values ('name', 10)
declare #extracolumn as int = scope_identity()
select #extracolumn
use this #extracolumn in next insert operation.
Have you included the extra column in the schema of the temporary table?
create table table1
(
id int
,name varchar(50)
,age int
)
declare #TemporaryTable table -- or Create table #TemporaryTable
(
id int,
userid int -- defining the extra column
);
declare #extracolumn as int = 100;
-- or declare #extracolumn as int = (select value from table where condition)
-- note that subqueries cannot be added directly in the output clause
-- so need to declare and set a variable that holds the value
insert into table1
output inserted.id,#extracolumn into #TemporaryTable -- or #TemporaryTable
values(1,'name',10)
select * from #TemporaryTable
Output is
id userid
1 100

How to get the just inserted row in SQL Server stored procedure (without using trigger)?

I have stored procedures that inserts/updates records in some tables. Some columns of those tables have default values or auto-increment. Here's what I have:
ALTER PROCEDURE [dbo].[Usp___NewExpense]
#iCampaignID int,
#iCategory int,
#iUserID int,
#dDate Date,
#iAmountInINR int,
#strComments VarChar(200)
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO dbo.Tbl_Expenses(iCampaignID, iCategory, dDate, iAmountInINR, strComments)
VALUES (#iCampaignID, #iCategory, #dDate, #iAmountInINR, #strComments);
-- How to get the record inserted using the above statement here without using triggers
-- or another select statement, so that I can assign values to the following variables?
Declare #justInsertedValue1 type1;
Declare #justInsertedValue2 type2;
Declare #justInsertedValue3 type3;
INSERT INTO dbo.Tbl_SomeOtherTable(col1, col2, col3)
VALUES (justInsertedValue1, justInsertedValue2, justInsertedValue3);
END
GO
Tbl_Expenses has about 9 columns in which two have default values and two have auto-increment set. How can I get the just inserted record just below my INSERT statement?
I know that I can use SCOPE_IDENTITY() and then a SELECT, but a query would probably make it inefficient (am I right?).
(By getting the just inserted record, I mean values of all fields of the just inserted record)
Edit: I haven't specified values for all the fields in my INSERT statement. I want to get those values inserted automatically by SQL Server due to DEFAULT/AUTO INCREMENT constraints also.
You can use the OUTPUT clause. You can even combine both inserts into one composite:
create table T1 (ID int IDENTITY(1,1) not null,ColA varchar(10) not null)
create table T2 (ID int IDENTITY(1,1) not null,T1ID int not null,ColB varchar(10) not null)
--Look ma! no local variables at all
insert into T2 (T1ID,ColB)
select t1.ID,'def'
from (
insert into T1(ColA)
output inserted.ID
values ('abc')
) t1
select * from T1
select * from T2
Results:
ID ColA
----------- ----------
1 abc
ID T1ID ColB
----------- ----------- ----------
1 1 def

Bulk insert from a table to another

We have 2 tables in SQL Server 2008 R2. Periodically, we have to insert a batch of records from Table A to Table B. While the inserting, Table B still able to SELECT & UPDATE. Currently, we use INSERT..SELECT to copy from Table A to Table B. But the problem is while inserting, sometimes will cause UPDATE statement to TABLE B timeout.
Is there a better bulk insert solution from a table to another that won't cause blocking?
They most obvious solution is to use smaller batches as Stanley suggested. If this is really not an option you could explore '(transaction level) snapshot isolation.
1 Set the transaction timeout to a large enough value, so that the statement no longer goes in timeout.
2 Use CURSOR and do it row by row
3 Try this way of doing things. Requires a row identifier (IDENTITY for instance), best to have a PK or INDEX on that field:
SET NOCOUNT ON;
CREATE TABLE #A(
row_id INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
data INT NOT NULL
);
CREATE TABLE #B(
row_id INT NOT NULL PRIMARY KEY,
data INT NOT NULL
);
-- TRUNCATE TABLE #B; -- no truncate needed since you just want to add rows, not copy the whole table
DECLARE #batch_size INT;
SET #batch_size = 10000;
DECLARE #from_row_id INT;
DECLARE #to_row_id INT;
-- You would use this to establish the first #from_row_id if you wanted to copy the whole table
-- SELECT
-- #from_row_id=ISNULL(MIN(row_id),-1)
-- FROM
-- #A AS a;
SELECT
#from_row_id=ISNULL(MAX(row_id),-1)
FROM
#B AS b;
IF #from_row_id=-1
SELECT
#from_row_id=ISNULL(MIN(row_id),-1)
FROM
#A AS a;
ELSE
SELECT
#from_row_id=ISNULL(MIN(row_id),-1)
FROM
#A AS a
WHERE
row_id>#from_row_id;
WHILE #from_row_id>=0
BEGIN
SELECT
#to_row_id=ISNULL(MAX(row_id),-1)
FROM
(
SELECT TOP(#batch_size)
row_id
FROM
#A AS a
WHERE
row_id>=#from_row_id
) AS row_ids
IF #to_row_id=-1
BEGIN
INSERT
#B
SELECT
*
FROM
#A AS a
WHERE
row_id>=#from_row_id;
BREAK;
END
ELSE
INSERT
#B
SELECT
*
FROM
#A AS a
WHERE
row_id BETWEEN #from_row_id AND #to_row_id;
SELECT
#from_row_id=ISNULL(MIN(row_id),-1)
FROM
#A AS a
WHERE
row_id>#to_row_id;
END
DROP TABLE #B;
DROP TABLE #A;

Resources