I have two tables in MS SQL:
CREATE TABLE Table1 (ID INT IDENTITY(1,1) NOT NULL, TEXTVal VARCHAR(100), Table2Id int)
insert into Table1 (TEXTVal) values('aaa');
insert into Table1 (TEXTVal) values('bbb'); insert into Table1 (TEXTVal) values('ccc');
CREATE TABLE Table2 (ID INT IDENTITY(1,1) NOT NULL, TEXTVal VARCHAR(100), Table2Id int)
Id are identity columns. I want to copy TEXTVal values from Table1 to Table2:
INSERT INTO Table2 (TEXTVal)
SELECT TEXTVal FROM Table1
where TEXTVal <> 'ccc'
and after that update column Table2Id in Table1 with appropriate values of Id from Table2. I can do this with cursor and SCOPE_IDENTITY().
I am just wondering, is there a way to do it without cursor in T-SQL?
As Jeroen stated in comments, you'll want to use OUTPUT. In the following example if you don't have an AdventureWorks database, just use a test database. You should be able to copy/paste this and just run it to see it in action!
USE AdventureWorks;
GO
----Creating the table which will store permanent table
CREATE TABLE TestTable (ID INT, TEXTVal VARCHAR(100))
----Creating temp table to store ovalues of OUTPUT clause
DECLARE #TmpTable TABLE (ID_New INT, TEXTVal_New VARCHAR(100),ID_Old INT, TEXTVal_Old VARCHAR(100))
----Insert values in real table
INSERT TestTable (ID, TEXTVal)
VALUES (1,'FirstVal')
INSERT TestTable (ID, TEXTVal)
VALUES (2,'SecondVal')
----Update the table and insert values in temp table using Output clause
UPDATE TestTable
SET TEXTVal = 'NewValue'
OUTPUT Inserted.ID, Inserted.TEXTVal, Deleted.ID, Deleted.TEXTVal INTO #TmpTable
WHERE ID IN (1,2)
----Check the values in the temp table and real table
----The values in both the tables will be same
SELECT * FROM #TmpTable
SELECT * FROM TestTable
----Clean up time
DROP TABLE TestTable
GO
ResultSet:
TmpTable:
ID_New TextVal_New ID_Old TextVal_Old
——————— ——————— ——————— ———————
1 NewValue 1 FirstVal
2 NewValue 2 SecondVal
Original Table:
ID TextVal
——————— ———————
1 NewValue
2 NewValue
As you can see it is possible to capture new values, and the values you are updating. In this example I'm just stuffing them into a table variable but you could do whatever you'd like with them. :)
Related
I am trying to create a constraint to validate if a relation exists
I have tried to create a procedure and then use it in check constraint. Apparently that does not seem to work.
These are my tables:
STOCKITEMS table:
StockItemId INT
StockItemName VARCHAR
ColorId INT
COLOR table:
ColorId INT
ColorName VARCHAR
This is my stored procedure:
CREATE PROCEDURE USP_ValidateColor
(#Color NVARCHAR(50))
AS
IF NOT EXISTS(SELECT ColorName FROM WareHouse.Colors WHERE ColorName = #Color)
BEGIN
DECLARE #Id INT
SET #Id = (SELECT TOP(1) ColorId + 1 FROM Warehouse.Colors
ORDER BY ColorId DESC)
INSERT INTO Warehouse.Colors
VALUES (#Id, #Color)
PRINT 'Does not exist';
END;
ELSE
PRINT 'Exists';
So if a user insert into the table stock items, I want a check that checks if the colorId already exists in the color table
If it does not, then insert that colorname into colors and. I was thinking about using a constraint check with my procedure, but can't fix the query.
Don't use an SP to check a constraint, use a foreign key:
CREATE TABLE Colour (ColourID int PRIMARY KEY, --This should really have a name
ColourName varchar(20));
CREATE TABLE StockItem (StockItemID int PRIMARY KEY, --This should really have a name too
StockItemName varchar(20),
ColourID int);
ALTER TABLE dbo.StockItem ADD CONSTRAINT Colour_FK FOREIGN KEY (ColourID) REFERENCES dbo.Colour(ColourID);
Then, if you try to insert something into the StockItem table, it'll fail unless the colour exists:
INSERT INTO dbo.Colour (ColourID,
ColourName)
VALUES (1,'Green'),(2,'Blue');
GO
INSERT INTO dbo.StockItem (StockItemID,
StockItemName,
ColourID)
VALUES(1,'Paint',1); --works
GO
INSERT INTO dbo.StockItem (StockItemID,
StockItemName,
ColourID)
VALUES (1,'Wood Panels',3); --fails
GO
--clean up
DROP TABLE dbo.StockItem;
DROP TABLE dbo.Colour;
For checking, use a UNIQUE check constraint. If you want to insert a color only if it doesn't exist, use INSERT .. FROM .. WHERE to check for existence and insert in the same query.
The only "trick" is that FROM needs a table. This can be fixed using a table value constructor to create tables out of the values to insert. If the stored procedure accepts a table-valued parameter, there's no problem.
This example uses a LEFT JOIN to insert non-matching values :
declare #colors table (Color nvarchar(10) UNIQUE)
insert into #colors VALUES ('green')
select * from #colors;
insert into #Colors (Color)
select new.Color
from (VALUES ('red'),
('green')) new(Color)
left outer join #Colors old on old.Color=new.Color
where old.Color is NULL
-- (1 row affected)
insert into #Colors (Color)
select new.Color
from (VALUES ('red'),
('green')) new(Color)
left outer join #Colors old on old.Color=new.Color
where old.Color is NULL
-- (0 rows affected)
select * from #colors;
-- green
-- red
The same using a subquery:
insert into #Colors (Color)
select new.Color
from
(VALUES ('red'),
('green')) new(Color)
where not exists (select 1
from #colors
where color=new.Color);
By using the UNIQUE constraint we ensure that duplicate entries can't be inserted
I have a situation in which I need to insert some values from a query into a table that has an identity PK. For some of the records, I need also to insert values in another table which has a 1-to-1 (partial) relationship:
CREATE TABLE A (
Id int identity primary key clustered,
Somevalue varchar(100),
SomeOtherValue int)
CREATE TABLE B (Id int primary key clustered,
SomeFlag bit)
DECLARE #inserted TABLE(NewId int, OldId)
INSERT INTO A (Somevalue)
OUTPUT Inserted.Id into #inserted(NewId)
SELECT SomeValue
FROM A
WHERE <certain condition>
INSERT INTO B (Id, SomeFlag)
SELECT
i.NewId, B.SomeFlag
FROM #inserted i
JOIN A ON <some condition>
JOIN B ON A.Id = B.Id
The problem is that the query from A in the first INSERT/SELECT returns records that can only be differentiated by the Id, which I cannot insert. Unfortunately I cannot change the structure of the A table, to insert the "previous" Id which would solve my problem.
Any idea that could lead to a solution?
With INSERT ... OUTPUT ... SELECT ... you can't output columns that are not in the target table. You can try MERGE instead:
MERGE INTO A as tgt
USING (SELECT Id, SomeValue FROM A WHERE <your conditions>) AS src
ON 0 = 1
WHEN NOT MATCHED THEN
INSERT (SomeValue)
VALUES (src.SomeValue)
OUTPUT (inserted.Id, src.Id) -- this is your new Id / old Id mapping
INTO #inserted
;
SCOPE_IDENTITY() returns the last identity value generated by the current session and current scope. You could stick that into a #table and use that to insert into B
SELECT SCOPE_IDENTITY() as newid into #c
Though, your INSERT INTO B join conditions implies to me that the value in B is already known ?
when this query is executed
DECLARE #Temp TABLE (ID INT IDENTITY,Name VARCHAR(50),ID2 INT NULL)
INSERT INTO #Temp ([Name]) VALUES ('Ali')
UPDATE #Temp SET ID2= (SELECT SCOPE_IDENTITY()) WHERE [ID]=(SELECT SCOPE_IDENTITY())
INSERT INTO #Temp ([Name]) VALUES ('Veli')
UPDATE #Temp SET ID2= (SELECT SCOPE_IDENTITY()) WHERE [ID]=(SELECT SCOPE_IDENTITY())
SELECT * FROM #Temp
We can get this table
ID-NAME-ID2
1 - Ali - 1
2 - Veli - 2
is there a way to do this in one insert query ( Assigning inserted id to another column without using idendity property in that column) ?
thanks a lot.
You want to make it a computed column like below
create TABLE Temp(ID INT IDENTITY,Name
VARCHAR(50),
ID2 AS ID PERSISTED);
Then insert rows ... your ID values will be persisted in ID2 column
INSERT INTO Temp ([Name])
VALUES ('Ali');
INSERT INTO Temp ([Name])
VALUES ('Veli');
INSERT INTO Temp ([Name])
VALUES ('Neli');
Which will results in
See a demo fiddle here http://sqlfiddle.com/#!3/59fca/1
EDIT:
If you can't change your table structure then the only way insert ID value to ID2 column is the way you are currently doing it cause in same insert statement the identity value is still not available and so it will be null.
I have two tables with foreign key constraint on TableB on TablesAs KeyA column. I was doing manual inserts till now as they were only few rows to be added. Now i need to do a bulk insert, so my question if i insert multiple rows in TableA how can i get all those identity values and insert them into TableB along with other column values. Please see the script below.
INSERT INTO Tablea
([KeyA]
,[Value] )
SELECT 4 ,'StateA'
UNION ALL
SELECT 5 ,'StateB'
UNION ALL
SELECT 6 ,'StateC'
INSERT INTO Tableb
([KeyB]
,[fKeyA] //Get value from the inserted row from TableA
,[Desc])
SELECT 1 ,4,'Value1'
UNION ALL
SELECT 2 ,5,'Value2'
UNION ALL
SELECT 3 ,6, 'Value3'
You can use the OUTPUT clause of INSERT to do this. Here is an example:
CREATE TABLE #temp (id [int] IDENTITY (1, 1) PRIMARY KEY CLUSTERED, Val int)
CREATE TABLE #new (id [int], val int)
INSERT INTO #temp (val) OUTPUT inserted.id, inserted.val INTO #new VALUES (5), (6), (7)
SELECT id, val FROM #new
DROP TABLE #new
DROP TABLE #temp
The result set returned includes the inserted IDENTITY values.
Scope identity sometimes returns incorrect value. See the use of OUTPUT in the workarounds section.
I have a requirement to insert multiple rows into table1 and at the same time insert a row into table2 with a pkID from table1 and a value that comes from a SP parameter.
I created a stored procedure that performs a batch insert with a table valued parameter which contains the rows to be inserted into table1. But I have a problem with inserting the row into table2 with the corresponding Id (identity) from table1, along with parameter value that I have passed.
Is there anyone who implemented this, or what is the good solution for this?
CREATE PROCEDURE [dbo].[oSP_TV_Insert]
#uID int
,#IsActive int
,#Type int -- i need to insert this in table 2
,#dTableGroup table1 READONLY -- this one is a table valued
AS
DECLARE #SQL varchar(2000)
DECLARE #table1Id int
BEGIN
INSERT INTO dbo.table1
(uID
,Name
,Contact
,Address
,City
,State
,Zip
,Phone
,Active)
SELECT
#uID
,Name
,Contact
,Address
,City
,State
,Zip
,Phone
,Active
,#G_Active
FROM #dTableGroup
--the above query will perform batch insert using the records from dTableGroup which is table valued
SET #table1ID = SCOPE_IDENTITY()
-- this below will perform inserting records to table2 with every Id inserted in table1.
Insert into table2(#table1ID , #type)
You need to temporarily store the inserted identity values and then create a second INSERT statement - using the OUTPUT clause.
Something like:
-- declare table variable to hold the ID's that are being inserted
DECLARE #InsertedIDs TABLE (ID INT)
-- insert values into table1 - output the inserted ID's into #InsertedIDs
INSERT INTO dbo.table1(ID, Name, Contact, Address, City, State, Zip, Phone, Active)
OUTPUT INSERTED.ID INTO #InsertedIDs
SELECT
#ID, Name, Contact, Address, City, State, Zip, Phone, Active, #G_Active
FROM #dTableGroup
and then you can have your second INSERT statement:
INSERT INTO dbo.table2(Table1ID, Type)
SELECT ID, #type FROM #InsertedIDs
See the MSDN docs on the OUTPUT clause for more details on what you can do with the OUTPUT clause - one of the most underused and most "unknown" features of SQL Server these days!
Another approach using OUTPUT clause and only one statement for inserting data in both destination tables:
--Parameters
DECLARE #TableGroup TABLE
(
Name NVARCHAR(100) NOT NULL
,Phone VARCHAR(10) NOT NULL
);
DECLARE #Type INT;
--End Of parameters
--Destination tables
DECLARE #FirstDestinationTable TABLE
(
FirstDestinationTableID INT IDENTITY(1,1) PRIMARY KEY
,Name NVARCHAR(100) NOT NULL
,Phone VARCHAR(10) NOT NULL
);
DECLARE #SecondDestinationTable TABLE
(
SecondDestinationTable INT IDENTITY(2,2) PRIMARY KEY
,FirstDestinationTableID INT NOT NULL
,[Type] INT NOT NULL
,CHECK([Type] > 0)
);
--End of destination tables
--Test1
--initialization
INSERT #TableGroup
VALUES ('Bogdan SAHLEAN', '0721200300')
,('Ion Ionescu', '0211002003')
,('Vasile Vasilescu', '0745600800');
SET #Type = 9;
--execution
INSERT #SecondDestinationTable (FirstDestinationTableID, [Type])
SELECT FirstINS.FirstDestinationTableID, #Type
FROM
(
INSERT #FirstDestinationTable (Name, Phone)
OUTPUT inserted.FirstDestinationTableID
SELECT tg.Name, tg.Phone
FROM #TableGroup tg
) FirstINS
--check records
SELECT *
FROM #FirstDestinationTable;
SELECT *
FROM #SecondDestinationTable;
--End of test1
--Test2
--initialization
DELETE #TableGroup;
DELETE #FirstDestinationTable;
DELETE #SecondDestinationTable;
INSERT #TableGroup
VALUES ('Ion Ionescu', '0210000000')
,('Vasile Vasilescu', '0745000000');
SET #Type = 0; --Wrong value
--execution
INSERT #SecondDestinationTable (FirstDestinationTableID, [Type])
SELECT FirstINS.FirstDestinationTableID, #Type
FROM
(
INSERT #FirstDestinationTable (Name, Phone)
OUTPUT inserted.FirstDestinationTableID
SELECT tg.Name, tg.Phone
FROM #TableGroup tg
) FirstINS
--check records
DECLARE #rc1 INT, #rc2 INT;
SELECT *
FROM #FirstDestinationTable;
SET #rc1 = ##ROWCOUNT;
SELECT *
FROM #SecondDestinationTable;
SET #rc2 = ##ROWCOUNT;
RAISERROR('[Test2 results] #FirstDestinationTable: %d rows; ##SecondDestinationTable: %d rows;',1,1,#rc1,#rc2);
--End of test1
Since you need all inserted identity values, look at the output clause of the insert statement: http://msdn.microsoft.com/en-us/library/ms177564.aspx