I have a datetime column name [dbo].[Bus Station] that I want to pass it in T-SQL script as a parameter #StationName
DECLARE #CurrentTime TIME(0) = '20:53:00',
#StationName TIME(0), -- Datetime Column Name
#BusTime TIME(0);
SET #StationName = '[dbo].[Bus Station]'
SET #BusTime = (SELECT TOP 1 #StationName
FROM dbo.R1_pvt
WHERE #StationName >= CONVERT (time(0), #CurrentTime))
SELECT DateDiff(mi, #CurrentTime, #BusTime) % 60 As Minutes
How can I do it? :(
It appears that what you want to do is execute a dynamic SQL statement. This can be done, but you must be careful because depending on where the input comes from that's used to build the query, you could be susceptible to a SQL injection attack. On Microsoft's information page on this method, it says:
IMPORTANT: Before you call EXECUTE with a character string, validate
the character string. Never execute a command constructed from user
input that has not been validated.
https://learn.microsoft.com/en-us/sql/t-sql/language-elements/execute-transact-sql
To execute a dynamic SQL statement, build your query in a string (varchar, typically) variable, and then execute it. That's done by either using the EXECUTE() command or by calling the sp_executesql system stored procedure.
-- variable to store the query
DECLARE #query varchar(500)
-- build the query
SET #query = 'SELECT TOP 1 ' + #StationName + ' FROM dbo.R1_pvt WHERE ' + #StationName + ' >= CONVERT(time(0), ''' + CAST(#CurrentTime as varchar) + ''' )'
-- execute the query
EXECUTE(#query);
In your case, you also want to return a value. This can be done and has been covered well elsewhere on this site. I suggest you take a look at this question on Stack Overflow:
Getting result of dynamic SQL into a variable for sql-server
Related
I'm trying to pass user-inputted data to a SQL 'sp_executesql' (Dynamic SQL) statement in order to build a string for the 'SELECT','FROM', and 'WHERE' statements.
I know that SQL Server won't accept a table name or a column name as a parameter. However I was wondering if it was possible to take user-inputted values, store them in a locaL-SQL variable and then use the local variable in the 'FROM' clause?
I know this code would work:
set #tableName = 'SalesData'
set #monthNo = 2
set #sql = N'
select SalesPerson
from ' + #tableName + '
where mon = #monthNo'
exec sp_executesql #sql, N'#monthNo int', #monthNo
But, would this code run?
set #tableName = #ValueTypedByUser
set #monthNo = 2
set #sql = N'
select SalesPerson
from ' + #tableName + '
where mon = #monthNo'
exec sp_executesql #sql, N'#monthNo int', #monthNo
How many tables are you possibly dealing with? If it's a small number you have two better choices:
Use multiple stored procedures instead of one, and call them based on the table you need. You can use a parameter in your calling routine to indicate which SP you want.
Use a parameter to specify the table you want, but then, instead of using a variable to change the table name in your SP, use the following conditionals:
IF #table = 'SalesPersonTable'
BEGIN
SELECT SalesPerson
FROM SalesPersonTable
WHERE mon = #monthNo
END
IF #table = 'OtherTable'
BEGIN
SELECT SalesPerson
FROM OtherTable
WHERE mon = #monthNo
END
This avoids the SQL injection issues, but again, only works if the number of tables is "small" (with "small" being what you want it to be!)
I have a stored procedure in SQL Server which gets a XML as input parameter. In this XML is defined - what stored procedure with which parameters values should be executed. And according to that the stored procedure execute the wanted one using dynamic SQL with sp_executesql.
The problem is that the values of the parameters are vulnerable to SQL injection.
I have tried using typed parameters like that:
EXEC sys.sp_executesql
#stmt = #sql,
#params = N'#Username SYSNAME, #HireDate DATE',
#UserName = #Username, #HireDate = #HireDate;
But it doesn't really work in my case, because I don't know what procedure with what parameters will be executed. The number of parameters may vary and some of them are optional / have default values etc. All I can get is the names of the parameters as string :(
After some parsing of the input XML the SQL Query is build and executed like that
declare #params nvarchar(max);
select #params = coalesce(#params + N', ', N' ') + r.attrName + N' = ' + iif(p.isNumericType = 1, r.Value, '''' + r.Value /*cast(r.Value as nvarchar(max))*/ + '''') --+ r.Value
from dbo.#ruleConfig r
left join #spParams p on p.paramName = r.attrName -- datatype of a parameter from information_schema.parameters
declare #sql nvarchar(max) = (select #procName + isnull(#params, N''));
exec dbo.sp_executesql #sql
The value of #sql can look something like that:
'core.GetUser #LogonName = 'myDomain\myLogon''
But also can look like that:
'core.GetUser #fullLogonName = 'myDomain\myLogon;'WAITFOR DELAY '0:0:20';--'' and that's the problem.
To begin with, if you have any functionality that requires you to send dynamic SQL stored procedures execution commands, you have a big problem with your design as with a strict tightly-coupled design, each API call will be mapped to a single stored procedure.
If you still wish to stick with the current design, than you have to create a list of known stored procedures and their values. You cannot just accept the fact that you "don't know what procedure with what parameters will be executed" because than your publish an SQL Injection functionality by-design.
You need to create an enum of the known stored procedures (for example 1 = proc1, 2 = proc2 etc.) and perform a regular-expression-based input validation to their parameters.
In the example you just gave, if you want "myDomain\myLogon" to be accepted and "myDomain\myLogon;'WAITFOR DELAY '0:0:20';--" to be rejected, you can for example use a regular expression that looks like this:
^([A-Za-z\\])*$
You can test it in the regexr website.
You'll have to create an input validation regex for each field type - for example names, usernames, emails etc. Basically it's pretty similar to just creating a separate API for each procedure call, with proper input validation.
When I add the following code into my report stored procedure, I cannot add the shared dataset to my SSRS report, and it gives me this error:
Incorrect syntax near the keyword 'as'.
The code I have added (to a previously working procedure) is:
set #sqlstring = Concat(N'INSERT INTO #TEXT_SEARCH_RESULT',
N'(Result_Name,Count_of_tickets,HT,Created_Group)',
N' SELECT ',#TimeDefinition,' as RESULT_NAME',
N' ,sum(COUNT_OF_TICKETS) as COUNT_OF_TICKETS',
N' ,sum(HT) as HT',
N' ,''Group'' as CREATED_GROUP',
N' from #TEXT_SEARCH_MAIN ct',
N' where (1=1) ')
--other items that append to the string, but have no bearing on the question
set #sqlstring=concat(#sqlstring,N' group by ',#TimeDefinition)
The sql statement appends data to an temporary table with data already in it from the previous statement.
#TimeDefinition is declared as NVARCHAR(100)
Replacing #TimeDefinition with the text that is in the variable (example cast(CREATEDDATE as date) allows the report to be added
if I remove the AS from the concatenated string, then the error simply changes to Incorrect syntax near the keyword 'by'.
Running the query in SSMS does not give an error
The problem here is that SSRS wasn't able to determine the columns of the query without a parameter being supplied. When using dynamic SQL, sometimes the dynamic SQL won't have a value if no value is supplied for one (or more) parameters; thus the column values can't be determined. Take a simple example like:
DECLARE #SQL nvarchar(MAX);
SET #SQL = N'SELECT ' + QUOTENAME(#Col1) + N' AS Result1,' + NCHAR(10) +
N' ' + QUOTENAME(#Col2) + N' AS Result2' + NCHAR(10) +
N'FROM MyTable;';
PRINT #SQL;
EXEC sp_executesql #SQL;
Unless values for #Col1 and #Col2 are supplied, then the value of #SQL will result in NULL.
When using queries such as these, SSRS will therefore present you with a dialogue window. This'll look something like the below (taken from SSDT 2017)
You'll need to enter some values into this dialogue (in the second column, which is named Parameter Value) so that SSRS can correctly determine the column definitions. Once you've done that, SSRS will create the dataset correctly.
On a related note, I suggest against the use of 1=1. This just adds time to the processing (as the data engine still needs to check if 1=1) and can cause the query analyser to make poor choices. It actually can be considered quite bad practice to put things like that in your query's WHERE clause.
Is it possible to execute a SQL statement that has been stored within a table as text.
I am trying to create a configuration table to drive a large number of SSRS subscriptions and don’t want to code any values directly into the report I want them all driven from a table for maintance.
E.G.
If part of one reports file name will always be the year of the previous month (2013 for example) but this needs to be generated at run time.
Can I stored this as a text field
CAST(YEAR(DATEADD(month, -1, GETDATE())) AS VARCHAR(4))
Then execute it and resolve the result into a SQL query?
If I understand your question correctly, then yes by using dynamic SQL. For example:
DECLARE #SQL NVARCHAR(4000)
DECLARE #YEAR VARCHAR(4)
SET #SQL = 'SELECT #yr = CAST(YEAR(DATEADD(month, -1, GETDATE())) AS VARCHAR(4))'
EXECUTE sp_executesql #SQL, N'#yr VARCHAR(4) OUTPUT', #yr=#YEAR OUTPUT
SELECT #YEAR
...returns 2013 into variable #YEAR.
Here I've hardcoded the query but it is a simple case to build the value of #SQL from a table's column value instead. You can then use the result from this query to build another dynamic query and so on.
Below is a subset of the above showing the SQL being taken from a table instead:
CREATE TABLE Dynamic (id INTEGER, text VARCHAR(4000) )
INSERT Dynamic values (1, 'CAST(YEAR(DATEADD(month, -1, GETDATE())) AS VARCHAR(4))')
SET #SQL = 'SELECT #yr = ' + (SELECT text FROM Dynamic WHERE id = 1)
why couldn't you?
Is it only one line of values per report?
I see 2 choices with SSRS.
1) everything in sql, you do dynamic sql.
declare #sql nvarchar(4000)
select #sql = 'select * from mytable where year = ' mavar'
from tablevalue where uniqueid = blablabla
exec sp_executesql #sql
second possibilty :
You make a first dataset in SSRS getting this value and then you evaluate the value of your condition and send it to a third dataset.
If you have multiple values, you can still imagine to get the value from a first dataset, evaluate them and send this to a subreport.
I don't know your application enought to determine the performance of this.
Until you are in SSRS, i recommand to try to find a solution in SSRS instead of Sql (but it's not a gold rule at all!!!)
I have written a simple procedure:
CREATE PROCEDURE [dbo].[sp_GetPublishedDocs2]
#FromDate as Datetime
AS
BEGIN
DECLARE #strSQL VARCHAR(5000)
SET #strSQL='SELECT * From Task WHERE 1=1 '
IF #FromDate <>'1/1/1900'
SET #strSQL = #strSQL + ' AND Task.CreatedDate >= '+Cast(#FromDate as Datetime)
EXEC(#strSQL)
END
It run successfully when I pass parameter '1/1/1900' however when I pass any other date it says: Conversion failed when converting date and/or time from character string.
Is there anyone.. who could help me...
Thanks in Advance.
You should cast the #FromDate into a varchar since you are doing a string concat.
CREATE PROCEDURE [dbo].[sp_GetPublishedDocs2]
(
#FromDate as Datetime
)
AS
BEGIN
DECLARE #strSQL VARCHAR(5000)
SET #strSQL = 'SELECT * From Task WHERE 1=1 '
IF #FromDate <>'1/1/1900'
BEGIN
SET #strSQL = #strSQL + ' AND Task.CreatedDate >= ''' + Cast(#FromDate as varchar) + ''''
END
EXEC(#strSQL)
END
Try to avoid SELECT * FROM. It is faster to define all columns explicitly.
HINT: for testing you could use PRINT favor of EXEC to see what sql has been produced.
EDIT: You might use VARCHAR(MAX) here...
Don't use the sp_ prefix for stored procedures, because it's used for system procedures
If you didn't already, search this site or Google for "dynamic search conditions" in TSQL
If you didn't already, search this site or Google for "dynamic SQL"
The format YYYYMMDD for date string literals is always interpreted correctly
Don't use SELECT * unless you have a good reason
Always include the schema in object names
The '2' at the end of your procedure name is a code smell that suggests you may not be handling source code versioning and deployment cleanly
And last but not least, keep it simple if you can:
CREATE PROCEDURE [dbo].[usp_GetPublishedDocs2]
(
#FromDate as Datetime
)
AS
BEGIN
if #FromDate = '19000101'
begin
select * from dbo.Task
end
else
begin
select * from dbo.Task where CreatedDate >= #FromDate
end
END
A bit of research into dynamic search conditions will show you other possible ways to implement this, which will be useful if you need to have more complex search conditions.