It looks like select * in a UDF is dangerous. Consider this script:
create table TestTable (col1 int, col2 varchar(1))
insert into TestTable values (123, 'a')
go
create function TestFunction
(
#param1 bit
)
returns table
as
return
(
select * from TestTable
)
go
select * from TestFunction(0)
alter table TestTable
add col3 varchar(1)
select * from TestFunction(0)
drop function TestFunction
drop table TestTable
go
You will get two result sets, both with the same number of columns, even though I added col3. If the table is recreated an an extra column is inserted in the middle, everything will shift one column over, showing the data under the wrong column name. In other words, the columns will stay the same, but the data has an extra column.
I wasn't able to find any information about this, but it seems to me the only way to avoid this is to always specify your columns in a function.
So my question is, what exactly does a UDF cache? It seems output columns are--anything else? Also, any way to still use select * but prevent this problem? Thanks.
Add exec sp_refreshsqlmodule 'TestFunction' before the second call.
The function's metadata does not automatically update. Run an ALTER statement.
Related
I'm grabbing some rows from a table, manipulating them in a temp table, and then looking to insert them as new rows into my original table.
However, I'm running into an issue with the identity column, even when I don't have the identity column on my temp table. The identity column is an auto-incrementing int.
This seems like a simple thing I'm way overthinking.
select top 0 *
into #TestTable
from OriginalTable;
...
--insert and manipulate records
...
ALTER TABLE #TestTable
DROP COLUMN MyIdentityColumn;
DECLARE #InsertedRows TABLE (NewSeqNum INT);
INSERT INTO OriginalTable
OUTPUT MyIdentityColumn INTO #InsertedRows(NewSeqNum)
SELECT * FROM #TestTable
but I get this error:
An explicit value for the identity column in table 'OriginalTable' can only be specified when a column list is used and IDENTITY_INSERT is ON.
I absolutely do not want to set an explicit value, I want it to insert and give me the new identity (via #InsertedRows)
If you don't want to keep the id of inserted records, then you need to specify all your columns but the id column in the select. As general good practice, dont select *, always specify the columns you want to retrieve-insert.
INSERT INTO OriginalTable (col1, col2, col3...)
OUTPUT MyIdentityColumn INTO #InsertedRows(NewSeqNum)
SELECT (col1, col2, col3...) FROM #TestTable
If I'm understanding you, I think your problem is that you're trying to insert '*' into the original table - which means all of your columns from the temp table. Including your ID column (which you don't want to insert, because you're wanting it to auto-generate.)
Instead, I'd suggest doing something like this:
Select [ColumnB],[ColumnC],[ColumnD],[Etc] into your temp table
Select [ColumnB],[ColumnC],[ColumnD],[Etc] into your original table.
... aka, spell out the columns explicitly, and omit the Identity column.
In my SQL Server 2012 environment, I've created a series of stored procedures that pass pre-existing temporary tables among themselves (I have tried different architectures here, but wasn't able to bypass this due to the nature of the requirements / procedures).
What I'm trying to do is to, within a stored procedure check if a temporary table has already been created and, if not, to create it.
My current SQL looks as follows:
IF OBJECT_ID('tempdb..#MyTable') IS NULL
CREATE TABLE #MyTable
(
Col1 INT,
Col2 VARCHAR(10)
...
);
But when I try and run it when the table already exists, I get the error message
There is already an object named '#MyTable' in the database
So it seems it doesn't simply ignore those lines within the If statement.
Is there a way to accomplish this - create a temp table if it doesn't already exist, otherwise, use the one already in memory?
Thanks!
UPDATE:
For whatever reason, following #RaduGheorghiu's suggestion from the comments, I found out that the system creates a temporary table with a name along the lines of dbo.#MyTable________________________________________________0000000001B1
Is that why I can't find it? Is there any way to change that? This is new to me....
Following the link here, http://weblogs.sqlteam.com/mladenp/archive/2008/08/21/SQL-Server-2005-temporary-tables-bug-feature-or-expected-behavior.aspx
It seems as though you need to use the GO statement.
You meant to use IS NOT NULL i think... this is commonly used to clear temp tables so you don't get the error you mentioned in your OP.
IF OBJECT_ID('tempdb..#MyTable') IS NOT NULL DROP TABLE #MyTable
CREATE TABLE #MyTable
(
Col1 INT,
Col2 VARCHAR(10)
);
The big difference is the DROP TABLE statement after you do your logical check. Also, creating your table without filling data doesn't make it NULL
DROP TABLE #MyTable
CREATE TABLE #MyTable
(
Col1 INT,
Col2 VARCHAR(10)
);
IF OBJECT_ID('tempdb..#MyTable') IS NOT NULL
SELECT 1
Try wrapping your actions in a begin...end block:
if object_id('tempdb..#MyTable') is null
begin
create table #MyTable (
Col1 int
, Col2 varchar(10)
);
end
This seems odd, but it works when I try it
IF(OBJECT_ID('tempdb..#Test') IS NULL) --check if it exists
BEGIN
IF(1 = 0)--this will never actually run, but it tricks the parser into allowing the CREATE to run
DROP TABLE #Test;
PRINT 'Create table';
CREATE TABLE #Test
(
ID INT NOT NULL PRIMARY KEY
);
END
IF(NOT EXISTS(SELECT 1 FROM #Test))
INSERT INTO #Test(ID)
VALUES(1);
SELECT *
FROM #Test;
--Try dropping the table and test again
--DROP TABLE #Test;
I've searched and found this article about temporary tables in SQL Server because I've met a line in one of our stored procedures saying:
SELECT Value SomeId INTO #SomeTable FROM [dbo].[SplitIds](#SomeIds, ';')
I know that #SomeTable is stored in tempdb as a temporary table. However, I don't understand why we don't have to use CREATE TABLE #SomeTable first as it is written in the mentioned article. Our code is working fine, I just don't get why it is enough to use SELECT ... INTO #SomeTable. What would be the consequence when I add CREATE TABLE #SomeTable at the beginning? Would we get any differences in performance? Would the table be stored at another location?
Select ... into [table] uses the properties of the dataset generated from the Select statement to create a temporary table and subsequently fill the table.
The alternative to using Select ... into [table] is to use a Create Table statement followed by an Insert Into statement. Explicitly creating the table offers more control and precision.
Using a Select ... into [Table] may seem like a no-brainer, but there are situations where Select ... into [Table] can be problematic.
For instance, when you are going to create a temporary table and insert additional rows at a later time, using the Select ... into [Table] syntax can cause problems, especially with string-based and nullable fields.
As an example of the limitations of the Select ... into [table], the script below creates a temporary table with two fields, First_Name and Last_Name. Next, an Insert statement attempts to add another record to the temporary table, but fails as the values would be truncated.
Select 'Bob' as First_Name
, 'Smith' as Last_Name
Into #tempTable;
Insert into #tempTable (First_Name, Last_Name)
Select 'Christopher' as First_Name
, 'Brown' as Last_Name;
The script fails because the Select ... into [table] statement creates a table equivalent to the following script:
Create Table #tempTable (
First_Name varchar(3) Not Null
Last_Name varchar(5) Not Null
);
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.
I want to create a table variable or temporary table, using data from another table but with an extra column.
I know I could use SELECT INTO as follows
SELECT *
INTO #TempTable
FROM Existing_Table
WHERE Section = 2
and then use
ALTER TABLE #TempTable ADD New_Column
And finally
DROP #TempTable
I will then be looping through each row with a dynamically called stored procedure to place values in the new column. Its not a big table.
Is that the best way? My understanding is that I cannot use the above with a table variable.
Thanks
Chris.
Its simpler
SELECT *, cast('hbkkj' as nvarchar(100)) as New_Column
INTO #TempTable
FROM Existing_Table
WHERE Section = 2