Unable to drop a temporary table - sql-server

I have a stored procedure which uses a temporary table. The thing is that I've tried to use the temporary table more than one time in different SELECT INTO statements. Of course, before issuing the next statement I have issued a DROP #TempTableName and then issue the SELECT INTO statement. Apparently this DROP statement isn't enough since the next SELECT INTO statement complains that the object #TempTableName already exist - SSMS output is:
Msg 2714, Level 16, State 1, Procedure SYNC_SpreadMembers, Line 23
There is already an object named '#MM_SYNC_MEMBERS' in the database.
And here is my T-SQL code:
CREATE PROCEDURE SYNC_SpreadMembers
AS
BEGIN
BEGIN
-- Member
IF (OBJECT_ID('tempdb..#MM_SYNC_MEMBERS') IS NOT NULL)
DROP TABLE #MM_SYNC_MEMBERS;
-- Imported members
SELECT DISTINCT MemberInr INTO #MM_SYNC_MEMBERS FROM
(
SELECT DISTINCT DmInr AS MemberInr FROM MM_SYNC_EBOLIGWS WHERE NOT DmInr IS NULL
UNION
SELECT DISTINCT AmInr AS MemberInr FROM MM_SYNC_EBOLIGWS WHERE NOT AmInr IS NULL
) MemberHeap
;
DELETE #MM_SYNC_MEMBERS FROM #MM_SYNC_MEMBERS Sync INNER JOIN MM_Member Member ON Sync.MemberInr = Member.InteressentNr;
INSERT INTO MM_Member(InteressentNr) SELECT MemberInr FROM #MM_SYNC_MEMBERS;
END
-- Hardcoded members
DROP TABLE #MM_SYNC_MEMBERS;
SELECT DISTINCT InteressentNr AS MemberInr INTO #MM_SYNC_MEMBERS FROM MM_SYNC_HardcodedMemberRoles;
DELETE #MM_SYNC_MEMBERS FROM #MM_SYNC_MEMBERS Sync INNER JOIN MM_Member Member ON Sync.MemberInr = Member.InteressentNr;
INSERT INTO MM_Member(InteressentNr) SELECT MemberInr FROM #MM_SYNC_MEMBERS;
-- MemberRole
-- Area Managers
DELETE MM_MemberRole;
INSERT INTO MM_MemberRole(MemberSid, RoleSid)
SELECT DISTINCT Member.[Sid], (SELECT [Sid] FROM MM_Role WHERE Cipher LIKE 'AMA')
FROM MM_SYNC_EBOLIGWS Sync
INNER JOIN MM_Member Member ON Sync.AmInr = Member.InteressentNr
WHERE Sync.AmInr IS NOT NULL
;
-- Department Managers
INSERT INTO MM_MemberRole(MemberSid, RoleSid)
SELECT DISTINCT Member.[Sid], (SELECT Sid FROM MM_Role WHERE Cipher LIKE 'DM')
FROM MM_SYNC_EBOLIGWS Sync
INNER JOIN MM_Member Member ON Sync.DmInr = Member.InteressentNr
WHERE Sync.DmInr IS NOT NULL
;
-- Hardcoded Roles
INSERT INTO MM_MemberRole(MemberSid, RoleSid)
SELECT Member.Sid, Roles.Sid
FROM MM_SYNC_HardcodedMemberRoles HCR
INNER JOIN MM_Member Member ON HCR.InteressentNr = Member.InteressentNr
INNER JOIN MM_Role Roles ON HCR.RoleCipher = Roles.Cipher
;
END
GO

T-SQL is a very simple language - it basically compiles all of the code in the current scope/batch as soon as possible. At various times (such as when a new table is created) it will recompile the batch.
The error is actually being thrown when it does the recompile immediately after you first create the new temp table. At that point, when it tries to recompile the later statement that also tries to create a temp table with the same name, it produces the error.
It doesn't wait to see whether the normal flow of execution (including control flow) will prevent an error occurring when the statement is reached. E.g. this produces a similar error:
create table #Blah (ID int)
if 1 = 0
begin
create table #Blah (Foo int)
end
Even though we can look at it and know that no harm would actually occur
Msg 2714, Level 16, State 1, Line 4
There is already an object named '#Blah' in the database.

Since the table has the same columns in both inserts, why not just create the table once, and instead of dropping it:
TRUNCATE TABLE #MM_SYNC_MEMBERS

Temporary tables are tied to a connection. So when the connection is
dropped, the temporary table is dropped.
So it won't be dropped in the middle of the stored procedure.
This will be tied to the instance it is in, it could fit your needs:
DECLARE #TemporaryTable TABLE
(
id int,
name nvarchar(50)
)
Extra:
Maybe you could also have a look at CTE's as it may be a solution for your problem:
http://msdn.microsoft.com/en-us/library/ms190766(v=sql.105).aspx

why done you create structure of #MM_SYNC_MEMBERS globally and then you can delete or insert data in temp according to conditions.
EX-
create table #Temp
(name varchar(20))
IF(condtion1)
insert into temp (or delete)
else if(condition2)
insert to temp
.........

Related

What is the "lifespan" of a postgres CTE expression? e.g. WITH... AS

I have a CTE I am using to pull some data from two tables then stick in an intermediate table called cte_list, something like
with cte_list as (
select pl.col_val from prune_list pl join employees.employee emp on pl.col_val::uuid = emp.id
where pl.col_nm = 'employee_ref_id' limit 100
)
Then, I am doing an insert to move records from the cte_list to another archive table (if they don't exist) called employee_arch_test
insert into employees.employee_arch_test (
select * from employees.employee where id in (select col_val::uuid from cte_list)
and not exists (select 1 from employees.employee_arch_test where employees.employee_arch_test.id=employees.employee.id)
);
This seems to work fine. The problem is when I add another statement after, to do some deletions from the main employee table using this aforementioned cte_list - the cte_list apparently no longer exists?
SQL Error [42P01]: ERROR: relation "cte_list" does not exist
the actual delete query:
delete from employees.employee where id in (select col_val::uuid from cte_list);
Can the cte_list CTE table only be used once or something? I'm running these statements in a LOOP and I need to run the exact same calls for about 2 or 3 other tables but hit a sticking point here.
A CTE only exists for the duration of the statement of which it's a part. I gather you have an INSERT statement with the CTE preceding it:
with cte_list
as (select pl.col_val
from prune_list pl
join employees.employee emp
on pl.col_val::uuid = emp.id
where pl.col_nm = 'employee_ref_id'
limit 100
)
insert into employees.employee_arch_test
(select *
from employees.employee
where id in (select col_val::uuid from cte_list)
and not exists (select 1
from employees.employee_arch_test
where employees.employee_arch_test.id = employees.employee.id)
);
The CTE is part of the INSERT statement - it is not a separate statement by itself. It only exists for the duration of the INSERT statement.
If you need something which lasts longer your options are:
Add the same CTE to each of your following statements. Note that because data may be changing in your database each invocation of the CTE may return different data.
Create a view which performs the same operations as the CTE, then use the view in place of the CTE. Note that because data may be changing in your database each invocation of the view may return different data.
Create a temporary table to hold the data from your CTE query, then use the temporary table in place of the CTE. This has the advantage of providing a consistent set of data to all operations.

Select into Temp table cannot exist in seperate IF condition?

IF OBJECT_ID('tempdb..#tempTable2') IS NOT NULL
DROP TABLE #tempTable2;
IF #someCondition is not null
BEGIN
SELECT * INTO #tempTable2 FROM Table; --No problem, no error
END
ELSE
BEGIN
SELECT * INTO #tempTable2 FROM Table; --Execution failed
END
Msg 2714, Level 16, State 1, Line 197 There is already an object named
'#tempTable2' in the database.
May I know what could be the reason for the above case? Procedurally, the query in first IF section was not being executed and hence it should not create #tempTable2
You cannot have two statements in the same procedure that create a temp table with the same name.
This is a leftover from SQL 6.5 which did not have deferred name resolution.
Instead of using select into, use create table + insert.
Answer by Erland Sommarskog on MSDN Social
if object_id('tempdb..#tempTable2') is not null
drop table #tempTable2;
create table #tempTable2 (...);
if #someCondition is not null
begin
insert into #tempTable2
select * from Table;
end
else
begin
insert into #tempTable2
select * from Table;
end
I hope you can understand my pour english.
the problem is not "if" but the statement of "select"
with the structure "select * into table2 from table1"will create the ssms a new table automatically.
that means,when you use the same statement in another time,the table2 has already created in your database.
you don't need to create it again or you can create it with another name.

How To Get Temp Table to Run Multiple Time

I'm using MS SQL Server. I'm currently working on a query for pulling headcount. In this process, I'm creating temp tables, but noticed that I can only run the query once. If I try running it again after making changes, it gives me the 'There is already an object named '#Test1' in the database.'
My SQL looks like this:
SET NOCOUNT ON SET ANSI_WARNINGS OFF
IF OBJECT_ID('Tempdb..#Headcount') IS NOT NULL
Drop Table #Test1
Select Coalesce(Enddate,GETDATE()) as EndDate1,FirstName,LastName,EmployeeID,CostCenter,JobCode, CompanyCode
Into #Test1
from EmployeeDM.dbo.vEmployeeJobReporting EJ
--Group By FirstName,LastName,EmployeeID,CostCenter
Order by 1
IF OBJECT_ID('Tempdb..#Headcount') IS NOT NULL
Drop Table #Final1
Select max(EndDate1) as Date1, FirstName,LastName,EmployeeID,CostCenter,JobCode, CompanyCode
Into #Final1
From #Test1
Group by FirstName,LastName,EmployeeID,CostCenter,JobCode, CompanyCode
Order by 1
SELECT F.CostCenter,F.FirstName,F.LastName, F.Date1, F.CompanyCode, F.JobCode,F.EmployeeID,(t3.Day_of_Month-t2.Day_of_Month+1)*1.0/t4.Day_of_Month as Headcount,
Case
The last Select statement is the start of the non-temp table query. What can I do / write in the code to be able to run multiple times in a row? Also, the error I'm receiving:
Msg 2714, Level 16, State 6, Line 4
There is already an object named '#Test1' in the database.
Thanks!
When you write ...INTO #test1, you are creating the table based on the content of the select statement. You need to either 1) drop the temp tables at the end of your query, 2) check for them existing at the front end and drop if they exist, 3) both.
You are already checking for #headcount, but dropping #final and #test1 at the beginning. I dont see where you are declaring #headcount as a table?

Temp table in Azure SQL Server returns error on second insert in Stored Procedure

I have a temp table declared in a stored procedure within an Azure SQL Server instance.
After declaring it with
WITH temp
(cols) AS
(SELECT * FROM goaltable
WHERE someCondition = true)
and utilizing it in a couple of INSERT statements, SQL Server returns an error on the second INSERT that invalid object name 'temp'.
Will I have to declare the table again before my second INSERT statement, or is there a better way to do it?
WITH key word is used to initialise a CTE (Comman Table Expression), it is not a Temporary Table.
Temp Tables are prefixed with a pound sign # or ##(Global Temp tables, google for the differences).
A CTE's scope is limited to the very first statement after the CTE has been initialised.
WITH CTE AS
( /* Your query here */)
SELECT FROM CTE --<-- Scope of above CTE
-- it maybe select , delete, update statement here
SELECT FROM CTE --<-- this statement is out of scope
-- this will return an error
Temp Tables
In your case if you want to create a temp table and use it on multiple places you would need to do something like this...
SELECT * INTO #Temp
FROM goaltable
WHERE someCondition = true
Now this #Temp table's scope is the connection in which it is created. You can select from it multiple times anywhere in this session.
for example the following queries will be executed without any errors as long as they are executed in the same connection.
SELECT TOP 10 * FROM #Temp
SELECT * FROM #Temp
Note
Even though the scope of the temp table is your session, yet it is a good practice to drop temp tables once you are done working with them.
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL
DROP TABLE #Temp

SQL - Inserting and Updating Multiple Records at Once

I have a stored procedure that is responsible for inserting or updating multiple records at once. I want to perform this in my stored procedure for the sake of performance.
This stored procedure takes in a comma-delimited list of permit IDs and a status. The permit IDs are stored in a variable called #PermitIDs. The status is stored in a variable called #Status. I have a user-defined function that converts this comma-delimited list of permit IDs into a Table. I need to go through each of these IDs and do either an insert or update into a table called PermitStatus.
If a record with the permit ID does not exist, I want to add a record. If it does exist, I'm want to update the record with the given #Status value. I know how to do this for a single ID, but I do not know how to do it for multiple IDs. For single IDs, I do the following:
-- Determine whether to add or edit the PermitStatus
DECLARE #count int
SET #count = (SELECT Count(ID) FROM PermitStatus WHERE [PermitID]=#PermitID)
-- If no records were found, insert the record, otherwise add
IF #count = 0
BEGIN
INSERT INTO
PermitStatus
(
[PermitID],
[UpdatedOn],
[Status]
)
VALUES
(
#PermitID,
GETUTCDATE(),
1
)
END
ELSE
UPDATE
PermitStatus
SET
[UpdatedOn]=GETUTCDATE(),
[Status]=#Status
WHERE
[PermitID]=#PermitID
How do I loop through the records in the Table returned by my user-defined function to dynamically insert or update the records as needed?
create a split function, and use it like:
SELECT
*
FROM YourTable y
INNER JOIN dbo.splitFunction(#Parameter) s ON y.ID=s.Value
I prefer the number table approach
For this method to work, you need to do this one time table setup:
SELECT TOP 10000 IDENTITY(int,1,1) AS Number
INTO Numbers
FROM sys.objects s1
CROSS JOIN sys.objects s2
ALTER TABLE Numbers ADD CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number)
Once the Numbers table is set up, create this function:
CREATE FUNCTION [dbo].[FN_ListToTableAll]
(
#SplitOn char(1) --REQUIRED, the character to split the #List string on
,#List varchar(8000)--REQUIRED, the list to split apart
)
RETURNS TABLE
AS
RETURN
(
----------------
--SINGLE QUERY-- --this WILL return empty rows
----------------
SELECT
ROW_NUMBER() OVER(ORDER BY number) AS RowNumber
,LTRIM(RTRIM(SUBSTRING(ListValue, number+1, CHARINDEX(#SplitOn, ListValue, number+1)-number - 1))) AS ListValue
FROM (
SELECT #SplitOn + #List + #SplitOn AS ListValue
) AS InnerQuery
INNER JOIN Numbers n ON n.Number < LEN(InnerQuery.ListValue)
WHERE SUBSTRING(ListValue, number, 1) = #SplitOn
);
GO
You can now easily split a CSV string into a table and join on it:
select * from dbo.FN_ListToTableAll(',','1,2,3,,,4,5,6777,,,')
OUTPUT:
RowNumber ListValue
----------- ----------
1 1
2 2
3 3
4
5
6 4
7 5
8 6777
9
10
11
(11 row(s) affected)
To make what you need work, do the following:
--this would be the existing table
DECLARE #OldData table (RowID int, RowStatus char(1))
INSERT INTO #OldData VALUES (10,'z')
INSERT INTO #OldData VALUES (20,'z')
INSERT INTO #OldData VALUES (30,'z')
INSERT INTO #OldData VALUES (70,'z')
INSERT INTO #OldData VALUES (80,'z')
INSERT INTO #OldData VALUES (90,'z')
--these would be the stored procedure input parameters
DECLARE #IDList varchar(500)
,#StatusList varchar(500)
SELECT #IDList='10,20,30,40,50,60'
,#StatusList='A,B,C,D,E,F'
--stored procedure local variable
DECLARE #InputList table (RowID int, RowStatus char(1))
--convert input prameters into a table
INSERT INTO #InputList
(RowID,RowStatus)
SELECT
i.ListValue,s.ListValue
FROM dbo.FN_ListToTableAll(',',#IDList) i
INNER JOIN dbo.FN_ListToTableAll(',',#StatusList) s ON i.RowNumber=s.RowNumber
--update all old existing rows
UPDATE o
SET RowStatus=i.RowStatus
FROM #OldData o WITH (UPDLOCK, HOLDLOCK) --to avoid race condition when there is high concurrency as per #emtucifor
INNER JOIN #InputList i ON o.RowID=i.RowID
--insert only the new rows
INSERT INTO #OldData
(RowID, RowStatus)
SELECT
i.RowID, i.RowStatus
FROM #InputList i
LEFT OUTER JOIN #OldData o ON i.RowID=o.RowID
WHERE o.RowID IS NULL
--display the old table
SELECT * FROM #OldData order BY RowID
OUTPUT:
RowID RowStatus
----------- ---------
10 A
20 B
30 C
40 D
50 E
60 F
70 z
80 z
90 z
(9 row(s) affected)
EDIT thanks to #Emtucifor click here for the tip about the race condition, I have included the locking hints in my answer, to prevent race condition problems when there is high concurrency.
There are various methods to accomplish the parts you ask are asking about.
Passing Values
There are dozens of ways to do this. Here are a few ideas to get you started:
Pass in a string of identifiers and parse it into a table, then join.
SQL 2008: Join to a table-valued parameter
Expect data to exist in a predefined temp table and join to it
Use a session-keyed permanent table
Put the code in a trigger and join to the INSERTED and DELETED tables in it.
Erland Sommarskog provides a wonderful comprehensive discussion of lists in sql server. In my opinion, the table-valued parameter in SQL 2008 is the most elegant solution for this.
Upsert/Merge
Perform a separate UPDATE and INSERT (two queries, one for each set, not row-by-row).
SQL 2008: MERGE.
An Important Gotcha
However, one thing that no one else has mentioned is that almost all upsert code, including SQL 2008 MERGE, suffers from race condition problems when there is high concurrency. Unless you use HOLDLOCK and other locking hints depending on what's being done, you will eventually run into conflicts. So you either need to lock, or respond to errors appropriately (some systems with huge transactions per second have used the error-response method successfully, instead of using locks).
One thing to realize is that different combinations of lock hints implicitly change the transaction isolation level, which affects what type of locks are acquired. This changes everything: which other locks are granted (such as a simple read), the timing of when a lock is escalated to update from update intent, and so on.
I strongly encourage you to read more detail on these race condition problems. You need to get this right.
Conditional Insert/Update Race Condition
“UPSERT” Race Condition With MERGE
Example Code
CREATE PROCEDURE dbo.PermitStatusUpdate
#PermitIDs varchar(8000), -- or (max)
#Status int
AS
SET NOCOUNT, XACT_ABORT ON -- see note below
BEGIN TRAN
DECLARE #Permits TABLE (
PermitID int NOT NULL PRIMARY KEY CLUSTERED
)
INSERT #Permits
SELECT Value FROM dbo.Split(#PermitIDs) -- split function of your choice
UPDATE S
SET
UpdatedOn = GETUTCDATE(),
Status = #Status
FROM
PermitStatus S WITH (UPDLOCK, HOLDLOCK)
INNER JOIN #Permits P ON S.PermitID = P.PermitID
INSERT PermitStatus (
PermitID,
UpdatedOn,
Status
)
SELECT
P.PermitID,
GetUTCDate(),
#Status
FROM #Permits P
WHERE NOT EXISTS (
SELECT 1
FROM PermitStatus S
WHERE P.PermitID = S.PermitID
)
COMMIT TRAN
RETURN ##ERROR;
Note: XACT_ABORT helps guarantee the explicit transaction is closed following a timeout or unexpected error.
To confirm that this handles the locking problem, open several query windows and execute an identical batch like so:
WAITFOR TIME '11:00:00' -- use a time in the near future
EXEC dbo.PermitStatusUpdate #PermitIDs = '123,124,125,126', 1
All of these different sessions will execute the stored procedure in nearly the same instant. Check each session for errors. If none exist, try the same test a few times more (since it's possible to not always have the race condition occur, especially with MERGE).
The writeups at the links I gave above give even more detail than I did here, and also describe what to do for the SQL 2008 MERGE statement as well. Please read those thoroughly to truly understand the issue.
Briefly, with MERGE, no explicit transaction is needed, but you do need to use SET XACT_ABORT ON and use a locking hint:
SET NOCOUNT, XACT_ABORT ON;
MERGE dbo.Table WITH (HOLDLOCK) AS TableAlias
...
This will prevent concurrency race conditions causing errors.
I also recommend that you do error handling after each data modification statement.
If you're using SQL Server 2008, you can use table valued parameters - you pass in a table of records into a stored procedure and then you can do a MERGE.
Passing in a table valued parameter would remove the need to parse CSV strings.
Edit:
ErikE has raised the point about race conditions, please refer to his answer and linked articles.
If you have SQL Server 2008, you can use MERGE. Here's an article describing this.
You should be able to do your insert and your update as two set based queries.
The code below was based on a data load procedure that I wrote a while ago that took data from a staging table and inserted or updated it into the main table.
I've tried to make it match your example, but you may need to tweak this (and create a table valued UDF to parse your CSV into a table of ids).
-- Update where the join on permitstatus matches
Update
PermitStatus
Set
[UpdatedOn]=GETUTCDATE(),
[Status]=staging.Status
From
PermitStatus status
Join
StagingTable staging
On
staging.PermitId = status.PermitId
-- Insert the new records, based on the Where Not Exists
Insert
PermitStatus(Updatedon, Status, PermitId)
Select (GETUTCDATE(), staging.status, staging.permitId
From
StagingTable staging
Where Not Exists
(
Select 1 from PermitStatus status
Where status.PermitId = staging.PermidId
)
Essentially you have an upsert stored procedure (eg. UpsertSinglePermit)
(like the code you have given above) for dealing with one row.
So the steps I see are to create a new stored procedure (UpsertNPermits) which does
a) Parse input string into n record entries (each record contains permit id and status)
b) Foreach entry in above, invoke UpsertSinglePermit

Resources