I have a rather simple SQL Server stored procedure with 2 input parameters, a name which is nvarchar(100) and results which is nvarchar(max).
The stored procedure just does an insert - nothing more.
INSERT INTO TableX (name, results)
VALUES (#Name, #Results)
Results is a | delimited file serialized to a string. Sometimes when this stored procedure is run, the row in the table only has the name and results is blank. No SQL errors. It isn't just because of length of results because some of the calls that work are longer then some of the calls that end up empty. I did catch that some of the entries contain single quotes so I stripped out single quotes and that didn't help.
Any ideas?
Here is the stored procedure:
CREATE PROCEDURE [dbo].[tempdata_SaveMarketStatsToCache]
(#ScreenName NVARCHAR(100),
#Results NVARCHAR(MAX))
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO TempData_MarketStatsCache (MarketStatsKey, Results)
VALUES (#ScreenName, #Results)
END
Stored Procedure is called from .NET. SQLDbType.NVarchar with length of -1
UPDATE: I suspect it is the size of the string being passed. The smallest string that is failing is just under 250K characters. This is OK for NVARCHAR(max), but it seems that something happens during the INSERT.
UPDATE 2: Turns out it was an issue with SQL Server Managment Studio. I was right clicking on table and choosing edit 200 rows. That showed the column as empty for the rows with the large results (over 200K characters). If I simply ran a query the column values showed.
Turns out it was a SQL Server Management Studio issue making it seem like data wasnt inserted. See Update 2 in the question.
Related
Ambiguous thread name, I apologize. I am not new to SQL, but I'm new to coding longer stored procedures so I don't deal with variables much outside of passing through maybe a table name or returning row count, etc.
I have a stored procedure that is executing an insert from a staging table to a fact table. There are a couple type casts in the insert.
If the insert fails due to a typecast. Is there any way to return the name of the column that failed, along with what the failed value was? How would I code that? I know that Try_parse would make it so the stored procedure doesn't fail on type cast failure, but I want to be able to pass back exactly what column and value failed.
I show an example here:
Create Procedure dbo.Example_Insert
#updateUser varchar(255)
As
Begin
Insert Into dbo.Energy_Costs (Energy_Cost_Id, Project_Id, Propane_Cost_Dollars,
Electricity_Cost_Dollars, Fuel_Savings_Evaluator)
Select
Next Value For energy_cost_id,
r.project_id,
Cast(r.propane_cost_dollars As Decimal(18,2)),
Cast(r.electricity_cost_dollars As Decimal(18,2)),
#update_user fuel_savings_evaluator
From
staging_table r
return ##ROWCOUNT
end
You can use CURSOR in sql then insert one line at a time. When insert fail return value currently row error.
I hope my idea suitable with you.
SQL Server 2012. Based on the stored procedure variable length; input value is automatically trimmed and inserted to the table.
Example : I am passing a variable #name varchar(10):
#name VARCHAR(10) = null
However, on inserting the record which is more than 10 characters through stored procedure, record get inserted by trimming the characters to first 10 digit.
I am expecting to get the error exception such as
String or binary data would be truncated
How should I throw an exception error from the stored procedure?
CREATE TABLE tbl_test
(
[ID] INT,
[NAME] VARCHAR(10),
)
GO
CREATE PROCEDURE usp_test
(#name VARCHAR(10) = NULL)
AS
SET ANSI_WARNINGS ON
BEGIN
INSERT INTO tbl_test
VALUES (1, #name)
INSERT INTO tbl_test([ID], [NAME])
VALUES (2, #name)
END
The behavior depends on the ANSI_WARNINGS session setting.
With ANSI_WARNINGS ON (the default in modern Microsoft SQL Server APIs), you will get the expected "string or binary data would be truncated" error when data are inserted into a column with a shorter length. ANSI_WARNINGS ON is implicitly set by ANSI_DEFAULTS ON.
With ANSI_WARNINGS OFF, the data will be silently truncated.
However, when you pass a parameter value that is longer than the defined parameter length, the value is truncated without error or warning regardless of the session setting. This is documented behavior that may not be what one expects:
SET ANSI_WARNINGS is not honored when passing parameters in a
procedure, user-defined function, or when declaring and setting
variables in a batch statement. For example, if a variable is defined
as char(3), and then set to a value larger than three characters, the
data is truncated to the defined size and the INSERT or UPDATE
statement succeeds.
so it is important to ensure supplied values do not exceed the defined parameter length.
One way to get this to work is to make sure your parameters are longer than the table column. Doesn't have to be much longer - one character would be enough. Then if you get passed a longer string, it will still be longer inside the stored procedure.
At that point you can either test for the length being too long and raise your own error, or if you try and put it in the table you'll get an error anyway. Either way, at least you'll know.
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
I created a stored proc that creates a temp table, inserts, selects then drops. Executing the stored proc within SQL Server Management Studio works fine and gives the expected result.
CREATE PROCEDURE usp_TempTableTest
-- Add the parameters for the stored procedure here
#color VARCHAR(10)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
CREATE TABLE #tmptable (
color VARCHAR(10)
)
INSERT INTO #tmptable (color) VALUES (#color)
SELECT color FROM #tmptable
DROP TABLE #tmptable
END
GO
However, when creating in the Import/Export tool and using that stored proc as the data source, it gives me the error:
Invalid object name '#tmptable'.
Any idea why this would happen? If I change it to a table variable it seems work fine with Import/Export, but I don't understand why it is not working with a temp table.
When I run a mimicked stored procedure, like yours above, in SSMS, I can get the data returned like you mentioned in the procedure. However, if I try the #tmptable, like you did, I also get the same error because the DROP TABLE removes it. From what I can tell, the import/export is basically a final INSERT process. The reason it works with the table variable is because the data still exist on the final insert; in the case of the DROP TABLE, it does not. For instance, when I remove the DROP TABLE, it works.
I might be wrong here, but it seems the logic when it's an import or export in the case of the above procedure is
INSERT data
SELECT data
DROP data
INSERT (import/export): this generates the "Invalid object name tmptable'"
With the variable (or no DROP), it's
INSERT data
SELECT data
INSERT (import/export)
In the second case, the data still exist. In the first case, they're gone. One way around it if you want to use the #tmptable, start your code with:
IF OBJECT_ID('tempdb..#tmptable') IS NOT NULL DROP TABLE #tmptable
Put "SET FMTONLY OFF;" right above "SET NOCOUNT ON"
http://msdn.microsoft.com/en-us/library/ms173839.aspx
I have written stored procedure which has 2 Insert queries and 1 Update query inside it. Of all these,either insert queries or update query are executed at a time. Now my problem is to get ROWCOUNT in each case. Say suppose if insert operations are executed,then I want stored procedure to return ##ROWCOUNT to the calling application, so that the application will be aware of whether the required operations executed correctly or not. Can anyone suggest/tell me how can I get the rows affected from the stored procedure?
Use Output parameters in your stored procedures to return the RowCount of your inserts / updates.
Refer MSDN link for more information on how to use Output params
You can have multiple output params so you can have 2 different output params one each for your insert and the 3rd for your update statement.
Example:
CREATE PROCEDURE GetEmployeeData
#employeeID INT,
#managerID INT **OUTPUT**
AS
BEGIN
....
....
Additionally, you can always concatenate the rowcounts of your 2 Inserts / Update using delimiters and return them as one value eg: "10;0" - However that is the old fashioned and "I would not recommend" approach.
Also, you could create a table variable and return the table with rows = number of Inserts / updates and the value of the column = RowCount affected.