SQL Parameterised column names - sql-server

I am trying to create a parameterised query for retrieving data back from a table
Essentially I have a table structure of
ID
nvarchar1
ntext
datetime1
datetime2
and I am trying to do a query like so that it selects all the data where the current date is greater than datetime1 and less than datetime2
SELECT
ID, nvarchar1,
ntext,
datetime1,
datetime2
FROM
TABLEName
WHERE
datetime1 >= #CurrentDate
AND datetime2 <= #CurrentDate
I want to make the columns parameters such as
#TableName, #CurrentDate, #StartDate, #EndDate
DECLARE #TableName NVARCHAR(100);
SET #TableName = '[Surveys].[dbo].[Table]'
DECLARE #CurrentDate DateTime;
SET #CurrentDate = GETDATE();
DECLARE #StartDate NVARCHAR(100);
SET #StartDate = 'datetime1'
DECLARE #EndDate NVARCHAR(100);
SET #EndDate = 'datetime2'
DECLARE #sql nvarchar(1000)
SET #sql = 'SELECT * FROM ' + #TableName + 'WHERE' + #EndDate + '>=' + #CurrentDate + 'AND' + #StartDatedatetime1 + '<=' + #CurrentDate
EXEC(#sql)
The data is going to be coming from a SP data source so I have no control of the column names etc. and when I create the SP Lists they automatically assign to a table column of that type so this is why I need to columns to be parameters.
Using the above code which I thought should work returns
Msg 241, Level 16, State 1, Line 14
Conversion failed when converting date and/or time from character string.
What am I doing wrong?

Try the below. As #GordonLinoff stated, you where missing the single quotes (') from around the #CurrentDate variable. Also, you where passing a DATETIME parameter in to the #sql variable which is an NVARCHAR data type. These are not implicitly converted, so the #CurrentDate variable needs to be converted to a NVARCHAR first:
DECLARE #sql nvarchar(1000)
SET #sql = 'SELECT * FROM ' + #TableName
+ ' WHERE ' + #EndDate + ' >= ''' + CONVERT(NVARCHAR(50), #CurrentDate,120)
+ ''' AND ' + #StartDate + ' <= ''' + CONVERT(NVARCHAR(50), #CurrentDate,120) + ''''
EXEC(#sql)

You are pretty close. I would construct the SQL for the table and then use parameters for the current date: This would be something like this:
SET #sql = '
SELECT *
FROM #TableName
WHERE datetime2 >= #CurrentDate AND datetime1 <= #CurrentDate';
SET #sql = REPLACE(#sql, '#TableName', #TableName);
exec sp_executesql #sql, N'#CurrentDate date', #CurrentDate = #CurrentDate;
Incidentally, the problem with your query is the lack of single quotes around the date constants.
EDIT:
You cannot substitute column or table names using parameters. I would write the code as:
SET #sql = '
SELECT *
FROM #TableName
WHERE #datetime2 >= #CurrentDate AND #datetime1 <= #CurrentDate';
SET #sql = REPLACE(#sql, '#TableName', #TableName);
SET #sql = REPLACE(#sql, '#datetime1', #DateTime1);
SET #sql = REPLACE(#sql, '#datetime2', '#DateTime2);
. . .
I use REPLACE() for this type of operation because the code is easier to understand and to maintain.

Related

Dates and Dynamic SQL error message

Using MS SQL 2012.
I am trying to query a set of tables using dynamic SQL and I am passing through the tablename, a start date in the format of dd/MM/yyyy and an end date in the format of dd/MM/yyyy.
My Code is as follows.
#tableName nvarchar(50),
#startDate date,
#endDate date
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Query varchar(max)
SET #Query = 'Select * from ''' + #tableName + ''' where Convert(date, Docdate, 103) >= ''' + CAST(#startDate AS VARCHAR(50)) + ''' and Convert(date, Docdate, 103) <= ''' + CAST(#endDate AS VARCHAR(50)) + ''
EXEC #Query
END
The Docdate field has a data type of Date and is in the format of yyyy-MM-dd.
I get the following error when I run this stored procedure.
Incorrect syntax near '/'.
What am I missing?
UPDATE
I am testing the query with the following entries for the variables and I still get the same error.
USE [TestDbs]
GO
DECLARE #return_value int
EXEC #return_value = [dbo].[getResultSet]
#tableName = Prices,
#startDate = 01/02/2016,
#endDate = 20/02/2016
SELECT 'Return Value' = #return_value
GO
You should use sp_executesql and pass proper date parameters to your query, and avoid the need for casting dates to strings at all:
CREATE PROCEDURE dbo.YourProcedure
#TableName SYSNAME,
#StartDate DATE,
#EndDate DATE
AS
BEGIN
DECLARE #Query NVARCHAR(MAX)
SET #Query = 'SELECT * FROM ' + QUOTENAME(#TableName) +
' WHERE Docdate >= #StartDateParam
AND Docdate <= #EndDateParam';
EXECUTE sp_executesql
#Query,
N'#startDateParam DATE, #endDateParam DATE',
#StartDateParam = #StartDate,
#EndDateParam = #EndDate;
END
I have made a couple of other minor tweaks too:
Change the datatype of #TableName from NVARCHAR(50) to SYSNAME (Synonym for `NVARCHAR(128), the maximum length of an object name)
Change the data type of #Query to NVARCHAR(MAX) since this is the type sp_executesql expects.
Wrapped #TableName with QUOTENAME to ensure any special characters do not cause an error.
If DocDate is already a date, then the explicit convert is not necessary so I have removed this.
ADDENDUM
You may also wish to add some validation to the table name:
-- CHECK TABLE NAME IS A VALID TABLE OF VIEW
IF ISNULL(OBJECT_ID(#TableName, 'U'), OBJECT_ID(#TableName, 'V')) IS NULL
BEGIN
-- HANDLE INVALID NAME
RETURN;
END
EDIT
I have changed the procedure slightly to cater for sending through a schema qualified table name
CREATE PROCEDURE dbo.getResultSet
#TableName SYSNAME,
#StartDate DATE,
#EndDate DATE
AS
BEGIN
DECLARE #Query NVARCHAR(MAX)
SET #Query = 'SELECT * FROM ' +
QUOTENAME(OBJECT_SCHEMA_NAME(OBJECT_ID(#TableName))) + '.' +
QUOTENAME(OBJECT_NAME(OBJECT_ID(#TableName))) +
' WHERE Docdate >= #StartDateParam
AND Docdate <= #EndDateParam';
EXECUTE sp_executesql
#Query,
N'#startDateParam DATE, #endDateParam DATE',
#StartDateParam = #StartDate,
#EndDateParam = #EndDate;
END
Then you would use this to execute it:
SET DATEFORMAT DMY;
DECLARE #return_value int
EXEC #return_value = [dbo].[getResultSet]
#tableName = 'dbo.Prices',
#startDate = '01/02/2016',
#endDate = '20/02/2016'
SELECT 'Return Value' = #return_value;
Or better still use a culture insensitive date format (yyyyMMdd) for your literals:
DECLARE #return_value int
EXEC #return_value = [dbo].[getResultSet]
#tableName = 'dbo.Prices',
#startDate = '20160201',
#endDate = '20160220'
SELECT 'Return Value' = #return_value;

sp_executesql date "Conversion failed when converting date and/or time from character string" in specific case

I have something strange:
Declare #SQLQuery As nvarchar(Max)
Declare #maxdat date
Declare #dateColumn nvarchar(10)
Set #maxdat = GETDATE()
Set #dateColumn = 'ERDAT'
Set #SQLQuery = 'SELECT * FROM myTable WHERE #dateColumn <= #maxdat'
Execute sp_executesql #SQLQuery, N'#maxdat date, #dateColumn nvarchar(10)', #maxdat, #dateColumn
This will fail with Conversion failed when converting date and/or time from character string.
But the following will work just fine:
Set #SQLQuery = 'SELECT * FROM myTable WHERE ERDAT <= #maxdat'
Try the updated code like this. Just put the #dateColumn parameter outside the query string
Declare #SQLQuery As nvarchar(Max)
Declare #maxdat date
Declare #dateColumn nvarchar(10)
Set #maxdat = GETDATE()
Set #dateColumn = 'ERDAT'
Set #SQLQuery = 'SELECT * FROM myTable WHERE ' + #dateColumn + ' <= #maxdat'
Execute sp_executesql #SQLQuery, N'#maxdat date', #maxdat
Problem is with your dynamic SQL. the way you are doing it the variables will go as strings. You have to use the "+" symbol and do it like example below so that SQL can parse it and use 'variable value' instead of 'variable name'.
SELECT GETDATE() AS A
INTO
#Temp
Declare #SQLQuery As nvarchar(Max)
Declare #maxdat date
Declare #dateColumn nvarchar(10)
Set #maxdat = DATEADD(Day,1,GETDATE())
Set #dateColumn = 'A'
--Set #SQLQuery = 'SELECT * FROM #Temp WHERE #dateColumn >= #maxdat'
Set #SQLQuery = 'SELECT * FROM #Temp WHERE ' + #dateColumn + ' >= '+ CAST(#maxdat AS nvarchar(10))
--Print #SQLQuery
Execute sp_executesql #SQLQuery, N'#maxdat date, #dateColumn nvarchar(10)', #maxdat, #dateColumn

The data types varchar(max) and date are incompatible in the add operator

I have created one of the search SP here, Just preparing parameters based on user input on screen.
Now I need to cast the table column as well as input parameter with DATE type and then need to query.
Below is the part of the procedure which I need to achieve.
Problem is, I'm getting the following error now:
The data types varchar(max) and date are incompatible in the add operator.
DECLARE #WhereClause VARCHAR(MAX)
DECLARE #DateField DATETIME = GETDATE()
DECLARE #DateFieldTo DATETIME = GETDATE() +1
SET #WhereClause = #WhereClause + ' AND CAST(tbl.DateField AS DATE) BETWEEN ''' + CAST(#DateField AS DATE) + ''' + AND ''' + CAST(#DateFieldTo AS DATE)+ ''
PRINT #WhereClause
I tried the below approach:
DECLARE #WhereClause VARCHAR(MAX)
DECLARE #DateField DATETIME = GETDATE()
DECLARE #DateFieldTo DATETIME = GETDATE() +1
DECLARE #YourSQLVariable VARCHAR(MAX)
SET #WhereClause = #WhereClause + ' AND CAST(tbl.DateField AS DATE) BETWEEN CAST(#DateField AS DATE) + AND CAST(#DateFieldTo AS DATE)'
SET #YourSQLVariable = 'SELECT 1 WHERE' + #WhereClause
EXEC sp_executeSQL #YourSQLVariable,N'#DateField DATETIME,#DateFieldTo DATETIME ',#DateField,#DateFieldTo
And got:
Error: Procedure expects parameter '#statement' of type 'ntext/nchar/nvarchar'.
It's because you are trying to concatenate a DATE type CAST(tbl.DateField AS DATE) with a string #WhereClause, which is not supported, in this line:
SET #WhereClause = #WhereClause + ' AND CAST(tbl.DateField AS DATE) BETWEEN ''' + CAST(#DateField AS DATE) + ''' + AND ''' + CAST(#DateFieldTo AS DATE)+ ''
You can achieve this using something like this:
SET #WhereClause = #WhereClause + ' AND CAST(tbl.DateField AS DATE) BETWEEN CAST(#DateField AS DATE) + AND CAST(#DateFieldTo AS DATE)'
and when running sp_executeSQL, use this:
#YourSQLVariable = #SelectClause + #WhereClause
EXEC sp_executeSQL #YourSQLVariable,N'#DateField DATETIME,#DateFieldTo DATETIME ',#DateField,#DateFieldTo
You can try this for date casting or date comparison only w.r.t date,
DECLARE #SqlQuery VARCHAR(MAX) = ''
DECLARE #UserId VARCHAR(MAX) = 'TestUser'
DECLARE #StartDate DATETIME = GETDATE()
DECLARE #EndDate DATETIME = DATEADD(WEEK, -6, GETDATE())
SET #SqlQuery = 'SELECT * FROM User Z WHERE Z.Id = ''' + #UserId + ''' AND CAST(Z.CreatedDate AS DATE) BETWEEN CAST(''' + CONVERT(NVARCHAR(24), #StartDate, 101) + ''' AS DATE) AND CAST(''' + CONVERT(NVARCHAR(24), #ENDDATE, 101) +''' AS DATE)'
You can't concatenate date objects with strings:
DECLARE #WhereClause VARCHAR(MAX)
DECLARE #DateField DATETIME = GETDATE()
DECLARE #DateFieldTo DATETIME = GETDATE() +1
SET #WhereClause = #WhereClause + ' AND CONVERT(VARCHAR(10),tbl.DateField,120) BETWEEN ''' + CONVERT(VARCHAR(10),#DateField,120) + ''' + AND ''' + CONVERT(VARCHAR(10),#DateFieldTo,120)+ ''''
PRINT #WhereClause
SQL does not allow strings to be concatenated with other data types.
DECLARE #WhereClause VARCHAR(MAX) = ''
DECLARE #DateField DATETIME = GETDATE()
DECLARE #DateFieldTo DATETIME = GETDATE() +1
SET #WhereClause = #WhereClause + ' AND CAST(tbl.DateField AS DATE) BETWEEN ''' + CONVERT(VARCHAR(25), #DateField, 120) + ''' AND ''' + CONVERT(VARCHAR(25), #DateFieldTo, 120) + ''''
PRINT #WhereClause
Also, the #WhereClause was not defined, so the concatenation was not working with the initial null string.

Passing parameters to declated variable T-SQL

I have a small version of my problem in a stored procedure i would like to pass the parameters to as follows:
create procedure VariableTest
#Date1 DATETIME,
#Date2 DATETIME,
#CustomName1 NVARCHAR(100),
#CustomNum2 INT
AS
DECLARE #Condition NVARCHAR(MAX)
DECLARE #SQL NVARCHAR(MAX)
IF(LEN(#CustomName1) > 0)
SET #Condition = 'CustomerName = ' + #CustomName1;
IF(LEN(#CustomNum2) > 0 AND #CustomNum2 > 0)
SET #Condition = 'ClientNumber = ' + #CustomNum2;
SET #SQL =
'SELECT * FROM MyExampleTable ex
WHERE DateEx1 between ' + #Date1 + ' AND ' + #Date2 + '
AND ' + #Condition + '
ORDER BY ex.Sort'
exec #SQL
The problem i am running into, is that when i print the sql the dates do not have the a quotation around them and they are in datetime format, the other issue is that in the condition when the CustomerName is available the customer name comes out without quotes around them as well...
How can i make it so that in the sql string i can include those quotes around the parameters passed?.
OPTED SOLUTION:
create procedure VariableTest
#Date1 DATETIME,
#Date2 DATETIME,
#CustomName1 NVARCHAR(100),
#CustomNum2 INT
AS
DECLARE #Condition NVARCHAR(MAX)
DECLARE #SQL NVARCHAR(MAX)
IF(LEN(#CustomName1) > 0)
SET #Condition = 'CustomerName = #MyVal1';
IF(LEN(#CustomNum2) > 0 AND #CustomNum2 > 0)
SET #Condition = 'ClientNumber = #MyVal2';
DECLARE #MyParams NVARCHAR(200)
SET #MyParams = N'#date1 datetime, #date2 datetime, #MyVal1 NVARCHAR(200), #MyVal2 int';
SET #SQL =
'SELECT * FROM MyExampleTable ex
WHERE DateEx1 between #date1 AND #date2
AND ' + #Condition + '
ORDER BY ex.Sort'
EXECUTE exec sp_executesql
#SQL,
#MyParams,
#date1 = #Date1,
#date2 = #Date2,
#MyVal1 = #CustomName1,
#MyVal2 = #CustomNum2;
Your procedure is prone to sql-injection avoid concatenating parameters and use paramerterised queries like this....
CREATE PROCEDURE VariableTest
#Date1 DATETIME,
#Date2 DATETIME,
#CustomName1 NVARCHAR(100),
#CustomNum2 INT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQL NVARCHAR(MAX);
SET #SQL = N'SELECT * FROM MyExampleTable '
+ N' WHERE DateEx1 between #Date1 AND #Date2 '
+ CASE WHEN LEN(#CustomName1) > 0
THEN N' AND CustomerName = #CustomName1 ' ELSE N'' END
+ CASE WHEN LEN(#CustomNum2) > 0 AND #CustomNum2 > 0
THEN N' AND ClientNumber = #CustomNum2 ' ELSE N'' END
+ N' ORDER BY [Sort] '
exec sp_executesql #SQL
,N'#Date1 DATETIME, #Date2 DATETIME
#CustomName1 NVARCHAR(100), #CustomNum2 INT '
,#Date1
,#Date2
,#CustomName1
,#CustomNum2
END
You can do this without dynamic query
SELECT * FROM MyExampleTable ex
WHERE DateEx1 between #Date1 AND #Date2
AND ((LEN(#CustomName1) > 0 AND CustomerName = #CustomName1)
or (LEN(#CustomNum2) > 0 AND #CustomNum2 > 0 and ClientNumber = #CustomNum2))
ORDER BY ex.Sort
Considering that both the condition should be applied in where clause if both the IF clause is satisfied
Dynamic query.
You need to add few more quotes to get the quotes around date
IF( Len(#CustomName1) > 0 )
SET #Condition = 'CustomerName = ''' + #CustomName1 + '';
IF( Len(#CustomNum2) > 0
AND #CustomNum2 > 0 )
SET #Condition = 'ClientNumber = '
+ CONVERT(VARCHAR(50), #CustomNum2);
SET #SQL = 'SELECT * FROM MyExampleTable ex
WHERE DateEx1 between '''
+ CONVERT(VARCHAR(50), #Date1) + ''' AND '''
+ CONVERT(VARCHAR(50), #Date2) + '''
AND '
+ #Condition + '
ORDER BY ex.Sort'
exec(#SQL)
The link Jeroen posted is an excellent resource for using dynamic SQL, and well worth reading (especially for avoiding the aforementioned injection attack).
That said, what you want to use here is the system stored procedure sp_executesql. The idea is you can essentially pass in (and out) variables so you don't have to concatenate them if you don't need to. The quick dirty explanation is you declare an extra variable to hold declarations for the parameters you want to pass in, then you execute sp_executesql indicating the sql text (first parameter), which variable holds your parameter declarations (second parameter) and as many variables as you declared (3rd through nth parameters). Here's the MSDN documentation on sp_executesql:
https://msdn.microsoft.com/en-us/library/ms188001.aspx
Then, your code would look like this instead:
create procedure VariableTest
#Date1 DATETIME,
#Date2 DATETIME,
#CustomName1 NVARCHAR(100),
#CustomNum2 INT
AS
DECLARE #Condition NVARCHAR(MAX)
DECLARE #SQL NVARCHAR(MAX)
declare #parameters nvarchar(2000)
IF(LEN(#CustomName1) > 0)
SET #Condition = 'CustomerName = ' + #CustomName1;
IF(LEN(#CustomNum2) > 0 AND #CustomNum2 > 0)
SET #Condition = 'ClientNumber = ' + #CustomNum2;
SET #SQL =
'SELECT * FROM MyExampleTable ex
WHERE DateEx1 between #Date1 AND #Date2
AND ' + #Condition + '
ORDER BY ex.Sort'
set #parameters = '#date1 datetime, #date2 datetime'
exec sp_executesql
#sql,
#params,
#date1,
#date2
I don't particularly like your opted solution.
NoDisplayName got it right by not using the dynamic query. Using dynamic queries means that you're not going to get the benefits of the SP query plan cache.
The non-dynamic query also has added benefit as it generates compile issues if you create the SP with query errors. The dynamic SQL hides all that.
The only time you'll get performance issues is if you're using a LIKE statement. Values like "%ABC", "AB%C" and "ABC%" all use different query plans. You can safely cache the query plan when using equals.
This is what I'd use:
SELECT *
FROM MyExampleTable ex
WHERE DateEx1 between #Date1 AND #Date2
AND ((LEN(#CustomName1) > 0 AND CustomerName = #CustomName1)
OR (#CustomNum2 > 0 and ClientNumber = #CustomNum2))
ORDER BY ex.Sort
I'd also make the parameter passed NULL if it didn't contain anything.

SELECT dynamic columns GROUP BY dynamic columns

I need to accomplish the following in the stored procedure:
Pass parameterized column names.
Select the parameterized column names and provide total group by selected columns.
Code:
CREATE PROCEDURE sproc (
#column1 NVARCHAR(MAX),
#column2 NVARCHAR(MAX),
#startdate DATE,
#enddate DATE ) AS
BEGIN
DECLARE #sqlquery NVARCHAR(MAX) = 'SELECT #column1, #column2, SUM(amountcolumn)
FROM tablename
WHERE column3 = ''#value3'',
datecolumn BETWEEN ''#startdate'' AND ''#enddate''
GROUP BY #column1, #column2';
DECLARE #params NVARCHAR(MAX) = '#column1 VARCHAR(MAX),
#column2 VARCHAR(MAX),
#startdate DATE,
#enddate DATE';
EXEC sp_sqlexec #sqlquery, #params,
#column1 = #column1,
#column2 = #column2,
#startdate = #startdate,
#enddate = #enddate;
END
GO
Assuming #value3 is a string and is another parameter to the stored procedure, that datecolumn is in fact date, and ignoring the fact that I have no idea how you can have a schema where the grouping fields can be random like this (which you ignored in other recent questions here):
DECLARE #sql nvarchar(max) = N'SELECT '
+ QUOTENAME(#column1) + ', '
+ QUOTENAME(#column2) + ', SUM(amountcolumn)
FROM dbo.tablename
WHERE column3 = #value3
AND datecolumn BETWEEN #startdate AND #enddate
GROUP BY ' + QUOTENAME(#column1)
+ ', ' + QUOTENAME(#column2) + ';';
EXEC sys.sp_executesql #sql,
N'#value3 varchar(255), #startdate date, #enddate date',
#value3, #startdate, #enddate;
-- strongly recommend against sp_sqlexec
-- it is undocumented and unsupported
This also assumes you don't care about order (you probably do and will want to add ORDER BY as well as GROUP BY).
For more info on dynamic SQL and even further ways to protect yourself from user input:
sqlblog.org/dynamic-sql

Resources