I tried to figure out a dynamic query to get date col within past 20 days. The idea is quite simple and, moreover, I know the table does contain dates from getdate() to -20 days but still no result get returned
DECLARE #date_past_period varchar(MAX);
DECLARE #date_past_number varchar(MAX);
SET #date_past_period='day';
SET #date_past_number='20';
DECLARE #aDate datetime;
DECLARE #sql varchar(MAX);
SET #sql='SELECT date FROM table WHERE convert(varchar,date,121) BETWEEN convert(varchar,getdate(),121) AND convert(varchar,dateadd('+#date_past_period+', -'+#date_past_number+', getdate()),121)';
exec(#sql);
Maybe the problem is in dynamic thing but I am not sure.
Any useful comment is appreciated
You can use CASE function (T-SQL):
CREATE PROCEDURE MyStoredProcedure
#IntervalType VARCHAR(15),
#Num INT
AS
DECLARE #StartDate DATETIME = GETDATE();
DECLARE #EndDate DATETIME =
CASE #IntervalType
WHEN 'DAY' THEN DATEADD(DAY,#Num,#StartDate)
WHEN 'MONTH' THEN DATEADD(MONTH,#Num,#StartDate)
WHEN 'YEAR' THEN DATEADD(YEAR,#Num,#StartDate)
END;
IF #EndDate IS NULL
RAISERROR('Invalid params', 16, 1);
ELSE
SELECT date FROM table WHERE date BETWEEN #StartDate AND #EndDate;
By converting to VARCHAR your search condition from WHERE will not be SARG ( 1 & 2 ).
I am pretty sure this scenario can be covered without using dynamic SQL, however, one obvious problem in your SQL is the between clause - the range is in the wrong order. Try changing your #sql as below:
SET #sql='SELECT date FROM table WHERE convert(varchar,date,121) BETWEEN convert(varchar,dateadd('+#date_past_period+', -'+#date_past_number+', getdate()),121) AND convert(varchar,getdate(),121)';
Related
I'm getting the error
Invalid parameter 1 specified for dateadd.
when I try to execute the following dynamic parametrized query in SQL Server 2012:
DECLARE #Interval nvarchar(5) = 'DAY'
DECLARE #Increment int = 10
DECLARE #BaseDate date = getdate()
DECLARE #ResultDate date
DECLARE #Query nvarchar(2000)
SET #Query = 'SELECT #result = DATEADD(#Interval, #Increment, CAST(#BaseDate AS DATE))'
EXECUTE sp_executesql #Query,
N'#result date OUTPUT, #Interval varchar(50), #Increment int, #BaseDate date',
#Interval = #Interval, #Increment = #Increment,
#BaseDate = #BaseDate, #result = #ResultDate OUTPUT
SELECT #ResultDate
I've changed the SET #Query line to this one.
SET #Query = 'SELECT #result = DATEADD(' + #Interval +', #Increment, CAST(#BaseDate AS DATE))'
Although it works fine, I'm curious about why the first statement is causing the error in my dynamic SQL query?. Doesn't sp_executesql generate the same statement than the concatenated query one?
So the way to think about parameterized dynamic sql is you can only use a parameter where you could if it were static SQL. DATEADD expects a special date part keyword (e.g. day, hour, year, etc), not a literal string, and not a variable. It's the same issue some people run into where they think they can parameterize something like a table name. The first statement fails because even in static sql, this is invalid:
declare #increment nvarchar(5) = 'day'
select dateadd(#increment, 1, getdate())
That's equivalent to
select dateadd('day', 1, getdate())
The second statement succeeds because you're concatenating the string "day" which gets evaluated to the keyword.
In the first case, the query (with #Interval expanded to its value) becomes this:
SELECT #result=DATEADD('DAY', #Increment, CAST(#BaseDate AS DATE))
and in the second query it becomes this:
SELECT #result=DATEADD(DAY, #Increment, CAST(#BaseDate AS DATE))
The first query is invalid because there the first parameter to DATEADD is a string value, where the compiler expects a language keyword, and those are not the same in SQL.
For more info, see here: https://learn.microsoft.com/en-us/sql/t-sql/functions/dateadd-transact-sql
Please note the line under datepart saying User-defined variable equivalents are not valid. In other words you can't put quotes around these "values", they are not strings but keywords, and they can not be put in variables.
Below is the procedure I created. When I run the query individually I find records in the database but when I execute the procedure it is not fetching any records. where have I gone wrong?
ALTER PROCEDURE [dbo].[GetTransferList]
#date1 datetime='2015-01-01 00:00:00.000',
#date2 datetime='2017-01-01 00:00:00.000',
#shipto varchar(50)=''
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
-- Insert statements for procedure here
DECLARE
#sql NVARCHAR(MAX),
#paramlist NVARCHAR(4000),
#nl CHAR(2) = CHAR(13) + CHAR(10),
#ParamDefinition NVarchar(2000);
SET #sql = 'SELECT A.ItemDescription,A.PurchaseOrderID,A.QuantityReceived,A.Price,A.StoreID,
C.ItemType,C.BinLocation,
B.PONumber,B.ShipTo,B.StoreID
FROM [dbo].[PurchaseOrderEntry] A, [dbo].[PurchaseOrder] B,[dbo].[Item] C
WHERE A.PurchaseOrderID=B.ID AND A.ItemID=C.ID ';
IF (#date1 IS NOT NULL) AND (#date2 IS NOT NULL )
SET #sql += ' AND B.[RequiredDate] between #date1 AND #date2';
IF #shipto IS NOT NULL --!='ALL'
SET #sql += ' AND B.ShipTo = #shipto ';
SET #sql += ' GROUP BY C.BinLocation,A.Price,C.ItemType, B.ID ,A.ItemDescription,
A.PurchaseOrderID,A.QuantityReceived,A.StoreID,B.PONumber,B.ShipTo,B.StoreID'
Set #ParamDefinition =' #shipto varchar(50),
#date1 datetime,
#date2 datetime'
Execute sp_Executesql #sql,
#ParamDefinition,
#shipto,
#date1,
#date2
If ##ERROR <> 0 GoTo ErrorHandler
Set NoCount OFF
Return(0)
ErrorHandler:
Return(##ERROR)
END
the query fetching value is below,
SELECT A.ItemDescription,A.PurchaseOrderID,A.QuantityReceived,A.Price,A.StoreID,
C.ItemType,C.BinLocation,
B.PONumber,B.ShipTo,B.StoreID
FROM [dbo].[PurchaseOrderEntry] A, [dbo].[PurchaseOrder] B,[dbo].[Item] C
WHERE A.PurchaseOrderID=B.ID AND A.ItemID=C.ID
GROUP BY C.BinLocation,A.Price,C.ItemType, B.ID ,A.ItemDescription,
A.PurchaseOrderID,A.QuantityReceived,A.StoreID,B.PONumber,B.ShipTo,B.StoreID
I suspect your biggest problem is #shipto varchar(50)=''. In your proc you then test for null
So this line....
IF #shipto IS NOT NULL
Will not be true if you don't pass anything in it will equal an empty string (unless you actually pass a null value in)
Change this to:
#shipto varchar(50) = null
Then you can test for null as you are doing and this code
IF #shipto IS NOT NULL --!='ALL'
SET #sql += ' AND B.ShipTo = #shipto ';
Will be test true if you do not provide this parameter.
If you ever want to add AND B.ShipTo = '' just pass in a value of '' to #shipto parameter.
However, this bit doesn't need to be dynamic as this will do the same thing
b.shipto = Coalesce(#shipto, B.ShipTo)
The same goes for your date parameters. They will only ever be null if you actually pass in a null value.
As you have no aggregate functions such as SUM, MAX, COUNT etc you don't need the group by.
So my personal preference here would be....
ALTER PROCEDURE [dbo].[GetTransferList]
#date1 datetime=null,
#date2 datetime=null,
#shipto varchar(50) = null
--This will make your parameters optional. IE. You dont have to provide them when you call it
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
IF(#date1 IS NULL) SET #date1 = cast('1899-1-1' as datetime) --This could be anytime in the past thatis prior to any records you hold
IF(#date2 IS NULL) SET #date2 = cast('2100-1-1' as datetime) -- A date well in the future
--You could also set directly as the default values for the parameter but if you do you will have a problem if `null` is passed in.
SELECT A.ItemDescription,A.PurchaseOrderID,A.QuantityReceived,A.Price,A.StoreID,
C.ItemType,C.BinLocation,
B.PONumber,B.ShipTo,B.StoreID
FROM [dbo].[PurchaseOrderEntry] A, [dbo].[PurchaseOrder] B,[dbo].[Item] C
WHERE A.PurchaseOrderID=B.ID AND A.ItemID=C.ID AND B.[RequiredDate] between #date1 AND #date2
AND B.ShipTo = COALESCE(#shipto, b.ShipTo)
If ##ERROR <> 0 GoTo ErrorHandler
Set NoCount OFF
Return(0)
ErrorHandler:
Return(##ERROR)
END
Now you can provide one date (return all records up to a date or all records after a date) or two dates (return all records between dates) or not provide any to see them all. Same for #shipto.
I strongly believe that your date parameter is not matching with the available records, as the query that works fine out side the procedure does not have date condition.
The Date parameters in the proc is marked with default values
if the front end is C# (not sure about other front end code).
The date parameter will be passed with minimum date value or
Null value will be passed if you are not added date parameters in the front end code
But both the case date parameter will have some date values.
Assign the date values to your working query as condition and check.
As asked by Mr.Fred, grouping in your query may not required unless if you are going to have any aggregated fields.
I'm going through stored procedures to make them sargable and I noticed something unexpected about how the index was used.
There's a non-clustered index on DateColumn, and a clustered index on the table (not directly referenced in the query).
While the following uses an index seek on the non-clustered index that has DateColumn as an index column:
DECLARE #timestamp as datetime
SET #timestamp = '2014-01-01'
SELECT column1, column2 FROM Table WHERE DateColumn > #timestamp
However the following uses an index scan:
DECLARE #timestamp as datetime
DECLARE #flag as bit
SET #timestamp = '2014-01-01'
SET #flag = 0
SELECT column1, column2 FROM Table WHERE (DateColumn > #timestamp) OR (#flag = 1)
I put the brackets in just in case, but of course it made no difference.
Because the #flag = 1 has nothing to do with the table, I was expecting a seek in both cases. Out of interest if I change it to 0 = 1 it uses index seek again. The #flag value is a parameter for the procedure that tells the query to return all records, so not something I can hard code in reality.
Is there a way to make this use a seek instead of a scan? The only option I can think of is the following, however in reality the queries are much more complex, so duplication like this hurts readability and maintainability:
DECLARE #timestamp as datetime
DECLARE #flag as bit
SET #timestamp = '2014-01-01'
SET #flag = 0
IF #flag = 1
BEGIN
SELECT column1, column2 FROM Table
END
ELSE
BEGIN
SELECT column1, column2 FROM Table WHERE DateColumn > #timestamp
END
Try with dynamic SQL like this.
DECLARE #flag BIT,
#query NVARCHAR(500)
SET #flag=0
SET #query='
SELECT <columnlist>
FROM <tablename>
WHERE columnname = value
or 1=' + CONVERT(NVARCHAR(1), #flag)
EXEC Sp_executesql
#query
Your dynamic solution is actually better because you won't get caught out when you pass in #flag=1 the first time and that's what you get for all subsequent calls. As #RaduGheorghiu says, a scan is better than a seek in these cases.
If it were me I would have 2 procedures, one for "get everything" and one for "get for date". Two procedures, two usages, two query plans. If the repetition bothers you, you can introduce a view.
Just for completeness I'm going to post an option that I just realised works in my specific situation. This is probably what I'm going to use due to simplicity, however this probably won't work for 99.9% of cases, so I don't consider it a better answer than the dynamic SQL.
declare #flag as int
declare #date as datetime
set #flag = 1
set #date = '2015-08-11 09:12:08.553'
set #date = (select case #flag when 1 then '1753-01-01' else #date end)
select Column1, Column2
from Table_1
where DateColumn > #date
The above works because the DateColumn stores the modified date for the record (I'm returning deltas). It has a default value of getdate() and is set to getdate() on updates. This means in this specific case I know that for a value of #date = '1753-01-01' all records will be returned.
The code select DATEPART(month,getutcdate()) returns an integer representing the numeric month value of the current date.
The value MONTH is one of a number of predefined options for the interval parameter.
What is the underlying data type of that interval value?
declare #date datetime = getutcdate()
, #interval notNVarChar(16) = 'month' --what type should this be?
--Would it need quotes for assignment?
select DATEPART(#interval, #date)
You can make it parameterize by making a dynamic SQL as below:
declare #date datetime
set #date = GETDATE()
declare #option varchar(50)
declare #sql varchar(max)
SET #option = 'MONTH' --Here you can set other options like hour, second, milisecond etc..
set #sql = 'SELECT DATEPART('+ #option + ',''' + CONVERT(varchar,#date,21)+''')'
print #sql
EXEC( #sql)
There isn't one - you cannot parameterize it.
From the documentation:
DATEPART ( datepart , date )
datepart
Is the part of date (a date or time value) for which an integer will be returned. The following table lists all valid datepart arguments. User-defined variable equivalents are not valid.
(my emphasis)
There are lots of options available like :
day
week
hour
minute
second
millisecond
etc..
PLease go thru thisLINK
hey guys,
i want to convert datetime from string, i have string which contains 'GETDATE' now i want to convert the same in datetime, the string in sql server is been defined as varchar(max)
Please reply how can i type cast the GETDATE() function from string to datetieme.
Regards
Abbas Electricwala
I guess that you mean that you have a string that contains the text GetDate() like this.
declare #S varchar(50)
set #S = 'getdate()'
And you want to turn that into a date variable executing getdate().
You could do this since you know what what getdate() means.
declare #S varchar(50)
set #S = 'getdate()'
declare #D datetime
set #D = (select
case #S when 'getdate()'
then getdate()
else null
end)
Does not really make sense, there probably is more to this than you are telling.
If you have just date string you can do it like this:
SELECT CONVERT(DATETIME, '2009-02-25 15:31:17.888')
But... GETDATE() returns a datetime value, not a string value. Just assign the GETDATE() result to your variable.
Sample (may be inacurate):
declare #mydate as datetime
set #mydate = GETDATE()