SQL Server 2008 Function for Current Academic Year - sql-server-2008-r2

I work at a college in London and for some reason no one has added a function to call the current academic year within our SQL Server 2008 instance. I had done this successfully in the past on an Oracle server so I thought, how hard can it be, right?
It is only after a failed attempt of my own I realize it isn't as easy as I thought it would be.
My code so far:
USE [DashboardData]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [fea].[current_academic_year]
()
RETURNS int
AS
BEGIN
DECLARE #Result as int
IF Month(GETDATE())<8
SET #Result= Year(GETDATE())-1
ELSE
SET #Result = Year(GETDATE())
RETURN #Result
END
Now, that looks simple enough to me - and it compiles. However, when I call the function in a simple query (on the same database):
Select fea.[current_academic_year]
I get the following error message:
Msg 4104, Level 16, State 1, Line 1 The multi-part
identifier "FEA.current_academic_year" could not be bound.
Having hardly ever used SQL Server before and after consulting Google, work-colleagues (who stare off into the distance) and former colleagues I am still without an answer. How and why is this happening?

Change:
Select fea.[current_academic_year]
To this:
SELECT [fea].[current_academic_year]()
You are missing (), remember this is a scalar-valued function, not a column name.

Related

SQL scalar function msg 107 after upgrade to 2019

I have a really simple scalar function with the following code:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[GetNDate_YYYYMM]
(
#InputDate DATETIME
)
RETURNS INT
AS
BEGIN
DECLARE #RIS AS INT
SET #RIS=NULL
IF (#InputDate IS NOT NULL) SET #RIS=(YEAR(#InputDate)*100)+(MONTH(#InputDate))
USCITA:
RETURN #RIS
END
This function has worked for years in SQL 2012 but now I have migrated the function to SQL 2019 I get the following message:
Msg 107, Level 15, State 1, Procedure GetNDate_YYYYMM, Line 1 [Batch Start Line 0]
The column prefix 'DT0' does not match with a table name or alias name used in the query.
In reality if I run a select on this function from the SQL management studio (and not during a stored procedure, where I first noticed the problem) I get this message only on the first run and then it doesn't appear until I reconnect to the DB.
Thanks for the help,
James
This looks like a scalar UDF inlining bug in 2019 RTM that has been fixed already in some cumulative update (quite likely CU2 as that fixed the below issue and you have such a label)
UDFs referencing labels without an associated GOTO command return
incorrect results (added in Microsoft SQL Server 2019 CU2)
It looks like some internal error that is mistakenly returned to the client but not actually treated as an error.
For the following SQL
BEGIN TRY
SELECT [dbo].[GetNDate_YYYYMM]('1900-01-01') AS FunctionResult
OPTION (RECOMPILE)
SELECT 'After UDF' AS Message
END TRY
BEGIN CATCH
SELECT 'In Catch' AS Message
END CATCH
The output with "Results to Text" selected is
Msg 107, Level 15, State 1, Procedure GetNDate_YYYYMM, Line 1 [Batch Start Line 21]
The column prefix 'DT0' does not match with a table name or alias name used in the query.
Msg 107, Level 15, State 1, Procedure GetNDate_YYYYMM, Line 1 [Batch Start Line 21]
The column prefix 'DT0' does not match with a table name or alias name used in the query.
FunctionResult
--------------
190001
Message
---------
After UDF
So the function result is returned successfully after the error message and execution continues without the CATCH block being reached.
Some possible resolutions to this
remove the problem label (USCITA:)
add INLINE=OFF to the function to disable inlining
upgrade to the latest CU to get the latest bug fixes
You could also avoid using a function :P
This returns the same thing :)
DECLARE #InputDate DATETIME = GETDATE()
SELECT FORMAT(#InputDate,'yyyyMM')
--202006
Interesting bug. I ran on my local SQL2019, same error.
Microsoft SQL Server 2019 (RTM) - 15.0.2000.5 (X64) Sep 24 2019
13:48:23 Copyright (C) 2019 Microsoft Corporation Developer Edition
(64-bit) on Windows 10 Pro 10.0 (Build 15063: )
The column prefix 'DT0' does not match with a table name or alias name
used in the query.
but db<>fiddle works fine https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=100c7cb025acfdeaa3fbf1093e91102b

TRY..CATCH Error_Line()....line

I'm looking at this example provided by MS as I'm trying to learn Try...Catch. I understand the syntax and Output (for the most part) but I have one question:
The Output will show the Error_Line as '4'. This is fine but if I remove the line break between GO and BEGIN TRY it'll show the Error_Line as '3'. I just want to understand the logic here.
What I imagine is happening is that SQL Server is counting the lines by beginning the batch immediately after GO, even if that line is blank but I do not know this for certain. Can anyone clarify? If that theory is correct, wouldn't that make finding errors difficult if scripts are written with line breaks like this?
-- Verify that the stored procedure does not already exist.
IF OBJECT_ID ( 'usp_GetErrorInfo', 'P' ) IS NOT NULL
DROP PROCEDURE usp_GetErrorInfo;
GO
-- Create procedure to retrieve error information.
CREATE PROCEDURE usp_GetErrorInfo
AS
SELECT
ERROR_NUMBER() AS ErrorNumber
,ERROR_SEVERITY() AS ErrorSeverity
,ERROR_STATE() AS ErrorState
,ERROR_PROCEDURE() AS ErrorProcedure
,ERROR_LINE() AS ErrorLine
,ERROR_MESSAGE() AS ErrorMessage;
GO
--Line 1
BEGIN TRY --Line 2
-- Generate divide-by-zero error. --Line 3
SELECT 1/0; --Line 4
END TRY
BEGIN CATCH
-- Execute error retrieval routine.
EXECUTE usp_GetErrorInfo;
END CATCH;
You can't really rely on ERROR_LINE(), especially when the error is thrown in internal stored procedure or there is dynamic T-SQL statement which is executed.
But do you really need the exact error line?
in real production code, the fix for the line causing the error may not be so obvious as in your example;
it will be better to debug the stored procedure or the function with the corresponding input parameter in order to reproduce the error
In this way it will be easier to fix an issue. In order to debug a SQL routine:
just script it
remove the drop and create stuff
add declare in front of the input parameters and initialized them with the values causing the error
Basically, instead of the exact error line (which can be easily fine having the correct input parameters and executing the routine) you may found useful two things:
which routing is causing the error (for example, you can add additional parameter to user usp_GetErrorInfo SP which is yielding the SP name as well
the input parameters which are causing the error (this can be done using separated table for logging the errors in the CATCH clause - you simple insert the input parameters in the table and information about the error)
Having this information, it will be easy to reproduce and then fix an issue (in many cases).

I can create a stored procure with invalid user defined function names in it

I just noticed that I could alter my stored procedure code with a misspelled user defined function in it.
I noticed that at 1st time I execute the SP.
Is there any way to get a compile error when an SP include an invalid user-defined function name in it?
At compile time? No.
You can, however, use some of SQL's dependency objects (if using MS SQL) to find problems just after deployment, or as part of your beta testing. Aaron Bertran has a pretty nice article rounding up the options, depending upon the version of SQL Server.
Here is an example using SQL Server 2008 sys object called sql_expression_dependencies
CREATE FUNCTION dbo.scalarTest
(
#input1 INT,
#input2 INT
)
RETURNS INT
AS
BEGIN
-- Declare the return variable here
DECLARE #ResultVar int
-- Add the T-SQL statements to compute the return value here
SELECT #ResultVar = #input1 * #input2
-- Return the result of the function
RETURN #ResultVar
END
GO
--Fn Works!
SELECT dbo.ScalarTest(2,2)
GO
CREATE PROCEDURE dbo.procTest
AS
BEGIN
SELECT TOP 1 dbo.scalarTest(3, 3) as procResult
FROM sys.objects
END
GO
--Sproc Works!
EXEC dbo.procTest
GO
--Remove a dependency needed by our sproc
DROP FUNCTION dbo.scalarTest
GO
--Does anything have a broken dependency? YES
SELECT OBJECT_NAME(referencing_id) AS referencing_entity_name,
referenced_entity_name, *
FROM sys.sql_expression_dependencies
WHERE referenced_id IS NULL --dependency is missing
GO
--Does it work? No
EXEC dbo.procTest
GO

How to do error handling in Stored procedure

I am using Sybase ASE 12.5 at the moment. I have a code below:
create procedure test_launcher_fail_wrapper
as
begin
select convert(numeric(2),1234345)
if ##error != 0
begin
select "SP failed to execute"
return 1
end
end
Here, I am trying to convert a very large value/amount (1234345) to Numeric size 2. Which is not possible and it generates error.
Questions:
Is having ##error useful here? I ran this SP and it never went into
error handling
How to error handle these kind of scenarios?
I treat error handling in procs similarly to error handling in applications -- if there's an opportunity for you to contribute some actual value by handling the error, then by all means, do so, but if you can't really do anything to help, then you're better off just letting it go.
As an example of adding value, I've got one or two procs that add contextual information in the error message, like a list of ID values that conflict with an update operation. In this particular case, I know that the upstream consumer of the proc will log this error, and the text will be available to an operator who will find this information valuable when debugging the problem. I also know that while this condition is a real error, it's been known to happen from time-to-time, and the effort to format the error is worthwhile.
Does this catch your error?
create procedure test_launcher_fail_wrapper
as
begin
declare #database_err int
set #database_err = 0
select convert(numeric(2),1234345)
set #database_err = ##error
if #database_err <> 0
begin
PRINT 'SP failed to execute'
return 1
end
end
##error is the way to go but beware since:
Every Transact-SQL statement, including print statements and if tests, resets ##error, so the status check must immediately follow the batch for which success is in question.
As for a suggestion on how to handle error management in similar scenarios, have you considered using raiserror ?
An example:
create procedure proc1 as
begin
select convert(numeric(2),1234345)
if ##error <> 0
begin
raiserror 20001 "Error during convert in proc1"
return 1
end
end

How to trace T-SQL function calls

I'm trying to debug a rather complicated formula evaluator written in T-SQL UDFs (don't ask) that recursively (but indirectly through an intermediate function) calls itself, blah, blah.
And, of course, we have a bug.
Now, using PRINT statements (that can then be read from ADO.NET by implementing a handler for the InfoMessage event), I can simulate a trace for stored procedures.
Doing the same for UDF results in a compile time message:
Invalid use of side-effecting or time-dependent operator in 'PRINT' within a function.
I get the message (PRINT does some stuff like resetting ##ROWCOUNT which definitly is a no-no in UDFs, but how can I trace through the calls? I want to have this trace printed out, so I can study it without getting distracted by stepping through the calls in the debugger...
EDIT: I have tried to use the SQL Profiler (this was a first time one for me), but I can't figure out what to trace for: Although I can get the trace to output the queries sent to the database, they are opaque in the sense that I can't drill down to the Expression-UDFs called: I can trace the actual Stored Procedure invoked, but the UDFs called by this procedure are not listed. Am I missing something? I guess not...
EDIT #2: Allthough the (auto-)accepted answer does trace the function calls - very helpful, thanks - it does not help in finding out what parameters were passed to the function. This, of course, is essential in debugging recursive functions. I will post if I find any sollution at all...
Why not use SQL Profiler with statement level events added?
Edit: Add events for Stored Procedures : SP:Stmt Starting or SP:Stmt Completed
Use variables to debug if needed, i.e. set #debug='i am here'; UDF's, while not technically stored procedures, will get traced with the statement level events.
In the SQL profiler, you need: SP:Starting, SP:StmtStarting, SP:Completed, SQL:BatchStarting. Then, you get every entry, exit from the functions/stored procedures.
alter FUNCTION [dbo].[ufn_mjf](#i numeric(10))
RETURNS numeric(20)
AS
BEGIN
declare #datapoint varchar(10)
set #datapoint = 'hello world'
return #i
END
go
drop table foo
go
create table dbo.foo ( foo_id numeric(10))
go
delete from foo
insert into foo ( foo_id ) values ( 1 )
insert into foo ( foo_id ) values ( 2 )
select foo_id, dbo.ufn_mjf(foo_id) from foo
with this, I get:
SQL:BatchStarting alter FUNCTION [dbo].[ufn_mjf](#i numeric(10))
SQL:BatchStarting drop table foo
SQL:BatchStarting create table dbo.foo ( foo_id numeric(10))
SQL:BatchStarting delete from foo
insert into foo ( foo_id ) values ( 1 )
insert into foo ( foo_id ) values ( 2 )
select foo_id, dbo.ufn_mjf(foo_id) from foo
SP:Starting select foo_id, dbo.ufn_mjf(foo_id) from foo
SP:StmtStarting set #datapoint = 'hello world'
SP:StmtStarting return #i
SP:Completed select foo_id, dbo.ufn_mjf(foo_id) from foo
SP:Starting select foo_id, dbo.ufn_mjf(foo_id) from foo
SP:StmtStarting set #datapoint = 'hello world'
SP:StmtStarting return #i
SP:Completed select foo_id, dbo.ufn_mjf(foo_id) from foo
is that enough for you?
This looks like what you need but it's only available in team/pro versions of Visual Studio.
Use SQL Profiler, I recommend you go overboard on adding events the first time around which will let you get a feel for what you need. Without testing I would add the events for SP:StmtStarted (or Completed or both), SQL:StmtStarted (again Completed or Both).
I second the SQL Profiler suggestion. Take some time to set it up so that only the events you are interested in are logged to cut output size. You can output the trace to a file - I have frequently then loaded that file back into a table to enable analysis. (extremely handy for performance analysis, though no doubt someone will tell me that 2008 has this all built in somwehere...)
Sometimes you won't have permissions to run SQL Profiler as it does slow the server down - ask your DBA to grant you permission on your Dev server. They shouldn't have any problems with that.
Well in the past I have had to take typical values that would be in the UDF and then run just the udf part in a separate query window as straight SQL not a udf using the typical values as variables set with a declare and a set statement. If it is run from a table instead of having only one value, I would set up a temp table or table variable with the input values and then run them through the sql in the UDF (but again as straight SQL not a UDF) through a cursor. By running straight SQL you could have print statements in it to see what is happening. I know this is a pain, but it works. (I go through a simliar process when creating/debugging triggers, setup #inserted and #deleted with my test values and then test the code I intend to put into the trigger, then global replace the # with nothing and add the create trigger code.)
Maybe you can use SQL CLR to do the tracing as described here
How to log in T-SQL
Can you take your function, and make a second copy of it, but returning a table type with an additional column for your debug information.
For example, the mySum function below
CREATE FUNCTION mySum
(
#param1 int,
#param2 int
)
RETURNS INT AS
BEGIN
DECLARE #mySum int
SET #mySum = #param1
SET #mySum = #mySum + #param2
RETURN #mySum
END
GO
SELECT dbo.mySum(1, 2)
Would turn into
CREATE FUNCTION mySumDebug
(
#param1 int,
#param2 int
)
RETURNS #myTable TABLE
(
[mySum] int,
[debug] nvarchar(max)
)
AS
BEGIN
DECLARE #debug nvarchar(max)
SET #debug = 'Declare #mySum variable. '
DECLARE #mySum int
SET #debug = #debug + 'Set #mySum = #param1(' + CONVERT(nvarchar(50), #param1) + ') '
SET #mySum = #param1
SET #debug = #debug + 'Add #param2(' + CONVERT(nvarchar(50), #param2) + ') to #mySum(' + CONVERT(nvarchar(50), #mySum) + ') '
SET #mySum = #mySum + #param2
SET #debug = #debug + 'Return #mySum variable. '
INSERT #myTable (mySum, debug) VALUES (#mySum, #debug)
RETURN
END
GO
SELECT mySum, debug FROM dbo.mySumDebug(1, 2)
Not an ideal solution, but useful just to return some text to help track down a bug.
I use SQL SPY which does what you are looking for and more.
SQL SPY
SQL SPY Feature Documentation
SQL SPY's Incoming SQL Sniffer shows the incoming SQL code of each connection (Includes DDL and DML statement tracking)
This feature is designed for MS SQL Server 2005\2008, but will work with MS SQL Server 2000 in limited scope. It has the ability to record and report on Incoming SQL. How to use the features: See
Disclosure: I am part of the SQL SPY team.