I need to write a SQL function to return column specific values, so I am passing the column name as a parameter to SQL-function to return its corresponding value. Here is the sample function
CREATE FUNCTION GETDATETIME(#columnName VARCHAR(100))
RETURNS DATETIME
AS
BEGIN
RETURN (SELECT TOP 1.#columnName FROM TEST_TABLE )
END
GO
The above function seems to be straight forward, but it not working as expected.
And when I execute the function
SELECT dbo.GETDATETIME('DATETIMECOLUMNNAME')
I am getting this error:
Conversion failed when converting date and/or time from character string.
Can someone help me to identify the issue?
For that you need to write dynamic sql. But Functions won't support execute statement.
So you need to write multiple If conditions for each column.
CREATE FUNCTION GETDATETIME(#columnName VARCHAR(100))
RETURNS DATETIME
AS
BEGIN
DECLARE #RESULT DATETIME;
IF (#columnName = 'ABC')
Begin
SELECT TOP 1 #RESULT = [ABC] FROM TEST_TABLE
END
ELSE IF (#columnName = 'DEF')
Begin
SELECT TOP 1 #RESULT = [DEF] FROM TEST_TABLE
END
ELSE IF (#columnName = 'GHI')
Begin
SELECT TOP 1 #RESULT = [GHI] FROM TEST_TABLE
END
RETURN #RESULT
END
GO
Edit 2:
If your column always return Datetime, then you can do like below.
CREATE TABLE A_DUM (ID INT, STARTDATE DATETIME, ENDDATE DATETIME, MIDDLEDATE DATETIME)
INSERT INTO A_DUM
SELECT 1, '2019-07-24 11:35:58.910', '2019-07-28 11:35:58.910', '2019-07-26 11:35:58.910'
UNION ALL
SELECT 2, '2019-07-29 11:35:58.910', '2019-08-01 11:35:58.910', '2019-07-24 11:35:58.910'
And your function like below
CREATE FUNCTION GETDATETIME(#columnName VARCHAR(100))
RETURNS DATETIME
AS
BEGIN
DECLARE #RESULT DATETIME;
SELECT TOP 1 #RESULT = CAST(PROP AS DATETIME)
FROM A_DUM
UNPIVOT
(
PROP FOR VAL IN (STARTDATE, ENDDATE,MIDDLEDATE)
)UP
WHERE VAL = #columnName
RETURN #RESULT
END
GO
There's a workaround to this, similar to #Shakeer's answer - if you are attempting to GROUP BY or perform a WHERE on a column name, then you can just use a CASE statement to create a clause to match on specific column names (if you know them).
Obviously this doesn't work very well if you have many columns to hard-code, but at least it's a way to achieve the general idea.
E.g. with WHERE clause:
WHERE
(CASE
WHEN #columnname = 'FirstColumn' THEN FirstColumnCondition
WHEN #columnname = 'SecondColumn' THEN SecondColumnCondition
ELSE SomeOtherColumnCondition
END)
Or with GROUP BY:
GROUP BY
(CASE
WHEN #columnname = 'FirstColumn' THEN FirstColumnGroup
WHEN #columnname = 'SecondColumn' THEN SecondColumnGroup
ELSE SomeOtherColumnGroup
END)
No you cannot use dynamic sql in functions in SQL. Please check this link for more info link.
So it is not possible to achieve this by any function, yes you may use stored procedures with output parameter for same.
You may find this link for reference link.
Related
I have 800+ functions in my database. I would need to modify their source databases dynamically and create snapshots.
example of the function:
create function [schema1].[funTest1] (#param1 varchar(50))
returns table as
return
(
select * from [curr_database1].[schema1].[funTest1](#param1)
union
select * from [curr_database2].[schema1].[funTest1](#param1)
)
I want to change the script as:
create or alter function [schema1].[funTest1] (#param1 varchar(50))
returns table as return
(
select * from [new_database2].[schema1].[funTest1](#param1)
union
select * from [new_database3].[schema1].[funTest1](#param1)
)
basically, I got all the functions script using the sys.syscomments. I'm looking for an option to find and replace the database dynamically to create the snapshots.
How can I get it? Thank you!
Here is the sample code that I have developed for sharing. All the database in the functions starts with the same text(for ex. "curr"). Please share your thoughts. Thanks in advance!
create or alter proc test_proc as
begin
set nocount on
-- this piece of code has the new databases
if object_id('tempdb..#dbNames') is not null drop table #dbNames
create table #dbNames (dbName varchar(1000), id int)
insert into #dbNames(dbName, id) values ('new_database2', 1),('new_database3', 2)
insert into #dbNames(dbName, id) values ('new_database8', 3),('new_database9', 4)
-- this one has the sample functions
if object_id('tempdb..#dbFunctions') is not null drop table #dbFunctions
create table #dbFunctions (funText nvarchar(max))
insert into #dbFunctions (funText) values('create function [schema1].[funTest1] (#param1 varchar(50))
returns table as
return
(
select * from [curr_database1].[schema1].[funTest1](#param1)
union
select * from [curr_database2].[schema1].[funTest1](#param1)
)'),
('create function [schema2].[funTest2] (#param1 varchar(50), #param2 varchar(100))
returns table as
return
(
select * from [curr_database4].[schema2].[funTest2](#param1, #param2)
union
select * from [curr_database5].[schema2].[funTest2](#param1, #param2)
)')
-- declare variables and assign value for #frmStr variable (for testing purposes)
declare #str nvarchar(max)
declare #dbName varchar(100)
declare #frmStr varchar(100) = '[curr_database1]'
-- get the total count of the databases and the functions to iterate and replace the string
declare #dbCnt int = (select count(id) from #dbNames)
declare #fnCnt int = (select count(*) from #dbFunctions)
while #dbCnt > 0
begin
set #dbname = (select dbname from #dbnames where id = #dbcnt)
while #fnCnt > 0
begin
-- this is where I would need to replace the code
select #str = replace(funText, #frmStr, #dbName) from #dbFunctions
select #str
set #fnCnt = #fnCnt - 1
end
set #dbCnt = #dbCnt - 1
end
end
Your actual goal isn't clear, but to answer the question you asked, you can use REPLACE functions in the query to syscomments that you used to get the code in the first place:
REPLACE(
REPLACE([FunctionTextColumn],'curr_database1','new_database2')
,'curr_database2','new_database3'
)
Problem
A stored procedure is receiving list of variables and values, and the delimiter. This stored procedure needs to insert those in a table.
--Example table
create table #tempo
(
Variable1 int,
Variable2 int,
Variable3 int
)
These are the parameters to the stored procedure:
declare #variableList varchar(100)
declare #valueList varchar(100)
declare #separator char(1)
set #variableList = 'Variable1#Variable2#Variable3'
set #valueList = '1111#2222#3333'
set #separator = '#'
Result
What I want to achieve is this:
select * from #tempo
+---------+---------+---------+
|Variable1|Variable2|Variable3|
+---------+---------+---------+
|1111 |2222 |3333 |
+---------+---------+---------+
One way to do it
I can use a loop and build dynamic SQL but I want to avoid it. Other than the obvious reasons for not using dynamic SQL, the loop structure is hard to maintain, explain and testing can become an issue too.
Ideal way
I am thinking about a more elegant way to do this, for example with string_split or coalesce etc. But cannot figure out a way without using dynamic SQL or loops.
If you always have same set of column names then it is very easy to do with pivoting, but if columns are changing then you can use the same script with dynamically adjusted list of variables, provided as a parameter or from direct reading from temp table:
INSERT INTO #tempo SELECT *
FROM (
SELECT [value], rv = 'Variable' + CAST(Row_Number() OVER ( ORDER BY (SELECT 1)) as VARCHAR)
FROM STRING_SPLIT(#valueList,#separator)
) AS src
PIVOT (MAX([value]) FOR rv IN (Variable1,Variable2,Variable3)) AS pvt;
You can always try pivoting out the data. This is just the select, but could easily have an insert wrapped into it.
We use a split string with a row ID to allow matching of two split data sets. Function is :
CREATE FUNCTION [dbo].[Split] (#RowData NVARCHAR(MAX), #SplitOn NVARCHAR(5))
RETURNS #RtnValue TABLE (Id INT IDENTITY(1, 1), Data NVARCHAR(100))
AS
BEGIN
DECLARE #Cnt INT;
SET #Cnt = 1;
WHILE (CHARINDEX(#SplitOn, #RowData) > 0)
BEGIN
INSERT INTO #RtnValue (Data)
SELECT Data = LTRIM(RTRIM(SUBSTRING(#RowData, 1, CHARINDEX(#SplitOn, #RowData) - 1)));
SET #RowData = SUBSTRING(#RowData, CHARINDEX(#SplitOn, #RowData) + 1, LEN(#RowData));
SET #Cnt = #Cnt + 1;
END;
INSERT INTO #RtnValue (Data)
SELECT Data = LTRIM(RTRIM(#RowData));
RETURN;
END;
You can then join the two sets together to give some key value pairs, and from there pivot out the data to give the format you requested. If you replace the last select with a select from any of the previous cte's then you can see how the logic unfolds.
DECLARE #variableList VARCHAR(100);
DECLARE #valueList VARCHAR(100);
DECLARE #separator CHAR(1);
SET #variableList = 'Variable1,Variable2,Variable3';
SET #valueList = '1111, 2222, 3333';
SET #separator = ',';
WITH cteVar AS (SELECT Id, Data FROM dbo.Split(#variableList, #separator) )
, cteVal AS (SELECT Id, Data FROM dbo.Split(#valueList, #separator) )
, cteData AS
(SELECT cteVar.Data VariableData
, cteVal.Data ValueData
FROM cteVar
JOIN cteVal ON cteVal.Id = cteVar.Id)
, ctePivot AS
(SELECT *
FROM cteData
PIVOT ( MAX(ValueData)
FOR VariableData IN ([Variable1], [Variable2], [Variable3])) AS PivotTable)
SELECT *
FROM ctePivot;
This is quite a long approach to it but hopefully it well help you understand the steps involved. Its worth looking at the Pivot function in general anyway, its well documented.
How to achieve below requirement in SQL Server.
Data what I have:
and Expected output is:
Thanks,
Lawrance A
Assuming of your given data value:
'Message:"A",Level:"0",type:"log"'
'Message:"B",Level:"1",type:"log"'
select substring(ColumnA,10,1) Message,
replace(dbo.udf_GetNumeric(replace(replace(substring(ColumnA,20,5),'"',''),',','')),' ','')[type],
replace(replace(substring(ColumnA,29,250),'"',''),',','')[Log] from YourTable
GetNumeric here
I have created two function to achieve your desired output.
use below query by creating two function split and getVal
select
dbo.getVal(columnA,'Message') Message,
dbo.getVal(columnA,'Level') [Level],
dbo.getVal(columnA,'type') [type]
from TableA
Query for creating split and getVal function
Create function split(#s varchar(500),#splitWith varchar(20))
returns #RetTable Table(id int identity(1,1), Value varchar(200))
as
begin
if(CHARINDEX(#splitWith,#s)>0)
begin
set #s=#s+#splitWith
declare #len int =len(#s)
while charindex(#splitWith,#s)>0
begin
insert #RetTable values(SUBSTRING(#s,1,charindex(#splitWith,#s)-1))
set #s=SUBSTRING(#s,charindex(#splitWith,#s,1)+1,#len)
end
end
return
end
Create function getVal(#str varchar(500),#column varchar(200))
returns varchar(200)
as
begin
declare #ret varchar(200)
select #ret=value From dbo.split((select value From dbo.split('Message:"A",Level:"0",type:"log"',',') where value like +#column+'%'),':') where id=2
return replace(#ret,'"','')
end
I've created the following stored procedure:
ALTER PROCEDURE [dbo].[CountInJunction]
#Mod as nvarchar(10),
#Junction as nvarchar(10),
#PJ as nvarchar(10),
**#case as varchar(10)**,
#Date as varchar(20)
as
begin
declare #result as int
select #result = count(distinct CONCAT ([UCID],[CALLSEGMENT]))
from IVR_LINES
where MODULE = #Mod and DATE = #date
and EVENT_NAME = #Junction and **EVENT_VALUE in (#case)**
insert into [dbo].[MainJuncTable] values(#Mod,#PJ,#Junction,#case,#result,null,null,#date)
return #result
end
I would like to pass ('0','5') as #case.
for some reason, I get 0 as a result, which is not correct. Its seems that the SP doesn't interpret ('0','5') correctly.
I've been trying multiple combinations such as:
'0','5'
'0'+','+5''
'0,5'
etc..
nothing works.
Is there any way I can pass these chars correctly?
Thanks.
Send the values as a single string like ('0,5')
Then in where condition u need to split and select the values like,
where EVENT_VALUE in (select val from Split(#case,','))
Split is user defined function,you need to create before using it.
CREATE FUNCTION [dbo].[Split]
(
#delimited nvarchar(max),
#delimiter nvarchar(100)
) RETURNS #t TABLE
(
-- Id column can be commented out, not required for sql splitting string
id int identity(1,1), -- I use this column for numbering splitted parts
val nvarchar(max)
)
AS
BEGIN
declare #xml xml
set #xml = N'<root><r>' + replace(#delimited,#delimiter,'</r><r>') + '</r></root>'
insert into #t(val)
select
r.value('.','varchar(max)') as item
from #xml.nodes('//root/r') as records(r)
RETURN
END
GO
In every case, use this as your parameter value: '0,5'
But how to use it depends on the version of sql server you're using.
If you've got 2016, there's STRING_SPLIT. https://msdn.microsoft.com/en-us/library/mt684588.aspx
If you don't have it, you can create a function. See related stackoverflow posts: How to split a comma-separated value to columns
Or if you want rows: SQL query to split column data into rows
(See the higher rated recommendations in both of those.)
Is there a way to make a TSQL variable constant?
No, but you can create a function and hardcode it in there and use that.
Here is an example:
CREATE FUNCTION fnConstant()
RETURNS INT
AS
BEGIN
RETURN 2
END
GO
SELECT dbo.fnConstant()
One solution, offered by Jared Ko is to use pseudo-constants.
As explained in SQL Server: Variables, Parameters or Literals? Or… Constants?:
Pseudo-Constants are not variables or parameters. Instead, they're simply views with one row, and enough columns to support your constants. With these simple rules, the SQL Engine completely ignores the value of the view but still builds an execution plan based on its value. The execution plan doesn't even show a join to the view!
Create like this:
CREATE SCHEMA ShipMethod
GO
-- Each view can only have one row.
-- Create one column for each desired constant.
-- Each column is restricted to a single value.
CREATE VIEW ShipMethod.ShipMethodID AS
SELECT CAST(1 AS INT) AS [XRQ - TRUCK GROUND]
,CAST(2 AS INT) AS [ZY - EXPRESS]
,CAST(3 AS INT) AS [OVERSEAS - DELUXE]
,CAST(4 AS INT) AS [OVERNIGHT J-FAST]
,CAST(5 AS INT) AS [CARGO TRANSPORT 5]
Then use like this:
SELECT h.*
FROM Sales.SalesOrderHeader h
JOIN ShipMethod.ShipMethodID const
ON h.ShipMethodID = const.[OVERNIGHT J-FAST]
Or like this:
SELECT h.*
FROM Sales.SalesOrderHeader h
WHERE h.ShipMethodID = (SELECT TOP 1 [OVERNIGHT J-FAST] FROM ShipMethod.ShipMethodID)
My workaround to missing constans is to give hints about the value to the optimizer.
DECLARE #Constant INT = 123;
SELECT *
FROM [some_relation]
WHERE [some_attribute] = #Constant
OPTION( OPTIMIZE FOR (#Constant = 123))
This tells the query compiler to treat the variable as if it was a constant when creating the execution plan. The down side is that you have to define the value twice.
No, but good old naming conventions should be used.
declare #MY_VALUE as int
There is no built-in support for constants in T-SQL. You could use SQLMenace's approach to simulate it (though you can never be sure whether someone else has overwritten the function to return something else…), or possibly write a table containing constants, as suggested over here. Perhaps write a trigger that rolls back any changes to the ConstantValue column?
Prior to using a SQL function run the following script to see the differences in performance:
IF OBJECT_ID('fnFalse') IS NOT NULL
DROP FUNCTION fnFalse
GO
IF OBJECT_ID('fnTrue') IS NOT NULL
DROP FUNCTION fnTrue
GO
CREATE FUNCTION fnTrue() RETURNS INT WITH SCHEMABINDING
AS
BEGIN
RETURN 1
END
GO
CREATE FUNCTION fnFalse() RETURNS INT WITH SCHEMABINDING
AS
BEGIN
RETURN ~ dbo.fnTrue()
END
GO
DECLARE #TimeStart DATETIME = GETDATE()
DECLARE #Count INT = 100000
WHILE #Count > 0 BEGIN
SET #Count -= 1
DECLARE #Value BIT
SELECT #Value = dbo.fnTrue()
IF #Value = 1
SELECT #Value = dbo.fnFalse()
END
DECLARE #TimeEnd DATETIME = GETDATE()
PRINT CAST(DATEDIFF(ms, #TimeStart, #TimeEnd) AS VARCHAR) + ' elapsed, using function'
GO
DECLARE #TimeStart DATETIME = GETDATE()
DECLARE #Count INT = 100000
DECLARE #FALSE AS BIT = 0
DECLARE #TRUE AS BIT = ~ #FALSE
WHILE #Count > 0 BEGIN
SET #Count -= 1
DECLARE #Value BIT
SELECT #Value = #TRUE
IF #Value = 1
SELECT #Value = #FALSE
END
DECLARE #TimeEnd DATETIME = GETDATE()
PRINT CAST(DATEDIFF(ms, #TimeStart, #TimeEnd) AS VARCHAR) + ' elapsed, using local variable'
GO
DECLARE #TimeStart DATETIME = GETDATE()
DECLARE #Count INT = 100000
WHILE #Count > 0 BEGIN
SET #Count -= 1
DECLARE #Value BIT
SELECT #Value = 1
IF #Value = 1
SELECT #Value = 0
END
DECLARE #TimeEnd DATETIME = GETDATE()
PRINT CAST(DATEDIFF(ms, #TimeStart, #TimeEnd) AS VARCHAR) + ' elapsed, using hard coded values'
GO
If you are interested in getting optimal execution plan for a value in the variable you can use a dynamic sql code. It makes the variable constant.
DECLARE #var varchar(100) = 'some text'
DECLARE #sql varchar(MAX)
SET #sql = 'SELECT * FROM table WHERE col = '''+#var+''''
EXEC (#sql)
For enums or simple constants, a view with a single row has great performance and compile time checking / dependency tracking ( cause its a column name )
See Jared Ko's blog post https://blogs.msdn.microsoft.com/sql_server_appendix_z/2013/09/16/sql-server-variables-parameters-or-literals-or-constants/
create the view
CREATE VIEW ShipMethods AS
SELECT CAST(1 AS INT) AS [XRQ - TRUCK GROUND]
,CAST(2 AS INT) AS [ZY - EXPRESS]
,CAST(3 AS INT) AS [OVERSEAS - DELUXE]
, CAST(4 AS INT) AS [OVERNIGHT J-FAST]
,CAST(5 AS INT) AS [CARGO TRANSPORT 5]
use the view
SELECT h.*
FROM Sales.SalesOrderHeader
WHERE ShipMethodID = ( select [OVERNIGHT J-FAST] from ShipMethods )
Okay, lets see
Constants are immutable values which are known at compile time and do not change for the life of the program
that means you can never have a constant in SQL Server
declare #myvalue as int
set #myvalue = 5
set #myvalue = 10--oops we just changed it
the value just changed
Since there is no build in support for constants, my solution is very simple.
Since this is not supported:
Declare Constant #supplement int = 240
SELECT price + #supplement
FROM what_does_it_cost
I would simply convert it to
SELECT price + 240/*CONSTANT:supplement*/
FROM what_does_it_cost
Obviously, this relies on the whole thing (the value without trailing space and the comment) to be unique. Changing it is possible with a global search and replace.
There are no such thing as "creating a constant" in database literature. Constants exist as they are and often called values. One can declare a variable and assign a value (constant) to it. From a scholastic view:
DECLARE #two INT
SET #two = 2
Here #two is a variable and 2 is a value/constant.
SQLServer 2022 (currently only as Preview available) is now able to Inline the function proposed by SQLMenace, this should prevent the performance hit described by some comments.
CREATE FUNCTION fnConstant() RETURNS INT AS BEGIN RETURN 2 END GO
SELECT is_inlineable FROM sys.sql_modules WHERE [object_id]=OBJECT_ID('dbo.fnConstant');
is_inlineable
1
SELECT dbo.fnConstant()
ExecutionPlan
To test if it also uses the value coming from the Function, I added a second function returning value "1"
CREATE FUNCTION fnConstant1()
RETURNS INT
AS
BEGIN
RETURN 1
END
GO
Create Temp Table with about 500k rows with Value 1 and 4 rows with Value 2:
DROP TABLE IF EXISTS #temp ;
create table #temp (value_int INT)
DECLARE #counter INT;
SET #counter = 0
WHILE #counter <= 500000
BEGIN
INSERT INTO #temp VALUES (1);
SET #counter = #counter +1
END
SET #counter = 0
WHILE #counter <= 3
BEGIN
INSERT INTO #temp VALUES (2);
SET #counter = #counter +1
END
create index i_temp on #temp (value_int);
Using the describe plan we can see that the Optimizer expects 500k values for
select * from #temp where value_int = dbo.fnConstant1(); --Returns 500001 rows
Constant 1
and 4 rows for
select * from #temp where value_int = dbo.fnConstant(); --Returns 4rows
Constant 2
Robert's performance test is interesting. And even in late 2022, the scalar functions are much slower (by an order of magnitude) than variables or literals. A view (as suggested mbobka) is somewhere in-between when used for this same test.
That said, using a loop like that in SQL Server is not something I'd ever do, because I'd normally be operating on a whole set.
In SQL 2019, if you use schema-bound functions in a set operation, the difference is much less noticeable.
I created and populated a test table:
create table #testTable (id int identity(1, 1) primary key, value tinyint);
And changed the test so that instead of looping and changing a variable, it queries the test table and returns true or false depending on the value in the test table, e.g.:
insert #testTable(value)
select case when value > 127
then #FALSE
else #TRUE
end
from #testTable with(nolock)
I tested 5 scenarios:
hard-coded values
local variables
scalar functions
a view
a table-valued function
running the test 10 times, yielded the following results:
scenario
min
max
avg
scalar functions
233
259
240
hard-coded values
236
265
243
local variables
235
278
245
table-valued function
243
272
253
view
244
267
254
Suggesting to me, that for set-based work in (at least) 2019 and better, there's not much in it.
set nocount on;
go
-- create test data table
drop table if exists #testTable;
create table #testTable (id int identity(1, 1) primary key, value tinyint);
-- populate test data
insert #testTable (value)
select top (1000000) convert(binary (1), newid())
from sys.all_objects a
, sys.all_objects b
go
-- scalar function for True
drop function if exists fnTrue;
go
create function dbo.fnTrue() returns bit with schemabinding as
begin
return 1
end
go
-- scalar function for False
drop function if exists fnFalse;
go
create function dbo.fnFalse () returns bit with schemabinding as
begin
return 0
end
go
-- table-valued function for booleans
drop function if exists dbo.tvfBoolean;
go
create function tvfBoolean() returns table with schemabinding as
return
select convert(bit, 1) as true, convert(bit, 0) as false
go
-- view for booleans
drop view if exists dbo.viewBoolean;
go
create view dbo.viewBoolean with schemabinding as
select convert(bit, 1) as true, convert(bit, 0) as false
go
-- create table for results
drop table if exists #testResults
create table #testResults (id int identity(1,1), test int, elapsed bigint, message varchar(1000));
-- define tests
declare #tests table(testNumber int, description nvarchar(100), sql nvarchar(max))
insert #tests values
(1, N'hard-coded values', N'
declare #testTable table (id int, value bit);
insert #testTable(id, value)
select id, case when t.value > 127
then 0
else 1
end
from #testTable t')
, (2, N'local variables', N'
declare #FALSE as bit = 0
declare #TRUE as bit = 1
declare #testTable table (id int, value bit);
insert #testTable(id, value)
select id, case when t.value > 127
then #FALSE
else #TRUE
end
from #testTable t'),
(3, N'scalar functions', N'
declare #testTable table (id int, value bit);
insert #testTable(id, value)
select id, case when t.value > 127
then dbo.fnFalse()
else dbo.fnTrue()
end
from #testTable t'),
(4, N'view', N'
declare #testTable table (id int, value bit);
insert #testTable(id, value)
select id, case when value > 127
then b.false
else b.true
end
from #testTable t with(nolock), viewBoolean b'),
(5, N'table-valued function', N'
declare #testTable table (id int, value bit);
insert #testTable(id, value)
select id, case when value > 127
then b.false
else b.true
end
from #testTable with(nolock), dbo.tvfBoolean() b')
;
declare #testNumber int, #description varchar(100), #sql nvarchar(max)
declare #testRuns int = 10;
-- execute tests
while #testRuns > 0 begin
set #testRuns -= 1
declare testCursor cursor for select testNumber, description, sql from #tests;
open testCursor
fetch next from testCursor into #testNumber, #description, #sql
while ##FETCH_STATUS = 0 begin
declare #TimeStart datetime2(7) = sysdatetime();
execute sp_executesql #sql;
declare #TimeEnd datetime2(7) = sysdatetime()
insert #testResults(test, elapsed, message)
select #testNumber, datediff_big(ms, #TimeStart, #TimeEnd), #description
fetch next from testCursor into #testNumber, #description, #sql
end
close testCursor
deallocate testCursor
end
-- display results
select test, message, count(*) runs, min(elapsed) as min, max(elapsed) as max, avg(elapsed) as avg
from #testResults
group by test, message
order by avg(elapsed);
The best answer is from SQLMenace according to the requirement if that is to create a temporary constant for use within scripts, i.e. across multiple GO statements/batches.
Just create the procedure in the tempdb then you have no impact on the target database.
One practical example of this is a database create script which writes a control value at the end of the script containing the logical schema version. At the top of the file are some comments with change history etc... But in practice most developers will forget to scroll down and update the schema version at the bottom of the file.
Using the above code allows a visible schema version constant to be defined at the top before the database script (copied from the generate scripts feature of SSMS) creates the database but used at the end. This is right in the face of the developer next to the change history and other comments, so they are very likely to update it.
For example:
use tempdb
go
create function dbo.MySchemaVersion()
returns int
as
begin
return 123
end
go
use master
go
-- Big long database create script with multiple batches...
print 'Creating database schema version ' + CAST(tempdb.dbo.MySchemaVersion() as NVARCHAR) + '...'
go
-- ...
go
-- ...
go
use MyDatabase
go
-- Update schema version with constant at end (not normally possible as GO puts
-- local #variables out of scope)
insert MyConfigTable values ('SchemaVersion', tempdb.dbo.MySchemaVersion())
go
-- Clean-up
use tempdb
drop function MySchemaVersion
go