Pivot table not worked by dynamic parameter - sql-server

I wrote this query:
declare #startdate nvarchar(10) = '2016/01/01',
#enddate nvarchar(10) = '2019/10/04',
#cols nvarchar(max),
#strsql nvarchar(max);
select
#cols =
(select distinct QUOTENAME(maintenancename)+','
from (select distinct maintenancename
from Table_maintenancetype where coalesce(maintenancename, '') != '') AS t1
for xml path('')
)
set #cols=LEFT(#cols,LEN(#cols)-1)
set #strsql=N'select *
from
(select sum(timedurationok) as wt, maintenancetype,tcode
from Table_maintenancereport
where ((svok=1 and need_netok=0) or (svok=1 and netok=1 and need_netok=1))and tcode<>-1
and dateendrole >='+ #startdate+ 'and dateendrole<='+ #enddate+'
group by maintenancetype,tcode
) d
pivot
(sum(wt)
for maintenancetype in ('+#cols+')
) piv
'
exec sp_executesql #strsql
When execute this no record return but when replace #startdate and #enddate by '2016/01/01','2019/10/04'
The query works fine I don't know what the problem is... and when use between in query I get error for can not convert nvarchar to data int 2016

first, do a print #strsql to show your dynamic query and check.
You will see that your query is dateendrole >= 2016/01/01
the date should be in single quote dateendrole >= 2016/01/01'
your query should be
and dateendrole >= '''+ #startdate+ ''' and dateendrole<= '''+ #enddate+'''
a better way is to use parameter with sp_executesql
declare both the variable as date data type since it is going to hold date value
declare #startdate date = '20160101',
#enddate date = '20191004'
and in your dynamic query, just use the variable
and dateendrole >= #startdate and dateendrole <= #enddate
finally when calling sp_executesql, pass in the variable. And don't forget to print out the verify your query
print #strsql
exec sp_executesql #strsql, N'#startdate date, #enddate date', #startdate, #enddate

Related

Utilising optional parameters in where clause

I have the following sample query within a stored procedure where the #StartDate, #EndDate and #ClientID parameters are all optional.
What is the best way to handle that within the query to ensure I get a result depending on whether 1 or many parameters have values?
select * from table
WHERE
StartDate >= #StartDate and
StartDate <= #EndDate and
CE.ClientID = #ClientID
For Example, someone could just enter a Start Date or just enter an End Date or select a particular ClientID or do a combination of all 3.
If you're willing to sacrifice a tiny amount of time on each execution, OPTION(RECOMPILE) will provide the performance equal to dynamic SQL but without all the perils of it.
select * from table
WHERE
(StartDate >= #StartDate or #StartDate is null) and
(StartDate <= #EndDate or #EndDate is null) and
(CE.ClientID = #ClientID or #ClientID is null)
option(recompile)
You can do something like this -
SELECT * FROM table
WHERE
(#StartDate IS NULL OR StartDate >= #StartDate) AND
(#EndDate IS NULL OR StartDate <= #EndDate) AND
(#ClientID IS NULL OR CE.ClientID = #ClientID)
The best way is to use dynamic SQL. Something like this:
declare #sql nvarchar(max);
set #sql = 'select * from table';
declare #where nvarchar(max);
set #where = (case when #StartDate is not null then ' and StartDate >= #StartDate' else '' end) +
(case when #EndDate is not null then ' and EndDate >= #EndDate' else '' end) +
(case when #ClientID is not null then ' and ClientID = #ClientID' else '' end);
set #where = stuff(#where, 1, 5, '');
set #sql = #sql + (case when len(#where) > 0 then ' where ' + #where' else '');
exec sp_executesql #sql,
N'#StartDate date, #EndDate date, #ClientId int',
#StartDate = #StartDate, #EndDate = #EndDate, #ClientId = ClientId;
The reason this is better is because each possible combination of inputs results in a different query. SQL Server can optimize the queries using appropriate indexes, and such optimization can be important when using optional parameters.

How to convert/cast column data type in concatenated SQL query string

i have the following variables defined in my stored procedure
#StartDate DateTime,
#EndDate DateTime,
I'm setting the sql to be executed dynamically, so when constructing the query where clause i have the below line.
SET #sql = #sql + ' AND (convert(datetime, R.ReportDate, 121) >= ' + #StartDate + 'AND convert(datetime, R.ReportDate, 121) <=' + #EndDate +')'
When i execute the stored procedure, the line above throws the error below
Conversion failed when converting datetime from character string.
If i change the variable datatype to NVARCHAR(MAX), the procedure executes successfully but then returns no rows because the date comparison/matching fails.
ReportDate column is of datatype datetime and has data in this format 2014-06-01 00:00:00.000
As you can see i have tried converting the column when constructing my query but that isn't working.
The problem is not with ReportDate, but when you are trying to concatenate your DateTime parameters with your nvarchar sql statement. The problem can be reproduced fairly simply with:
DECLARE #SQL NVARCHAR(MAX) = 'Some text' + GETDATE();
The wrong way to fix this is to convert the datetime parameter to a string so that it can be concatenated with a string, e.g.
SET #sql = #sql + ' AND r.ReportDate >= CONVERT(DATETIME, '''
+ CONVERT(VARCHAR(10), #StartDate, 112)
+ ''', 112) AND r.ReportDate <= CONVERT(DATETIME, ''' +
+ CONVERT(VARCHAR(10), #EndDate, 112)
+ ''', 112)';
N.B. I am including this for completeness of the answer, and in no way endorse this approach
The correct way to fix this is to use sp_executesql and pass properly typed parameters this way, this will avoid conversion issues. e.g.
SET #sql = #sql + 'AND r.ReportDate >= #StartDateParam AND r.ReportDate <= #EndDateParam';
EXECUTE sp_executesql
#sql,
N'#StartDateParam DATETIME, #EndDateParam DATETIME',
#StartDateParam = #StartDate,
#EndDateParam = #EndDate

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;

DATE parameter passed as 'YYYY-MM-DD' not being properly evaluated by a stored procedure?

Given:
CREATE PROCEDURE [dbo].[sp_func]
#StartDate DATE
, #EndDate DATE
AS
select * from [TRANS] where
(#StartDate IS NULL OR [TransDate] >= #StartDate) AND (#EndDate IS NULL OR [TransDate] <= #EndDate)
Both this:
declare #StartDate DATE = '2015-01-01'
declare #EndDate DATE = '2015-12-31'
exec dbo.sp_func #StartDate, #EndDate
And this:
declare #StartDate DATE = NULL
declare #EndDate DATE = NULL
exec dbo.sp_func #StartDate, #EndDate
...return rows as expected.
However, both of these calls return 0 rows:
exec dbo.sp_func '2015-01-01', '2015-12-31'
exec dbo.sp_func NULL, NULL
Am I fundamentally misunderstanding something here? As far as I can recall, I’ve always called sp’s passing dates like 'YYYY-MM-DD' with absolutely no issues.
Possibly related...this:
select CAST('2015-01-01' AS DATE), CAST('2015-12-31' AS DATE)
returns:
2015-01-01 2015-12-31
Whereas this:
exec dbo.sp_func CAST('2015-01-01' AS DATE), CAST('2015-12-31' AS DATE)
returns:
Incorrect syntax near '2015-01-01'.
I feel like I'm taking crazy pills.
I discovered the problem, it was stupidity.
Not shown in the example, I had other parameters in my production code, one of which was:
#JobClass VARCHAR(10) = 'PRODUCTION'
What I had been doing wrong in my test code was passing NULL to this, and incorrectly thinking that a passed NULL to this would instead default to 'PRODUCTION', whereas this defaulting only happens when the parameter is not passed at all. Which I knew of course. One of those days.
A couple of things , I would re-write the whole proc as below:
Procedure Definition
CREATE PROCEDURE [dbo].[usp_Proc] --<-- do not use sp_
#StartDate DATE = NULL
, #EndDate DATE = NULL
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(MAX);
SET #sql = N' select * from [TRANS] where 1 = 1 '
+ CASE WHEN #StartDate IS NOT NULL
THEN N' AND [TransDate] >= #StartDate' ELSE N' ' END
+ CASE WHEN #EndDate IS NOT NULL
THEN N' AND [TransDate] <= #EndDate ' ELSE N' ' END
Execute sp_executesql #sql
,N'#StartDate DATE,#EndDate DATE '
,#StartDate
,#EndDate
END
Execute Procedure
exec dbo.usp_Proc '20150101', '20151231' --<-- Pass date in ANSI format 'YYYYMMDD'
How about specifying the variable names and values like this:
EXECUTE dbo.sp_func #StartDate='2015-01-01',#EndDate='2015-12-31'
Works on my machine... :)

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