I have a query that should be reused in many scenarios. This query receives some parameters.
Because it has to be reused, it can't be a stored procedure. So, it's created as a Function (not a View, because it needs some parameters).
This is the best approach so far, right?
The issue is that this query returns data that needs some post processing, i.e. reused in some other queries. I'm facing the issue about reusing them in other queries.
Example:
Function GetMyFirstData returns several columns, including a FootNoteSymbol column. I should create another Function (GetFootnoteText) to return the text (and some other details) about these footnotes.
How should I create the second function that will receive as a parameter the FootNoteSymbol (many) returned by the first function GetMyFirstData?
I'm avoiding Stored Procedure, because these results will most likely be reused in other queries.
Also, the FootNoteSymbol is also returned in many other functions, with different return structures (therefore I can't create a TableType, because the structure is not fixed - however FootNoteSymbol is common among all of them).
Using SQL Server 2008 R2.
Functions that return data:
CREATE FUNCTION GetMyFirstData
(
#Param1 int,
#Param2 int
)
RETURNS #Return TABLE
(
Col1 int,
Col2 int,
FootnoteSymbol int,
Col3 int,
Col4 int
)
AS
BEGIN
SELECT Col1, Col2, FootnoteSymbol, Col3, Col4
FROM MyData
RETURN;
END
CREATE FUNCTION GetMySecondData
(
#Param1 int,
#Param2 int
)
RETURNS #Return TABLE
(
Col1 int,
FootnoteSymbol int,
Col2 int
)
AS
BEGIN
SELECT Col1, FootnoteSymbol, Col2
FROM MyOtherData
RETURN;
END
Function that should get footnotes text:
CREATE FUNCTION GetFootnoteText
(
#FootnoteSymbol --this is the issue, how to reuse the footnotesymbols from the other functions
)
RETURNS #Return TABLE
(
Symbol int,
Text text,
OtherDetail nvarchar(200)
)
AS
BEGIN
SELECT Symbol, Text, OtherDetail
FROM MyFootnotes
WHERE Symbol in --this is the issue, how to reuse the footnotesymbols from the other functions
RETURN;
END
Thanks!
DO. NOT. DO. THIS.
Reusing code is a noble goal, but SQL is not the language for it. There are many documented performance problems resulting from your approach. Some quick links Query Performance and multi-statement table valued functions, Improving query plans with the SCHEMABINDING option on T-SQL UDFs or Compute Scalars, Expressions and Execution Plan Performance.
I wish I had a good alternative for you, but I don't. Views are OK for query re-use. But attempting to compose SQL table value functions has always ended in disaster, in every engagement I've seen.
Don't do it.
At the very least stick to Inline Table Value Functions;
The RETURNS clause contains only the keyword table. You do not have to define the format of a return variable, because it is set by the format of the result set of the SELECT statement in the RETURN clause.
There is no function_body delimited by BEGIN and END.
The RETURN clause contains a single SELECT statement in parentheses. The result set of the SELECT statement forms the table returned by the function. The SELECT statement used in an inline function is subject to the same restrictions as SELECT statements used in views.
The table-valued function accepts only constants or #local_variable arguments
As far as I can tell (and I reference you to #SeanLange comment "You know what your tables look like, what the data is like, what the rules are and what the expected results are. I on the other hand can't see any of that.") you have a basic miss-understanding about how relational databases work. To "solve" the problem presented here using standard relational database practices I would not split it up into multiple functions (as there is no gain there) instead I would create a SP that did a JOIN to get all the data you need. Like this:
CREATE PROCEDURE GetData
(
#Param1 int,
#Param2 int
)
AS
BEGIN
SELECT MyData.Col1,
MyData.Col2,
MyFootnotes.Text,
MyFootnotes.OtherDetail,
MyData.Col3,
MyData.Col4
FROM MyData
JOIN MyFootnotes ON MyData.FootnoteSymbol = MyFootnotes.Symbol
END
You don't show how you use the parameters so I can't address that, but I can guess. Let's say the parameters in this function are used in the where clause to limit the results. (Col1=#Param1 and Col2=#Param2) but in another case you have different limits (eg Col3=#Param1 and Col4=#Param2).
In this case the best way to do it is to make a view that is shared and limited in each SP. I would not use functions as I see no value to them (and a high potential for problems as #RemusRusanu points out). Like this:
CREATE VIEW MyData AS
SELECT MyData.Col1,
MyData.Col2,
MyFootnotes.Text,
MyFootnotes.OtherDetail,
MyData.Col3,
MyData.Col4
FROM MyData
JOIN MyFootnotes ON MyData.FootnoteSymbol = MyFootnotes.Symbol
with
CREATE PROCEDURE GetData1
(
#Param1 int,
#Param2 int
)
AS
BEGIN
SELECT *
FROM MyData
WHERE MyData.Col1,
MyData.Col2,
MyFootnotes.Text,
MyFootnotes.OtherDetail,
MyData.Col3,
MyData.Col4
FROM MyData
WHERE Col1=#Param1 and Col2=#Param2
END
and
CREATE PROCEDURE GetData2
(
#Param1 int,
#Param2 int
)
AS
BEGIN
SELECT *
FROM MyData
WHERE MyData.Col1,
MyData.Col2,
MyFootnotes.Text,
MyFootnotes.OtherDetail,
MyData.Col3,
MyData.Col4
FROM MyData
WHERE Col3=#Param1 and Col4=#Param2
END
I know that as a programmer who has worked in non-relational systems this is not intuitive. However trust me, this will get you the best results. This is how your server software expects to be used and over the years it it has been tuned to deliver you fast results using a view in this way.
Related
I use a function as BLOCK and FILTER PREDICATE in order to implement row-level security.
CREATE FUNCTION [dbo].[AccessPredicate]
(#accessId int)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
SELECT 1 AS AccessPredicateResult
WHERE #accessId = CAST(SESSION_CONTEXT(N'accessId ') AS int)
I now need to modify the session context variable to hold any number of accessId. Since it is a SQL_VARIANT type, I cannot use a TABLE variable or custom one. I resorted to a CSV list as NVARCHAR, e.g. 1,2,3
That means I need to update the SELECT query in my function to do a 'WHERE #accessId IN (...)'. Of course I can't just
SELECT 1 AS AccessPredicateResult
WHERE #accessId IN (CAST(SESSION_CONTEXT(N'accessId ') AS VARCHAR)) -- where accessId = '1,2,3'
Without liking the implications, I tried to do that by putting the whole query into a #sql VARCHAR variable and then EXEC sp_executesql #sql but that failed because SQL Server cannot have a DECLARE in a table valued function (for whatever reason!?). Neither does the table valued function allow EXEC a stored procedure as far as my research suggests.
I'm blocked by this, can't think of a way to do this now.
So I've got the allowed accessId in my C# code and somehow need to persist them in the session context (or are there other, well performing, methods?) so that my predicate can use it to confirm the row in question is accessible. What's the best way forward now?
Thanks
You can use STRING_SPLIT in a subquery
CREATE OR ALTER FUNCTION dbo.AccessPredicate
(#accessId int)
RETURNS TABLE
WITH SCHEMABINDING
AS RETURN
SELECT 1 AS AccessPredicateResult
WHERE #accessId IN (
SELECT s.value
FROM STRING_SPLIT(CAST(SESSION_CONTEXT(N'accessId') AS varchar(4000)), ',')
);
I warn you though, that RLS is not foolproof security, and can be circumvented by a user able to craft custom queries.
As a side note: Why does SQL Server not allow variables or EXEC in functions?
EXEC is not allowed because a function must not be side-affecting.
Furthermore, an inline table function must be a single statement, therefore it makes no sense to DECLARE variables. It is effectively a parameterized view, and functions that way as far as the compiler is concerned.
If you do need to "declare" variables, you can use a VALUES virual table, along with CROSS APPLY. For example:
CREATE OR ALTER FUNCTION dbo.AccessPredicate
(#accessId int)
RETURNS TABLE
WITH SCHEMABINDING
AS RETURN
SELECT c2.Calc2 AS AccessPredicateResult
FROM (VALUES(
SomeCalculationHere
)) AS c1(Calc1)
CROSS APPLY (VALUES(
SomeFunction(c1.Calc1)
)) AS c2(Calc2);
I've been developing a few stored procedure and I have been repeating a portion of codes that derives a column based on a few other columns. So instead of copy this piece of code from one stored procedure to another, I'm thinking of having a function that takes the input columns and produces the output columns.
Basically, the function goes as:
SELECT columnA, columnB, columnC, myFunction(columnA, columnB) as columnD FROM myTable
As we can see, this function will take column A and column B as inputs, then return column D.
However, based on some research, it seems to have some performance issues when using UDF (user-defined) function like this. Is that true? What's the best way to handle this situation?
Thank you guys.
Scalar functions and multi statement table valued user defined functions can cause performance issues, because they implicitly turn your set based operation into a cursor based operation.
However, inline table valued user defined functions do not suffer from this problem. They're fast.
The difference is how you declare the fuction, and what the code looks like inside them. A multi statement function does what it says on the tin - it lets you have multiple statements. Like this:
create function slow() returns #t table(j int, k int) as
begin
declare #j int = 1; -- statement 1
declare #k int = 2; -- statement 2
insert #t values (#j, #k); -- statement 3
return; -- statement 4
end
An inline table valued function does not return a named table which is populated inside the function. It returns a select statement:
create function quick() returns table as
return
(
select j = 1, k = 2
);
The inline table valued function can be "inlined" into the outer select statement, in much the same way as a view. The difference, of course, being that the UDF can take parameters, whereas a view cannot.
You also have to use them differently. Use cross apply:
select t.columnA, t.columnB, u.j, u.k
from MyTable t
cross apply quick(t.columnA, t.columnB) u
In case it's not clear - yes, in your case you only want a "scalar" value back, but that's just a table valued function which returns a single column and a single row. So instead of writing a scalar function, write an inline table valued function that does the same job, and cross apply it.
I am using a stored procedure to insert records into a table. And do this at least 12 times in a loop to insert multiple records which is very inefficient.
here is the procedure as CREATED
Create PROC [dbo].[SP_INSERT_G_SAMPLING]
#GameID INT,
#ScoreID INT
as
begin
INSERT INTO GAMESCORE (GAMEID, SCOREID) VALUES
(#GameID, #ScoreID)
end
I pass on the values ex(1,3) and loop with more values from the website.
I want to however pass on all the values at one time like (1,3),(4,5),(8,9)
and then alter the above procedure to receive and insert multiple rows.
ALTER PROC [dbo].[SP_INSERT_G_SAMPLING]
#totalinsert nvarchar(Max)
INSERT INTO GAMESCORE (GAMEID, SCOREID) VALUES
(#totalinsert)
with #totalinsert being like (1,3),(4,5),(8,9) pushed from the webpage.
any help is greatly appreciated
What you're going to have to do is write a table valued function which accepts the multi-value string and breaks it out into a table object. If you can change your source to use a record delimiter instead of having comma sets it would be slightly easier to process. An example of that would look like this.
The below is pure psuedo and has not been validated in any way, just meant to give you a rough idea of where to go.
ex: #TotalInsert = 1,2|4,5|8,9
DECLARE #Results TABLE
(
value1 INT,
value2 INT
)
DECLARE #setlist VARCHAR(max);
WHILE Len(#TotalInsert) > 0
BEGIN
SET #setlist = LEFT(#totalinsert, Charindex('|', #totalinsert))
INSERT INTO #results
SELECT LEFT(#setlist, Charindex(',', #setlist) - 1),
RIGHT(#setlist, Charindex(',', Reverse(#setlist)) + 1)
SET #totalinsert = RIGHT(#totalinsert, Len(#totalinsert) - Len(#setlist))
END
I'm assuming you're using .NET for your website since you're also using SQL Server.
Have a look at table valued parameters, this page also includes a nice example of how to use the table valued parameters in .NET.
Check here for a better example of making a stored procedure with a table valued parameter in T-SQL.
Here is the full discussion:
http://www.sommarskog.se/arrays-in-sql-2005.html#XMLlist%20of%20values
Personally, I sent xml to the stored procedure, I "shred it" into #variable or #temp tables, then I do my INSERT/UPDATE/MERGE/DELETE from there.
Here is a fuller discussion on xml-shredding.
http://pratchev.blogspot.com/2007/06/shredding-xml-in-sql-server-2005.html
My personal trick is to create a strong dataset, populate the strong dataset with rows, and use the ds.GetXml() to send the xml down to the TSQL. With a strong dataset, I get strong-typing when populating the values. But at the end of the day, dataset is just some super fancy xml.
I've just begun to learn how to write stored procedures and SQL code outside of the basic DML stuff. Something that I've recently come across is table value parameters. I've found a script to make a TVP and it works just fine but there are two things that I don't understand about it. One, when to use them. What's a typical real world scenario when a TVP would be beneficial. Two, how come when I remove the begin and end from the following script does it work the same; what's the difference between having those keywords and not? SQL Server 2008 R2
use [tempdb]
--this is the database table that will be populated
create table SampleTable
(
id int not null identity (1,1)
,SampleString varchar(50)
,SampleInt int null
)
go
--create the table data type
create type dbo.SampleDataType as table
(
SampleString varchar(50)
,SampleInt int
)
go
--the stored procedure takes the SampleDataType as an input parameter
create proc SampleProc
(
--accepts the TVP as the lone parameter
--make sure that the TVP is readonly
#Sample as dbo.SampleDataType readonly
)
as
begin
--we simply insert the values into the db table from the parameter
insert into SampleTable(SampleString,SampleInt)
select SampleString,SampleInt from #Sample
end
go
--this is the sample script to test that the sproc worked
declare #SampleData as dbo.SampleDataType
insert into #SampleData(SampleString,SampleInt) values ('one',1);
insert into #SampleData(SampleString,SampleInt) values ('two',2);
select * from #SampleData
One real world use is to parameterise an in clause.
Where a query has a filter on (x, y, z, ...) you no longer have to resort to one of the methods here such as passing it in as a comma delimited list and then splitting it.
The BEGIN ... END make no difference there. It defines a block. You might use that after an IF statement for example to group multiple statements together into one logical block.
Can we create parameterized VIEW in SQL Server 2008.
Or Any other alternative for this ?
Try creating an inline table-valued function. Example:
CREATE FUNCTION dbo.fxnExample (#Parameter1 INTEGER)
RETURNS TABLE
AS
RETURN
(
SELECT Field1, Field2
FROM SomeTable
WHERE Field3 = #Parameter1
)
-- Then call like this, just as if it's a table/view just with a parameter
SELECT * FROM dbo.fxnExample(1)
If you view the execution plan for the SELECT you will not see a mention of the function at all and will actually just show you the underlying tables being queried. This is good as it means statistics on the underlying tables will be used when generating an execution plan for the query.
The thing to avoid would be a multi-statement table valued function as underlying table statistics will not be used and can result in poor performance due to a poor execution plan.
Example of what to avoid:
CREATE FUNCTION dbo.fxnExample (#Parameter1 INTEGER)
RETURNS #Results TABLE(Field1 VARCHAR(10), Field2 VARCHAR(10))
AS
BEGIN
INSERT #Results
SELECT Field1, Field2
FROM SomeTable
WHERE Field3 = #Parameter1
RETURN
END
Subtly different, but with potentially big differences in performance when the function is used in a query.
No, you cannot. But you can create a user defined table function.
in fact there exists one trick:
create view view_test as
select
*
from
table
where id = (select convert(int, convert(binary(4), context_info)) from master.dbo.sysprocesses
where
spid = ##spid)
...
in sql-query:
set context_info 2
select * from view_test
will be the same with
select * from table where id = 2
but using udf is more acceptable
As astander has mentioned, you can do that with a UDF. However, for large sets using a scalar function (as oppoosed to a inline-table function) the performance will stink as the function is evaluated row-by-row. As an alternative, you could expose the same results via a stored procedure executing a fixed query with placeholders which substitutes in your parameter values.
(Here's a somewhat dated but still relevant article on row-by-row processing for scalar UDFs.)
Edit: comments re. degrading performance adjusted to make it clear this applies to scalar UDFs.
no. You can use UDF in which you can pass parameters.