SQL Server unexpected behaviour with IN (SELECT...) [duplicate] - sql-server

This question already has answers here:
sql server 2008 management studio not checking the syntax of my query
(2 answers)
T-SQL in SQL Server - bug or feature? [duplicate]
(1 answer)
Closed 9 months ago.
I made a mistake when updating code and ended up with this situation (I renamed the column in a table variable and missed renaming where it was used)...
CREATE TABLE MyTable (Id INT, OtherCol VARCHAR(100));
-- populate with lots of data, just a demo here
INSERT MyTable(Id) VALUES (1, 'bla'), (2, 'bla'), (3, 'bla'), (4, 'bla');
DECLARE #TableVar TABLE (MyCol INT);
-- populate the table variable somehow, e.g. ...
INSERT #TableVar VALUES (1);
UPDATE MyTable
SET OtherCol = 'Some value'
WHERE Id IN (SELECT Id FROM #TableVar);
The mistake is that the Id column in the IN (SELECT...) should have been MyCol of course, but I didn't expect the following behaviour...
If #TableVar contains no rows, no update is done at all - I expected that
If #TableVar contains any rows, every row in MyTable is updated
I would have expected that if #TableVar included one row, that the (SELECT...) would return one row, though I'm not sure what value I'd expect in the Id column.
The code isn't great anyway for various reasons, but that behaviour of an IN (SELECT...) where a column name from the outer query is referenced acting as if it matched every row surprised me (and made it a very nasty error).
Am I just dumb to be so surprised by this? What does SQL Server actually make out of that statement?

Related

What is the use of OUTPUT clause in sql server

What is the purpose of the OUTPUT clause? I have gone through the MSDN documentation for the OUTPUT clause, which includes the following example:
DELETE FROM dbo.table1
OUTPUT DELETED.* INTO #MyTableVar
WHERE id = 4 OR id = 2;
From the above query, it seems that deleted records are saved in some magic table called deleted, and the query will load those records into table called MyTableVar from the magic deleted table. .
I still do not understand the purpose of the OUTPUT clause usage.
As another SQL example:
USE AdventureWorks2012;
GO
DECLARE #MyTableVar table( NewScrapReasonID smallint,
Name varchar(50),
ModifiedDate datetime);
INSERT Production.ScrapReason
OUTPUT INSERTED.ScrapReasonID, INSERTED.Name, INSERTED.ModifiedDate
INTO #MyTableVar
VALUES (N'Operator error', GETDATE());
--Display the result set of the table variable.
SELECT NewScrapReasonID, Name, ModifiedDate FROM #MyTableVar;
--Display the result set of the table.
SELECT ScrapReasonID, Name, ModifiedDate
FROM Production.ScrapReason;
GO
What is this actually doing? Can anyone explain what this clause is doing with an easy example?
UPDATE with non-functioning example:
create proc test
as
CREATE TABLE dbo.table1
(
id INT,
employee VARCHAR(32)
)
go
INSERT INTO dbo.table1 VALUES
(1, 'Fred')
,(2, 'Tom')
,(3, 'Sally')
,(4, 'Alice')
delete from table1
select * from deleted
This gives me an error when I run it, because it can't see the deleted table.
The general purpose of this clause is to capture the changes made to your data without an additional query, which would introduce locking and blocking issues. Example:
DELETE FROM X WHERE Name = 'Foo'
You want to know which IDs were deleted. You can do this naively like this:
SELECT ID FROM X WHERE Name = 'Foo'
DELETE FROM X WHERE Name = 'Foo'
But these selected IDs are unreliable unless you are running in a transaction with isolation level SERIALIZABLE which is usually not the case. Someone else can add, delete or change "Foo"-Records between your two statements. So instead you can use the OUTPUT clause and get back exactly and reliably the deleted IDs without any performance or reliability issues.
Another frequent use is to get the value of inserted default values, especially when using identity columns. For a single insert you can do this:
CREATE TABLE X
(
ID INT IDENTITY,
Name VARCHAR(10)
);
INSERT X (Name) VALUES ('Foo')
SELECT SCOPE_IDENTITY()
But SCOPE_IDENTITY() can give you only the last inserted ID. If you do multiple inserts, like
INSERT X (Name) VALUES ('Foo'), ('Bar')
or
INSERT X (Name) SELECT OtherName FROM Y
and you want to know the inserted IDs, you are out of luck. You can try to find them with another SELECT, but you need another unique column to even formulate the query and then you run into the same issues as with the DELETE sample above. So, the OUTPUT clause lets you identify neatly which Names got which IDs.
You will need these IDs for example when creating dependent records with foreign keys. Think "Order" and "OrderDetails" which are linked by an OrderID column with an IDENTITY clause. Again, with a single INSERT you can get away with using SCOPE_IDENTITY() or ##IDENTITY, but when inserting multiple orders at once, you will need OUTPUT.
When you perform Insert/Update/Delete operation on particular table and want to know what rows are affected OR want to log them for audit trail OR you want to use multiple values of affected rows in subsequent sql statements, you can use OUTPUT clause.
For Insert statement, it will have INSERTED table.
For Delete statement, it will have DELETED table. In case of Update DELETED table will contain rows (with old values) before update operation performed.
For Update statement, it will have DELETED and INSERTED tables.
DELETED table will contain rows (with old values) before update operation performed.
INSERTED table will contain rows (with new values) after update operation performed.
USE AdventureWorks2012;
GO
DECLARE #MyTableVar table( NewScrapReasonID smallint,
Name varchar(50),
ModifiedDate datetime);
INSERT Production.ScrapReason
OUTPUT INSERTED.ScrapReasonID, INSERTED.Name, INSERTED.ModifiedDate
INTO #MyTableVar
VALUES (N'Operator error', GETDATE());
--Display the result set of the table variable.
SELECT NewScrapReasonID, Name, ModifiedDate FROM #MyTableVar;
--Display the result set of the table.
SELECT ScrapReasonID, Name, ModifiedDate
FROM Production.ScrapReason;
Now your query inserts rows in Production.ScrapReason as well as table variable #MyTableVar. Later it selects inserted rows from Production.ScrapReason and #MyTableVar. Thus you can compare both the resultset and it must have identical rows (considering Production.ScrapReason is empty table.)
I hope it makes sense!
Edit:
Inserted/Deleted tables will be available with Insert/Update/Delete statement and not after that. You may want to store those magic table values in db table or temp table.
Without the OUTPUT clause, how would you know which rows were deleted? Your example seems so simple because you already know the Id values, but what if you did this:
DELETE FROM T WHERE SomeColumn LIKE 'SomePattern%'
And you want to find out what was deleted. That's the purpose of the OUTPUT clause.

Cannot create the same #temp table twice in the same batch

Wrote a very simple test case in SQLServer and don't quite understand why it doesn't work:
create table #temp(
id int,
val int)
insert into #temp values (1, 1), (2, 2)
select * from #temp
if object_id('tempdb..#temp') is not null
drop table #temp
create table #temp(
id int,
val int)
insert into #temp values (1, 1), (2, 2)
select * from #temp
Please see the documentation, which states:
If more than one temporary table is created inside a single stored procedure or batch, they must have different names.
Your code yields the following error:
Msg 2714, Level 16, State 1, Line 11
There is already an object named '#temp' in the database.
And this is not because the table has been dropped and can't be re-created; this code never gets executed, the parser actually sees you trying to create the same table twice (and it has no ability to see logic like your DROP command).
Other than using two different #temp table names, another workaround would be to just create the table once, and truncate it when you're done with your first bit of code.
This is a feature by design and is clarified by Microsoft against Microsoft Connect BugID 666430
A case study on the same issue is given at : temporary-table-could-not-be-re-created

Copy a primary key from a target table as a foreign key on the source table during an insert [duplicate]

This question already has answers here:
Using merge..output to get mapping between source.id and target.id
(3 answers)
Closed 10 years ago.
The Situation:
I am inserting information from one table to another, a source and target. When the information is inserted into the target, a primary key is created. (In this case it is an integer.) I then need to be able to tie back to the source table. However, based on the data being moved, I am not able to reliably get the 1:1 match between the target and source tables.
The Question:
Is there a way to copy the primary key that was created for record(x) in the target table and copy it as a foreign key to that same record(x) in the source table as the bulk insert is happening?
Details:
I am trying to get this done in SQL. I have a work-around to this problem but I figure there has to be a way to do what I'm asking.
I found my answer after reading this great article.
http://sqlblog.com/blogs/adam_machanic/archive/2009/08/24/dr-output-or-how-i-learned-to-stop-worrying-and-love-the-merge.aspx
I acheived what I was looking for by using a MERGE and its OUTPUT clause. Here is my sample code that I used to figure this out.
I started by creating 3 temporary tables, #Temp2, #Temp3 and #Temp4. #Temp2 is considered the source table. #Temp3 would be the target table and #Temp4 is a bridge. I then inserted a few rows of very simple data, in this case just one field - Value.
CREATE TABLE #Temp2(
OldID INT IDENTITY(1,1),
Value INT,
NewFK INT)
CREATE TABLE #Temp3(
NewerID INT IDENTITY(1,1),
Value INT)
CREATE TABLE #Temp4(
OldID INT NOT NULL,
NewerID INT NOT NULL,
Value INT)
INSERT INTO #Temp2(Value)
VALUES(30), (40), (50), (70)
INSERT INTO #Temp3(Value)
VALUES (333), (444), (555), (777)
Then comes the MERGE statement that does the dirty work. It will be taking the value from #Temp2 and putting it into #Temp3. It will then take the ID created in #Temp3, the ID from #Temp2 and the Value that was passed, and throw them all into #Temp4.
MERGE INTO #Temp3 AS tgt
USING #Temp2 AS src
ON 1=0
WHEN NOT MATCHED THEN
INSERT(
Value)
VALUES(
src.Value)
OUTPUT
src.OldID,
INSERTED.NewerID,
src.Value
INTO #Temp4(OldID, NewerID, Value);
Then I ran an UPDATE to the staging table #Temp2 to update the NewFK field with the new ID. Lastly, do a simple SELECT to see the updated information.
UPDATE X
SET X.NewFK = Z.NewerID
FROM #Temp2 X
JOIN #Temp4 Z
ON X.OldID = Z.OldID
SELECT * FROM #Temp2
This acheived exactly what I needed and is a pretty streamlined way of doing things. I hope this will help some people who come across this question. Thanks everyone for your insight and responses.
NOTE:
I believe MERGE was introduced in SQL Server 2008.
Jonathan
One approach would be to set identity insert for your target table to 'on' (http://msdn.microsoft.com/en-us/library/ms188059.aspx). Then make that identity part of your 'source' data before you run the insert. Just remember to turn identity insert back off again once you're done.
EDIT
Not sure what your situation is, but one apporach I've taken in the past is to create a field to hold 'external source ID', just in case I needed to refer back to the source at some point in the future. In my case, this was for reference only, not normal transactional use.
If you can get a SharedExtPK in the target then this should work.
In this case logID is the PK of the source.
Tested:
DECLARE #MyTableVar table(
TargetPK int NOT NULL,
SourcePK int NOT NULL
);
INSERT INTO IdenOutPut (someValue, sharedExtKey)
OUTPUT INSERTED.iden,
INSERTED.sharedExtKey
INTO #MyTableVar
SELECT name, logID
FROM CatID
update sPK
set sPK.ExtPK = tTbl.TargetPK
FROM #MyTableVar as tTbl
JOIN CatID as sPK
on sPK.logID = tTbl.SourcePK
GO
If the values you insert are unique then could use that.
But it would get trickier.

Getting the primary key of an newly inserted row in SQL Server 2008

I have a bunch of data which will insert into a table. This issue is that I need it to return the primary key to that table. I wasn't sure if there was things like:
insert into TABLE (...) values (...) RETURNING p_key
or
select p_key from (insert into TABLE (...) values (...))
I am making a workaround for a browser and saved information which will more or less add a row and then update it... but without the primary key, there is no way to update it as there is no reference to it.
I was looking online and found some examples via google, but it confused me slightly with these examples.
http://en.wikipedia.org/wiki/Insert_(SQL)#Retrieving_the_key
http://www.daniweb.com/web-development/databases/ms-sql/threads/299356/returning-identity-of-last-inserted-row-uniqueidentifier
Wikipedia was saying that for SQL Server 2008 to use OUTPUT instead of RETURNING, possible to use something like OUTPUT p_key
If you're inserting a whole set of rows, selecting the SCOPE_IDENTITY() won't do. And SCOPE_IDENTITY also only works for (numeric) identity columns - sometimes your PK is something else...
But SQL Server does have the OUTPUT clause - and it's very well documented on MSDN!
INSERT INTO dbo.Table(columns)
OUTPUT INSERTED.p_key, INSERTED.someothercolumnhere .......
VALUES(...)
Those values will be "echoed" back to the calling app, e.g. you'll see them in a grid in SQL Server Management Studio, or you can read them as a result set from your C# or VB.NET calling this INSERT statement.
Scope_Identity() is what you want, assuming that by "primary key" you mean "Identity"
declare #id int
insert yourtable values (some, values)
select #id = Scope_Identity()
In C#, right after your SQL Statement write SELECT SCOPE_IDENTITY(); so your code would be:
insert into TABLE (...) values (...); SELECT SCOPE_IDENTITY();
then, instead of executeNonQuery use executeScalar.
That should do the trick!
After performing insert, query:
select scope_identity()
to retrieve last inserted primary key.
Using ##IDENTITY , you can get the last generated primary key
Insert into TableName (Name,Class) values('ABC','pqr')
select ##IDENTITY

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