Evaluating expression in SQL with XQuery (without stored procedure) - sql

I am trying to evaluate an expression in a function, and it happens that XQuery is not able to get string from the SQL variable
DECLARE #XML AS XML = ''
DECLARE #Formula AS NVARCHAR(MAX) = '1+1'
SELECT #XML.query(#Formula)
Using ...
SELECT #XML.query('sql:variable("#Formula")')
... just returns a string 1+1, and not the sum.
Is there a way to make this work in SQL (without using stored procedures, because those will not run form within the function)?
Thanks in advance!

Related

SQL Server Xquery sql:variable usage

I need to use a dynamic string for an xquery path but .query/.nodes methods require a literal string as parameter. So I decided to try sql:variable
DECLARE #xmlData XML, #node varchar(max)
SET #xmlData = 'Some XML Here'
SET #node = '/path1/path2/path3'
When I query with
select #xmlData.query('/path1/path2/path3')
It returns the intended result
But when I query with
select #xmlData.query('sql:variable("#node")')
It returns the variable value itself as "/path1/path2/path3"
What is wrong here?
This should do the trick:
select #xmlData.query('/*[local-name()=sql:variable("#node")]')
It matches any node with a wildcard *, but there is an extra predicate that the name has to match the variable
For performance reasons, you should preferably use /text() to get inner text, and use .values to get a single value.
select #xmlData.value('(/*[local-name()=sql:variable("#node")]/text())[1]', 'nvarchar(100)')
sql:variable is used to substitute a single scalar variable into an XPath expression, so can't be used to define a full path. You can use it to test against a single node's name, though, e.g.:
declare #xmlData XML = '<path1><path2><path3>foo</path3></path2></path1>'
select [example1] = #xmlData.query('/path1/path2/path3')
--example1
--<path3>foo</path3>
declare #node1 varchar(max) = 'path1'
declare #node2 varchar(max) = 'path2'
declare #node3 varchar(max) = 'path3'
select [example2] = #xmlData.query('//*[local-name()= sql:variable("#node1")]/*[local-name()= sql:variable("#node2")]/*[local-name()= sql:variable("#node3")]');
--example2
--<path3>foo</path3>

how evaluate an arithmetic expression within a SQL scalar function

i am trying to execute this scalar function and i tried a lot of approaches to achieve this but i get stuck
Create FUNCTION CalculateElementFunc()
RETURNS int
AS
BEGIN
DECLARE #ResultVar numeric(18,6)
DECLARE #eq nvarchar(MAX)
set #eq = '7.5/100*1258.236'
declare #expression nvarchar(max)
set #expression = #eq
declare #result int
declare #SQLString nvarchar(max)
Set #SQLString = N'Select #result = #expression'
exec sp_executesql #SQLString, N'#expression nvarchar(100)',
#expression,
#result = #result output
select #ResultVar = #result
if( #ResultVar <> ROUND( #ResultVar, 2 ,1))
set #ResultVar = cast( ROUND( #ResultVar, 2 ,1) + .01 as numeric(18,2))
RETURN #ResultVar
END
When i try to execute it
select dbo.CalculateElementFunc()
i get this error
Msg 557, Level 16, State 2, Line 1
Only functions and some extended stored procedures can be executed from within a function.
Please Advice
What you want to do is not recommended in SQL Server. First, it is really hard. As you have learned, a SQL Server function cannot execute dynamic SQL.
This is subtly in the documentation:
EXECUTE statements calling extended stored procedures.
exec and sp_executesql are not extended stored procedures.
What can you do? Here are some options:
Is a stored procedure instead of a UDF a possibility? Stored procedures can execute the dynamic SQL.
Can you get around the problem of expression evaluation? Perhaps dynamic SQL can be used one level up in your code.
You can execute an extended stored procedure that starts another transaction and executes the dynamic SQL. Think: really bad performance.
You can write a CLR extended function.
Limitations on SQL User Defined Functions:
Non-deterministic build in functions cannot be used in user defined functions. e.g. GETDATE() or RAND().
XML data type is not supported.
Dynamic SQL queries are not allowed.
User defined functions does not support any DML statements (INSERT, UPDATE, DELETE) unless it is performed on Table Variable.
We cannot make a call to the stored procedure. Only extended stored procedure can be called from function.
We cannot create Temporary tables inside UDFs.
It does not support Error Handling inside UDF. Although, we can handle errors (RAISEERROR, TRY-CATCH) for the statements which uses this function.
And it looks like you are using/calling a stored procedure inside your User Defined Function. It is not the expression that's bugging you, it's that stored procedure call.
Try to replace it with some logic to achieve your desired output.
Hope this is helpful. If it helps to solve your problem then don't forget to mark it as an answer.

Dynamic SQL from a Dynamic View to Return the field value of a record

this function should accept the viewname and return the date on the last record of the view. Can someone tell me what am I missing here?
I call the function in a stored procedure and got this error:
Only functions and some extended stored procedures can be executed from within a function.
My query:
ALTER FUNCTION [dbo].[udf_GetLastDate] (#ViewName nvarchar(4000))
RETURNS date
AS
BEGIN
DECLARE #SQLCommand nvarchar(4000);
DECLARE #LastTransDate date;
SET #SQLCommand = 'SELECT #LastTransDate=LAST(TRANSDATE) FROM' + #ViewName;
EXECUTE sp_executesql #sqlCommand
RETURN #LastTransDate;
end
The problem is you are trying to execute Dynamic-SQL from function. You simply cannot do it. Period.
Dynamic SQL in User-Defined Functions:
This very simple: you cannot use dynamic SQL from used-defined functions written in T-SQL. This is because you are not permitted do
anything in a UDF that could change the database state (as the UDF may
be invoked as part of a query). Since you can do anything from dynamic
SQL, including updates, it is obvious why dynamic SQL is not
permitted.
I've seen more than one post on the newsgroups where people have been banging their head against this. But if you want to use dynamic
SQL in a UDF, back out and redo your design. You have hit a roadblock,
and in SQL 2000 there is no way out.
In SQL 2005 and later, you could implement your function as a CLR
function. Recall that all data access from the CLR is dynamic SQL.
(You are safe-guarded, so that if you perform an update operation from
your function, you will get caught.) A word of warning though: data
access from scalar UDFs can often give performance problems. If you
say
SELECT ... FROM tbl WHERE dbo.MyUdf(somecol) = #value
and MyUdf performs data access, you have more or less created a hidden
cursor.
Consider changing function to stored procedure:
CREATE PROCEDURE [dbo].[mysp_GetLastDate]
#ViewName SYSNAME
AS
BEGIN
DECLARE #SQLCommand NVARCHAR(MAX) =
N'SELECT MAX(TRANSDATE) FROM' + QUOTENAME(#ViewName);
EXECUTE [dbo].[sp_executesql]
#sqlCommand;
END
Also keep in mind that SQL Server does not have LAST function. If you need newest TRANSDATE use MAX() or SELECT TOP 1 TRANSADATE FROM ... ORDER BY some_column DESC

WHERE clause in dynamic TSQL and prevent SQL Injection

I have a stored procedure for selecting rows. I want to pass a parameter to filtering rows dynamically like this :
Create Procedure CustomerSelectAll
#FilterExpresion NVARCHAR(MAX)
DECLARE #CMD NVARCHAR(MAX)
SET #CMD = N'SELECT * FROM dbo.Customers '+#FilterExpresion;
EXEC(#CMD)
The above code works fine, but it is at risk for SQL injection, so I want to be able pass multiple columns with any WHERE statement such as:
exec CustomerSelectAll
#FilterExpresion = N' where Name = 'abc' and family = ''xyz'''
I am not aware if you can pass the entire where clause as parameter.But from the little amount of knowledge I have, I can suggest you use a sql parser to see if the where clause has just select statements and if the answer is affirmative then you can pass the where clause to your stored procedure. Hope this is of some help.

Using expressions as a parameter for a SQL Server function

SQL Server comes with a number of string functions, such as RIGHT(), which accepts an Expression as a parameter so that it can accept either a varchar or nvarchar value.
How do I create my own custom function to do the same?
I am not a SQL Server expert, so a simple example with syntax would help.
Thank You
Here is a simple example:
CREATE FUNCTION myUDF (#input nvarchar(255))
RETURNS nvarchar(255)
AS
BEGIN
-- function logic here
declare #output nvarchar(255)
select #output = 'The value passed in was: ' + #input
return #output
End
GO
select dbo.myUDF('I wrote a function!')
Here you can try this way.
How to: Create and Run a CLR SQL Server User-Defined Function