Related
I have a a number of sp's that create a temporary table #TempData with various fields. Within these sp's I call some processing sp that operates on #TempData. Temp data processing depends on sp input parameters. SP code is:
CREATE PROCEDURE [dbo].[tempdata_proc]
#ID int,
#NeedAvg tinyint = 0
AS
BEGIN
SET NOCOUNT ON;
if #NeedAvg = 1
Update #TempData set AvgValue = 1
Update #TempData set Value = -1;
END
Then, this sp is called in outer sp with the following code:
USE [BN]
--GO
--DBCC FREEPROCCACHE;
GO
Create table #TempData
(
tele_time datetime
, Value float
--, AvgValue float
)
Create clustered index IXTemp on #TempData(tele_time);
insert into #TempData(tele_time, Value ) values( GETDATE(), 50 ); --sample data
declare
#ID int,
#UpdAvg int;
select
#ID = 1000,
#UpdAvg = 1
;
Exec dbo.tempdata_proc #ID, #UpdAvg ;
select * from #TempData;
drop table #TempData
This code throws an error: Msg 207, Level 16, State 1, Procedure tempdata_proc, Line 8: Invalid column name "AvgValue".
But if only I uncomment declaration AvgValue float - everything works OK.
The question: is there any workaround letting the stored proc code remain the same and providing a tip to the optimizer - skip this because AvgValue column will not be used by the sp due to params passed.
Dynamic SQL is not a welcomed solution BTW. Using alternative to #TempData tablename is undesireable solution according to existing tsql code (huge modifications necessary for that).
Tried SET FMTONLY, tempdb.tempdb.sys.columns, try-catch wrapping without any success.
The way that stored procedures are processed is split into two parts - one part, checking for syntactical correctness, is performed at the time that the stored procedure is created or altered. The remaining part of compilation is deferred until the point in time at which the store procedure is executed. This is referred to as Deferred Name Resolution and allows a stored procedure to include references to tables (not just limited to temp tables) that do not exist at the point in time that the procedure is created.
Unfortunately, when it comes to the point in time that the procedure is executed, it needs to be able to compile all of the individual statements, and it's at this time that it will discover that the table exists but that the column doesn't - and so at this time, it will generate an error and refuse to run the procedure.
The T-SQL language is unfortunately a very simplistic compiler, and doesn't take runtime control flow into account when attempting to perform the compilation. It doesn't analyse the control flow or attempt to defer the compilation in conditional paths - it just fails the compilation because the column doesn't (at this time) exist.
Unfortunately, there aren't any mechanisms built in to SQL Server to control this behaviour - this is the behaviour you get, and anything that addresses it is going to be perceived as a workaround - as evidenced already by the (valid) suggestions in the comments - the two main ways to deal with it are to use dynamic SQL or to ensure that the temp table always contains all columns required.
One way to workaround your concerns about maintenance if you go down the "all uses of the temp table should have all columns" is to move the column definitions into a separate stored procedure, that can then augment the temporary table with all of the required columns - something like:
create procedure S_TT_Init
as
alter table #TT add Column1 int not null
alter table #TT add Column2 varchar(9) null
go
create procedure S_TT_Consumer
as
insert into #TT(Column1,Column2) values (9,'abc')
go
create procedure S_TT_User
as
create table #TT (tmp int null)
exec S_TT_Init
insert into #TT(Column1) values (8)
exec S_TT_Consumer
select Column1 from #TT
go
exec S_TT_User
Which produces the output 8 and 9. You'd put your temp table definition in S_TT_Init, S_TT_Consumer is the inner query that multiple stored procedures call, and S_TT_User is an example of one such stored procedure.
Create the table with the column initially. If you're populating the TEMP table with SPROC output just make it an IDENTITY INT (1,1) so the columns line up with your output.
Then drop the column and re-add it as the appropriate data type later on in the SPROC.
The only (or maybe best) way i can thing off beyond dynamic SQL is using checks for database structure.
if exists (Select 1 From tempdb.sys.columns Where object_id=OBJECT_ID('tempdb.dbo.#TTT') and name = 'AvgValue')
begin
--do something AvgValue related
end
maybe create a simple function that takes table name and column or only column if its always #TempTable and retursn 1/0 if the column exists, would be useful in the long run i think
if dbo.TempTableHasField('AvgValue')=1
begin
-- do something AvgValue related
end
EDIT1: Dang, you are right, sorry about that, i was sure i had ... this.... :( let me thing a bit more
All, I have the following query
IF NOT EXISTS (SELECT name
FROM sys.databases
WHERE name = N'Report')
BEGIN
DECLARE #DatabasePath NVARCHAR(1000);
SET #DatabasePath = (SELECT ResultMessage + '\'
FROM [Admin]..[Process]);
EXEC ispCREATEDB N'Report', #DatabasePath, N'10MB', N'20%'
END
ELSE
BEGIN
IF EXISTS (SELECT *
FROM Report.sys.objects
WHERE name = N'FatalErrSumm' AND type = N'U')
BEGIN
DROP TABLE [Report]..[FatalErrSumm];
CREATE TABLE [Report]..[FatalErrSumm]
(
[MDF] NVARCHAR(255) NULL,
[Error] INT NULL,
);
END
END
This checks if Report exists from a different databse; if it does not exist it creates it, if it does, it checks if table FatalErrSumm exists and if it does it drops and recreates it.
The problems is that it seems to be executing both possiblities of the IF NOT EXISTS block and giving the error
Msg 2702, Level 16, State 2, Line 24
Database 'Report' does not exist.
when the database Report does not exist. So it should never be entering the ELSE block, however it seems to be. This is very basic stuff, but I cannot for the life of me spot the error, What am I doing wrong here?
Thanks for your time.
You should bypass it using a dynamic sql
IF NOT EXISTS (SELECT name
FROM sys.databases
WHERE name = N'Report')
BEGIN
DECLARE #DatabasePath NVARCHAR(1000);
SET #DatabasePath = (SELECT ResultMessage + '\'
FROM [Admin]..[Process]);
EXEC ispCREATEDB N'Report', #DatabasePath, N'10MB', N'20%'
END
ELSE IF DB_ID('Report') IS NOT NULL
EXEC
(
'BEGIN
IF EXISTS (SELECT *
FROM Report.sys.objects
WHERE name = N''FatalErrSumm'' AND type = N''U'')
BEGIN
DROP TABLE [Report]..[FatalErrSumm];
CREATE TABLE [Report]..[FatalErrSumm]
(
[MDF] NVARCHAR(255) NULL,
[Error] INT NULL,
);
END
END'
);
I think dynamic sql is the good solution for that. because in compile time compiler checked that the database "report" is not exist in you server.
If the report is offline I think this failes, check if the database is online /attached. There is flag for this in the sys.databases table.
Also do not put your statements in the ELSE. If you enter the 'THEN' part you create the database. After that check if it is created. Than ALWAYS check for you FATALERRSUMM table and not from the IF.
pseudo code:
if (not exists database) -- watch it not exists is really NOT EXISTS not just not online
create the database
if (exists database and not online)
put online the database
if (not exists database or not online database)
throw error
if (exists table fatalerrsum)
drop table
create table
Sorry I'm a bit new to this so just trying to get my head around linking everything up.
At the moment I have a normal query - SELECT FROM WHERE which basically finds about 2000 records that I need to update which link across several tables.
Can someone tell me how I can link this simple query to something else so I can basically execute several stored procedures, all in the same script? But only affecting the records returned by my simple query?
Apologies, that probably sounds as clear as mud!
*EDIT - MORE DETAIL *
So here is my Select query:
SELECT [MembershipTermID]
,[MemberStatusProgKey]
,[StartDate]
,[EndDate]
,[AdditionalDiscount]
,[EntryDateTime]
,[UpdateDateTime]
,[MembershipID]
,[AgentID]
,[PlanVersionID]
,[ForceThroughReference]
,[IsForceThrough]
,[NextTermPrePaid]
,[IsBillingMonthly]
,[CICSMEMBERNUM]
,[CICSHISTORY]
,[TMPSeqNoColumn]
,[LastPaymentDate]
,[PaidToDate]
,[IsIndeterminate]
,DATEDIFF(MONTH, PaidToDate, GETDATE()) as MonthsDifference
,dbo.FullMonthsSeparation (PaidToDate, GETDATE())
FROM [Apollo].[dbo].[MembershipTerm]
WHERE MemberStatusProgKey='DORMANT'
AND IsBillingMonthly=1
AND dbo.FullMonthsSeparation (PaidToDate, GETDATE()) >= 2
So using the rows that this returns I want to exec several stored procedures to update everything I need to in the database which would be affected by changing these rows. An example of one stored procedure is below, I think I will need to execute about 10 of these if not more:
USE [Apollo]
GO
/****** Object: StoredProcedure [dbo].[spCancellationDetailInsert] Script Date: 01/10/2012 10:21:50 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
/* ************************* INSERT *************************/
/* Auto Generated 11/29/2006 7:28:53 PM by Object Builder */
/* ************************* INSERT *************************/
ALTER Procedure [dbo].[spCancellationDetailInsert]
#StampUser char (10),
#CancellationDetailID int,
#RefundAmount float,
#OldEndDate datetime,
#EffectiveDate datetime,
#CancelDate datetime,
#ReasonCodeProgKey nvarchar (50)
As
/* insert CancellationDetail record */
Insert [CancellationDetail]
(
RefundAmount,
OldEndDate,
EffectiveDate,
CancelDate,
ReasonCodeProgKey
)
Values
(
#RefundAmount,
#OldEndDate,
#EffectiveDate,
#CancelDate,
#ReasonCodeProgKey
)
If ##Error <> 0 GoTo InsertErrorHandler
/* save the key of the new row created by the insert */
Select #CancellationDetailID = Scope_Identity()
/* add audit record */
Insert CancellationDetailAudit
(StampUser,
StampDateTime,
StampAction,
CancellationDetailID,
RefundAmount,
OldEndDate,
EffectiveDate,
CancelDate,
ReasonCodeProgKey)
Values
(#StampUser ,
GetDate() ,
'I',
#CancellationDetailID,
#RefundAmount,
#OldEndDate,
#EffectiveDate,
#CancelDate,
#ReasonCodeProgKey)
If ##Error <> 0 GoTo AuditInsertErrorHandler
Select
CancellationDetailID = #CancellationDetailID
Return (0)
InsertErrorHandler:
Raiserror ('SQL Error whilst inserting CancellationDetailrecord: Error Code %d',17,1,##Error)
With Log
Return (99)
AuditInsertErrorHandler:
Raiserror ('SQL Error whilst inserting audit record for CancellationDetailInsert: Error Code %d',17,1,##Error)
With Log
Return (99)
If you're asking what I think you are -
Stored procedures can contain (pretty much) any valid SQL statement. This includes returning multiple results sets, performing multiple updates and calling other stored procedures.
For example:
CREATE PROCEDURE usp_Sample AS
SELECT * FROM INFORMATION_SCHEMA.COLUMNS
SELECT * FROM INFORMATION_SCHEMA.TABLES
UPDATE Users SET Active = 0 WHERE ExpiredDate < GetDate()
SELECT Active, COUNT(*) FROM Users GROUP BY Active
EXEC usp_Sample2
GO
Obviously that's a rather artificial example, but assuming all the objects existed it'd run perfectly well.
In order to perform more queries at the same time you just need to append them after your select.
So you can do
Select *
From table1
Select *
From table2
Select *
From table3
as many times as you want and they'll all execute independently.
If you want to UPDATE based on a SELECT you usually do something like:
UPDATE table1
WHERE ID IN (SELECT ID FROM TABLE2)
with regards to your stored procedures it would help if you posted more details.
how to write a if else statement to insert query for showing the error in stored procedure
below is my stored procedure.I want to show a error message when already inserted student_id is insert again..Student_id is primary key so its show error in my code but i dono how to get that error and show ....how to do friends plz help me.....
ALTER PROCEDURE [dbo].[spinsertstudentapplication]
-- Add the parameters for the stored procedure here
#Student_id nvarchar(50),
#Select_Country nvarchar(50),
#Select_State nvarchar(50),
#Select_Franchise nvarchar(50),
#Select_Sensei nvarchar(50),
#Enter_Student_Name nvarchar(50),
#Enter_Student_Address nvarchar(50),
#Students_Father_Name nvarchar(50),
#Student_DOB datetime,
#Gender bit,
#Group nvarchar(50),
#Enter_Kyu nvarchar(50)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
insert into StudentApplication(Student_id,Select_Country,Select_State,Select_Franchise,Select_Sensei,Enter_Student_Name,Enter_Student_Address,Students_Father_Name,Student_DOB,Gender,[Group],Enter_Kyu)values(#Student_id,#Select_Country,#Select_State,#Select_Franchise,#Select_Sensei,#Enter_Student_Name,#Enter_Student_Address,#Students_Father_Name,#Student_DOB,#Gender,#Group,#Enter_Kyu)
END
You can add an output parameter(int/bit) in Sp and set value to that parameter whether record exist or not. Then you can check the output parameter value in Front End. Below I've added an output parameter #RecordExist as bit and setting the value 1 when record already exists otherwise setting 0. In front end you can get the Parameter value from SqlCommand after executing the Sp. (SqlCommand.Parameters["#RecordExist"].Value)
ALTER PROCEDURE [dbo].[spinsertstudentapplication]
-- Add the parameters for the stored procedure here
#Student_id nvarchar(50),
#Select_Country nvarchar(50),
#Select_State nvarchar(50),
#Select_Franchise nvarchar(50),
#Select_Sensei nvarchar(50),
#Enter_Student_Name nvarchar(50),
#Enter_Student_Address nvarchar(50),
#Students_Father_Name nvarchar(50),
#Student_DOB datetime,
#Gender bit,
#Group nvarchar(50),
#Enter_Kyu nvarchar(50),
#RecordExist bit output -- newly added parameter
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
If Exists (Select * from StudentApplication where Student_id = #Student_id)
Begin
Select #RecordExist = 1
return
End
Else
Begin
insert into StudentApplication (Student_id, Select_Country, Select_State, Select_Franchise, Select_Sensei, Enter_Student_Name, Enter_Student_Address, Students_Father_Name, Student_DOB, Gender, [Group], Enter_Kyu)
Select #Student_id, #Select_Country, #Select_State, #Select_Franchise, #Select_Sensei, #Enter_Student_Name, #Enter_Student_Address, #Students_Father_Name, #Student_DOB, #Gender, #Group, #Enter_Kyu
Select #RecordExist = 0
return
End
END
As Dean 'codeka' Harding mentioned in the comment, it would be helpful to know how you're calling the stored proc. But here are some general suggestions.
First, a common convention for stored procedures is to return 0 on success and non-zero value on error (you can use an output parameter for error codes, but it's kind of redundant).
Second, before attempting to insert a value, you should check if it already exists. E.g. here is some pseudo code:
if exists (select 1 from StudentApplication where Student_ID = #Student_ID)
begin
raiserror('Student ID already exists.', 16, 1)
return 1 -- Your caller would need to know that 1 identifies existing record
end
Notice that in this example, T-SQL code exits after calling raiserror, so you need to handle this as an exception if you call the stored proc from C#/VB.NET/etc. Alternatively, you can omit the raiserror call and just have the stored proc return an expected (by the client) error code.
Then there is still a minor possibility that a duplicate would be inserted, but I think that it would generate a fatal error that you would need to handle in the client code (error handling is dependent on the client; in C#, you will probably get a SqlException which you can query for specific code).
Another option would be to put a transaction around the code that checks for existing record and then inserts a new one.
If you want to handle error in C#, you need to take care of two things. First, check the return code and process non-zero values accordingly (the C# client and stored proc have to agree on the meaning of each error code). Then, you also need to handle SqlExceptions. The State and Number properties of the SqlException object can help you identify the problem. Keep in mind that for error messages defined on the fly (as in my example), Number will alway return 50,000 (I think).
You can add an output parameter at the top of your SP:
#ErrorOutput INT OUTPUT
Then add:
IF EXISTS (SELECT 1 FROM StudentApplication WHERE Student_id=#Student_id)
SET #ErrorOutput = -1;
RETURN #ErrorOutput;
ELSE
-- Insert statement
We have an C# application which posts to a database which is replicated to another database (using merge-replication) and has one custom resolver which is a stored procedure.
This was working fine under SQL Server 2000 , but when testing under SQL Server 2005 the custom resolver is attempting to change any empty varchar columns to be nulls (and failing cos this particular column does not allow nulls).
Note that these varchar fields are not the ones which cause the conflict as they are current empty on both databases and are not being changed and the stored procedure does not change them (all it is doing is attempting to set the value of another money column).
Has anyone come across this problem, or has example of a stored procedure which will leave empty strings as they are?
The actual stored procedure is fairly simply and and re-calculates the customer balance in the event of a conflict.
ALTER procedure [dbo].[ReCalculateCustomerBalance]
#tableowner sysname,
#tablename sysname,
#rowguid varchar(36),
#subscriber sysname,
#subscriber_db sysname,
#log_conflict INT OUTPUT,
#conflict_message nvarchar(512) OUTPUT
AS
set nocount on
DECLARE
#CustomerID bigint,
#SysBalance money,
#CurBalance money,
#SQL_TEXT nvarchar(2000)
Select #CustomerID = customer.id from customer where rowguid= #rowguid
Select #SysBalance = Sum(SystemTotal), #CurBalance = Sum(CurrencyTotal) From CustomerTransaction Where CustomerTransaction.CustomerID = #CustomerID
Update Customer Set SystemBalance = IsNull(#SysBalance, 0), CurrencyBalance = IsNull(#CurBalance, 0) Where id = #CustomerID
Select * From Customer Where rowguid= #rowguid
Select #log_conflict =0
Select #conflict_message ='successful'
Return(0)
You have a few options here, each are a bit of a workaround from what my research seems to show is an issue with SQL Server.
1- Alter this statement: Select * From Customer Where rowguid= #rowguid to explicitly mention each of the columns, and use an "isNull" for the offending fields
2- Alter the column in the table to add a default constraint for ''. What this will do, is if you attempt to insert a 'null', it will replace it with the empty string
3- Add a 'before insert' trigger which will alter the data before the insert, to not contain a 'null' anymore
PS: Are you positive that the replication system has that column marked as "required"? I think if it is not required, it will insert 'null' if no data exists.