Pass Datetime in SQL query in string format? - sql

I have string query in and pass #Date object to string. It is giving error. See below code.
Declare #MidDate datetime, #MaxDate datetime
set #qrysales_trans_unit_26wks ='update historical_result
set sales_trans_unit_26wks = (
SELECT
SUM(sales_trans_unit)
FROM reg_summary_rowno WHERE
period_idx >= '+ #MidDate // error
+' AND period_idx <'+ #MaxDate /error
+' AND Client_id ='+ #Client_id
+' and historical_result.[store_idx] = reg_summary_rowno.[store_idx]
And [attributes] ='+ #attributes +')'
How to pass Datetime object in the proper way to string Query?

Try using two single quotes to escape quote marks so dates end up like: period_idx >= '#MidDate'
set #qrysales_trans_unit_26wks ='update historical_result
set sales_trans_unit_26wks = (
SELECT
SUM(sales_trans_unit)
FROM reg_summary_rowno WHERE
period_idx >= '''+ #MidDate
+''' AND period_idx <'''+ #MaxDate
+''' AND Client_id ='+ #Client_id
+' and historical_result.[store_idx] = reg_summary_rowno.[store_idx]
And [attributes] ='+ #attributes +')'
Click here for more information on escaping quotes in SQL.

A couple of better options, IMHO.
If you really want to use dynamic SQL, read up on sp_executesql - and use the ability to pass in parameters to the SQL. You'll prevent SQL injection attacks this way and will also avoid running into problems with having to string-ify parameter values.
Otherwise, used stored procedures - which I would consider the better option here.

To fix your ERROR, you need to add some single quotes ' around the dates within the string.
One more thing which improves clarity. Use the BETWEEN keyword:
WHERE period_idx BETWEEN #MinimumDate AND #MaximumDate

You can use instead of datetime a smalldatetime
And you may use the dates like this :
Declare #MidDate smalldatetime,
set #MidDate = '20110317'
Hope it helps.

If you must pass a date in string format - first of all, put it in quotes, and second of all, I would strongly urge you to use the standard ISO-8601 date format (YYYYMMDD or YYYY-MM-DDTHH:MM:SS).
The big benefit of these ISO standard formats is that they'll work no matter what language and regional settings your SQL Server is set to. Any other string representation is language-dependent, e.g.
05/10/2010
will mean:
10th of May 2010 in the US
5th of October 2010 in pretty much all of the rest of the world
but 20101005 is clear and never ambiguous - it's always the 5th of October, 2010 - even for in the US :-)

I think you should use convert before concatenate the date variable with the sentence
Declare #MidDate datetime, #MaxDate datetime
set #qrysales_trans_unit_26wks = 'update historical_result
set sales_trans_unit_26wks = (
SELECT
SUM(sales_trans_unit)
FROM reg_summary_rowno
WHERE
period_idx >= '+ '''' + convert(varchar, #MidDate, 112) + '''' // error
+' AND period_idx <'+ '''' + convert(varchar, #MaxDate, 112) + '''' /error
+' AND Client_id ='+ #Client_id
+' and historical_result.[store_idx] = reg_summary_rowno.[store_idx]
And [attributes] ='+ #attributes +')'

I would really recommend that you shy from concatenating SQL this way. It will really open you to injection attacks etc.
Look at this sample to see another approach that you might take.
use tempdb
create table foo (id int not null identity, data datetime)
insert foo(data) values
('1/1/2010'),('1/10/2010'),('3/31/2010')
Declare #SQLStr nvarchar(4000)
set #SQLStr = 'select * from foo where data = #Data'
exec sp_executeSQL #SQLStr, N'#Data datetime', '1/1/2010'

Related

Using sp_executesql with input and output paramters?

Using SQL Server 2016. I am writing a stored procedure. I have the following code that is happening inside a WHILE loop
DELCARE #recordExists int;
SET #recordExistsQuery = 'SELECT #recordExists=COUNT(*) FROM #fullTableName WHERE validFrom <= CAST(#asOfDate as datetime)';
exec sp_executesql #recordExistsQuery, N'#recordExists INT OUT, #fullTableName varchar(60), #asOfdate datetime' #recordExists OUT
I get the error `Must declare the table variable #fullTableName however I have already declared and set this variable (prior to the while loop) and used it multiple times prior the while loop so I know it exists and is valid. It's defined like so -
DECLARE #fullTableName varchar(60);
SET #fullTableName = (SELECT CONCAT(#schema, '.', #TableName));
and I have it printed prior to the while loop and it looks fine, and I have it printer per loop and that works as well.
What is wrong with my dynamic sql here? I am trying to use the paramterization method instead of string building with quotes as I am dealing with a datetime and want to do that with better practices than a bunch of quotes. Is that possible? How can I rewrite
SET #recordExistsQuery = 'SELECT #recordExists=COUNT(*) FROM #fullTableName WHERE validFrom <= CAST(#asOfDate as datetime)';
exec sp_executesql #recordExistsQuery, N'#recordExists INT OUT, #fullTableName varchar(60), #asOfdate datetime' #recordExists OUT
so that it works as expected?
UPDATE: Hardcoding the tablename instead of passing it as a parameter worked. I am now getting the following error from the following code -
SET #recordExistsQuery = 'SELECT #recordExists=COUNT(*) FROM ' + #fullTableName + ' WHERE validFrom<=CAST(#asOfDate as datetime)';
PRINT #recordExistsQuery
exec sp_executesql #recordExistsQuery, N'#asOfDate datetime, #recordExists INT OUT', #recordExistsOut`
The print statement shows
SELECT #recordExists=COUNT(*) FROM [MySchema].[MyTable] WHERE validFrom<=(#asOfDate as datetime)
The error I now get is
Msg 8162, Level 16, State 2, Line 0
The formal parameter "#asOfDate" was not declared as an OUTPUT paramter, but the actual paramter passed in requested output.
I have #asOfDate as a paramter of my stored proecedure defined as
#asOfDate DATETIME=NULL and the first line of my sp sets a default value if none is passed in
IF #asOfdate IS NULL
SET #asOfDate = GETDATE();
Anyone know what is going wrong now?
UPDATE 2:
Using this line instead
'SELECT #recordExists=COUNT(*) FROM '+ #fullTableName +' WHERE validFrom <= CAST('+#asOfDate'+' as datetime)'
Now gets me the error Msg 241, Level 16, State 1, Conversion failed when converting date and/or time from character string.
#asOfDate is defined as a paramter of my sp like #asOfDate datetime=NULL and initialized to getdate(). I am testing this with #asOfDate=null, so it is using getdate() to initialize the value.
Try splitting the string like this
'SELECT COUNT(*) FROM'+ #fullTableName +'WHERE validFrom <= CAST('+#asOfDate+' as datetime)'
since #fullTableName is going as a string and not a variable, it was not working. If you change to the above, the final string will have the table name you wanted
This update should fix the asofdate issue
Also, you need to remove #recordExists and use the response from execution as #recordExists
Could not quite get it to work as it was, so I ended up doing the following - when I initialized the date I also made a formatted version of it -
paramters -
#asOfDate DATETIME=NULL,
...
BEGIN
IF #asOfdate IS NULL
SET #asOfDate = GETDATE()
DECLARE #asOfDateFormatted NVARCHAR(30);
SET #asOfDateFormatted = CONVERT(NVARCHAR(30), #asOfDate, 126);
...
Dynamic sql -
SET #recordExistsQuery = 'SELECT COUNT(*) FROM '+ #fullTableName +' WHERE validFrom <= '''+asOfDateFormatted+'''';
exec sp_executesql #recordExistsQuery, N'#recordExists INT OUT', #recordExists OUT
If someone knows a better way please let me know but this is how I got it to work for now.
When using dynamic SQL, bear in mind the following:
Table names (and other objects such as columns) cannot be passed as parameters, they must be injected.
Only do this using QUOTENAME to ensure proper escaping
Object names should be stored in sysname (alias for nvarchar(128)) and queries in nvarchar(max)
Data should always be passed as parameters, not injected
DECLARE #asOfDate DATETIME='20210923',
IF #asOfdate IS NULL
SET #asOfDate = GETDATE();
DECLARE #recordExistsQuery nvarchar(max) = N'
SELECT #recordExists = COUNT(*)
FROM ' + QUOTENAME(#schema) + '.' + QUOTENAME(#TableName) + '
WHERE validFrom <= #asOfdate;
';
PRINT #recordExistsQuery; -- for testing
EXEC sp_executesql
#recordExistsQuery,
N'#recordExists INT OUT, #asOfdate DATETIME',
#recordExists = #recordExists OUT,
#asOfdate = #asOfdate;

Use Open Query with quoted items

I am trying to use openquery to access a linked server. However, it appears to be impossible because, you cannot have the correct number of Single quotes. I need to pass a variable Start and End date, so I cannot use the basic openquery method, but instead must use the EXEC(#OPENQUERY+ #SQL) method. The problem is, to pass the date through the #SQL variable I must use ''' so it has 1 quote, but then when it gets passed to the EXEC(OPENQUERY+#SQL) the open query which introduces another level of quotes, causes the dates to now be not be quoted and I get an error. If I add another layer of quotes it then causes them to be double quotes causing that error. Is it not possible to use quotes in an open query? I have the same issue even passing things like Where Username = 'Jack'. I can never have the correct number of quotes.
DECLARE #STARTDT NVARCHAR(10) = '2019-01-01'
,#ENDDT NVARCHAR(10) = '2019-03-01'
DECLARE #SQL NVARCHAR(4000)
DECLARE #OPENQUERY nvarchar(4000)
, #LinkedServer nvarchar(4000)
SET #LinkedServer = 'ProductionSvr'
SET #SQL =
'select *
from SalesData a
where a.Sale_date between ''' + #StartDt + ''' and ''' + #ENDDT + ''')
'''
print #SQL
SET #OPENQUERY = 'SELECT * FROM OPENQUERY('+ #LinkedServer + ','''
EXEC (#OPENQUERY+#SQL)
Within a quoted string, you have to escape a single quote with an additional single quote, which is where you start to see the four and five single quotes in a row. In order to help simplify things, I would make a couple of suggestions.
First, keep your strings more segregated in their duties. Your #SQL variable includes the necessary ending punctuation for your OPENQUERY, but the beginning punctuation is in the #OPENQUERY variable. (This might be clearer in the code below.)
Also, I'd recommend using proper data types (such as your dates), and then use the CONCAT function, which conveniently handles all of the data type conversions for you.
So, the modified version of what you started with would look like this:
DECLARE #OPENQUERY nvarchar(4000)
, #LinkedServer nvarchar(256)
, #SQL NVARCHAR(4000);
--Set your dates. Use the right data type to avoid sending
--invalid dates into your query. Easier to debug them here.
DECLARE #STARTDT DATE = '2019-01-01'
,#ENDDT DATE = '2019-03-01';
--Set your server.
SET #LinkedServer = 'ProductionSvr';
--Then set up the inner query.
SET #SQL =
CONCAT(
'select *
from SalesData a
where a.Sale_date between ''', #StartDt, ''' and ''', #ENDDT,'''');
--Set up the OPENQUERY variable with all of the punctuation that it needs,
--so you just need to drop in your LinkedServer name and your SQL statment.
--Use CONCAT because it handles the data type conversions for you.
SET #OPENQUERY = CONCAT('SELECT * FROM OPENQUERY(',#LinkedServer,',''(',#SQL,')'')');
PRINT #OPENQUERY;
Result:
SELECT * FROM OPENQUERY(ProductionSvr,'(select *
from SalesData a
where a.Sale_date between '2019-01-01' and '2019-03-01')')
How about this:
DECLARE #STARTDT NVARCHAR(10) = '2019-01-01'
,#ENDDT NVARCHAR(10) = '2019-03-01'
DECLARE #SQL NVARCHAR(4000)
DECLARE #OPENQUERY nvarchar(4000)
, #LinkedServer nvarchar(4000)
SET #LinkedServer = 'ProductionSvr'
SET #SQL =
'SELECT * FROM OPENQUERY(' + #LinkedServer + ',''select *
from SalesData a
where a.Sale_date between ''' + #StartDt + ''' and ''' + #ENDDT + ''')'
print #SQL
All you have to remember is within quotes, double single quotes means 1 single quote. So, ''')' essentially translates to "')". Hope this helps.

Select into tables dynamically with variables

I have some code to create tables based on a set of dates I define.
Example, I have 5 dates, and they are aren't consecutive. For any of these dates, I want to create a table and I am currently using a Select into.
I am having to do this 5 times, even though the only thing changing is the name of the new table created and the date. Is there a way to do this in an elegant way.
I started writing some code, but I am struggling to get it to loop through all the dates I want. The way I have written it currently, I only works if I edit the date at the start.
DECLARE #MyDate DATE;
SET #MyDate = '2019-01-01';
SET #TableName = 'Table1';
SELECT *
into #TableName
FROM Original_Table
WHERE Query_Date = #MyDate;
Is this a one time thing or do you have to do this on a regular basis?
If it's the first, than I would just do it and get it over with.
If it's the latter, then I suspect something is very wrong with the way that system is designed - but assuming that can't be changed, you can create a stored procedure that will do this using dynamic SQL.
Something like this can get you started:
CREATE PROCEDURE dbo.CreateTableBasedOnDate
(
#MyDate DATE,
-- sysname is a system data type for identifiers: a non-nullable nvarchar(128).
#TableName sysname
)
AS
-- 200 is long enough. Yes, I did the math.
DECLARE #Sql nvarchar(200) =
-- Note: I'm not convinced that quotename is enough to protect you from sql injection.
-- you should be very careful with what user is allowed to execute this procedure.
N'SELECT * into '+ QUOTENAME(#TableName) +N'
FROM Original_Table
WHERE Query_Date = #MyDate;';
-- When dealing with dynamic SQL, Print is your best friend.
-- Remark this row and unremark the next only once you've verified you get the correct SQL
PRINT #SQL;
--EXEC sp_ExecuteSql #Sql, N'#MyDate Date', #MyDate
GO
Usage:
EXEC CreateTableBasedOnDate '2018-01-01', 'zohar';
Use dynamic SQL:
DECLARE #MyDate DATE, #TableName varchar(50);
SET #MyDate = '2019-01-01';
SET #TableName = 'Table1';
DECLARE #sql NVARCHAR(4000);
DECLARE #params NVARCHAR(4000);
SELECT #sql=N'
SELECT *
INTO ' + QUOTENAME(#TableName) + '
FROM Original_Table
WHERE Query_Date = #MyDate;';
SELECT #params = N'#MyDate DATE';
EXEC sys.sp_executesql #sql, #params, #MyDate=#MyDate
Note that dynamic SQL can be dangerous as it opens up a path for SQL injection. Its fine if you are just using it in your own local scripts, but take care if you e.g. wrap this in a procedure that is more widely accessible.
I would use dynamic SQL although I would add another variables for the schema:
DECLARE
#MyDate nVarchar(50) = '2019-01-01',
#Schema nVarchar (50) = 'dbo',
#TableName nVarchar(250) = 'Table1',
#SQL nVarchar(500);
Set #SQL = '
SELECT *
into '+ QUOTENAME(#Schema)+'.'+ QUOTENAME(#TableName) +'
FROM Original_Table
WHERE Query_Date = '+ #MyDate +';
'
--print #SQL
Exec(#SQL)
You can use the print statement to see how the SQL will look before executing this properly. You may also want to look at adding this as a stored procedure.

sp_executesql with datetime output parameter

I'm facing a problem here in SQL Server. I have to use the stored procedure sp_executesql that is a system procedure, because the table on which I do my select clause depends on an other parameter.
Here is the sample :
#p_Origin is a parameter that is given in my procedure, so to reproduce the problem let's declare it like this :
DECLARE #p_Origin nvarchar(255) = 'Sales'
I also have one parameter in my SELECT clause, that are columns of the target table :
DECLARE #v_valueVersion as int
IF #p_Origin = 'Sales'
SET #v_valueVersion = (SELECT VersionId FROM Version WHERE ...)
ELSE IF
...
But now, I want to get back a column from my origin table, (which is Sales here)
With this query :
DECLARE #v_query as nvarchar(max) = 'SELECT MAX(Date) FROM dbo.' + #p_Origin + ' WHERE VersionId = ' + CAST(#v_valueVersion as nvarchar) + ' AND Month = CONVERT(NVARCHAR(6),GETDATE(),112) '
This column, date, is a datetime column, declared here : (Month column and date column are two different things, and they don't refer the same period)
DECLARE #v_maxDate as datetime
And here is my problem :
EXEC sp_executesql #v_query, N'#v_maxDate datetime out', #v_maxDate out
When I get this, the SELECT clause give me the following result, which is the good one :
-----------------------
2016-01-19 15:49:58.000
But When I PRINT the value, nothing is printed, and the value is null. How can I get back some datetime value with this stored procedure ? Is it even possible ?
EDIT : as Adwaenyth said bellow, the following query works perfectly :
DECLARE #query nvarchar(max) = 'SELECT #v_maxDate = MAX(create_date) FROM sys.tables'
DECLARE #v_maxDate datetime
EXEC sp_executesql #query, N'#v_maxDate datetime out', #v_maxDate out
PRINT #v_maxDate
Is it possible that the fact that I use several variables in my query modifies the result ?
Change your query to
DECLARE #v_query as nvarchar(max) = 'SELECT #v_maxDate = MAX(DATE) FROM dbo.' + #p_Origin + ' WHERE VersionId = ' + CAST(#v_valueVersion as nvarchar) + ' AND Month = CONVERT(NVARCHAR(6),GETDATE(),112) '
and it should work.
/edit: just tried it myself with this ad-hoc query and it worked perfectly:
DECLARE #query nvarchar(max) = 'SELECT #v_maxDate = MAX(create_date) FROM sys.tables'
DECLARE #v_maxDate datetime
EXEC sp_executesql #query, N'#v_maxDate datetime out', #v_maxDate out
PRINT #v_maxDate
Exactly printed the output:
Jan 18 2016 1:10PM
Perhaps try to run the query above and see if that runs on your server... if it does, maybe look at the syntax of your query again.
Thanks a lot for this info. I had the same problem, and this helped me solve it.
Here's my understanding: When using sp_executesql to assign the result of a query to a variable, the variable must be part of the query itself. That is, it doesn't work to (conceptually) run the query in Step 1 ({query} = "SELECT Max(Date) FROM {table}...") and then, in Step 2, to assign the output to a variable (e.g., sp_executesql {query}, #varOut OUTPUT). This is what the OP was doing at first, and it also was my original approach. Instead, the query itself must assign the result to the variable in one step: "SELECT #varOut = Max(Date) FROM {table}..."
It seems that the first approach runs the query in the "normal" way (i.e., as if it were manually typed at the command line) and therefore sends the results to STDOUT as normal. This makes Max(Date) visible on screen, as the OP could see. But the output doesn't get assigned to the variable. Instead, sp_exectuesql must have some kind of success/failure indication that it returns by default. This is what the OP saw when printing the value of #varOut.
Thanks again for the help.

How do I reference a field in a dynamic sql query?

I have a dynamic sql query in SQL Server. So I build it, set it to a varible and then I attempt to format a where clause based on a date. However, when I attempt to do this, I get "The multi-part identifier 'FIELD NAME" can not be bound. I beleive this is because the actual tables are in a dynamic from claus so they can't be seen until it is compiled. Any way around this?
Here I am attempting to say, give me all Persons where DOB between YEAR+MONTH specified, for example, 201001 and 201012 would be the entire year of 2010. Here is the code in part....
ALTER PROCEDURE get_persons_by_search_criteria
#month_from as nvarchar(2) = null,
#year_from as nvarchar(4) = null,
#month_to as nvarchar(2) = null,
#year_to as nvarchar(4) = null
AS
declare #from_date varchar(10)
declare #to_date varchar(10)
declare #sqlstr varchar(5000)
set #sqlstr = ' SELECT
Person.PersonID,
Person.FirstName,
Person.LastName,
FROM Person '
--Attemtping to create a value like 201108 (year + month)
set #from_date = Convert(VarChar(10), #year_from) + Replace(Str(#month_from, 2), ' ', '0')
set #to_date = Convert(VarChar(10), #year_to) + Replace(Str(#month_to, 2), ' ', '0')
set #sqlstr = #sqlstr + ' WHERE '
set #sqlstr = #sqlstr + Convert(VarChar(10), Person.DOBYear) + Replace(Str(Person.DOBMonth, 2), ' ', '0')
set #sqlstr = #sqlstr + ' BETWEEN ' + #from_date + ' and ' + #to_date
exec(#sqlstr)
This line gives the error, because the PERSON table is not open when you build the dynamic string.
set #sqlstr = #sqlstr + Convert(VarChar(10), Person.DOBYear) + Replace(Str(Person.DOBMonth, 2), ' ', '0')
Try this
set #sqlstr = #sqlstr + ' Convert(VarChar(10), Person.DOBYear) + Replace(Str(Person.DOBMonth, 2), '' '', ''0'') '
Should do the trick for you..
I realize you've already fixed your issue and accepted an answer, but I thought I would also point out a few other potential improvements (both for you and for any future readers of the question).
ALTER PROCEDURE dbo.get_persons_by_search_criteria
#month_from VARCHAR(2) = NULL,
#year_from VARCHAR(4) = NULL,
#month_to VARCHAR(2) = NULL,
#year_to VARCHAR(4) = NULL
AS
BEGIN
SET NOCOUNT ON;
SELECT
PersonID, DOBYear, DOBMonth
FROM
dbo.Person
WHERE
DOBYear + RIGHT('0' + DOBMonth, 2) + '01'
BETWEEN #year_from + RIGHT('0' + #month_from, 2) + '01'
AND #year_to + RIGHT('0' + #month_to, 2) + '01'
ORDER BY
PersonID, DOBYear, DOBMonth;
END
GO
Isn't that easier on the eyes, easier to follow, and easier to maintain?
Summary:
always use the schema prefix when creating, altering or referencing objects.
don't use Unicode (NCHAR/NVARCHAR) when you don't need to support Unicode data (numbers will never need to contain umlauts, for example). Choosing the right data type might not be that important in this specific case, but it can be crucial in others.
wrap your procedure body in BEGIN/END - this will prevent you from unknowingly picking up other unwanted code from the query window. And always use SET NOCOUNT ON at the beginning of your procedures. I address these and other issues in my "stored procedure best practices checklist."
to avoid changes in behavior, you should always include an ORDER BY clause. If today it orders by first name, and tomorrow it starts ordering by last name, someone is going to complain. See the second section of this post.
learn to write SQL without dynamic SQL, when possible. If you're going to continue using dynamic SQL, at least please try to use sp_executesql instead of EXEC(). I explained the reasons in another recent question: SQL Server use EXEC/sp_executesql or just plain sql in stored procedure?
Even better would be to just store their date of birth as a DATE in the first place. Why would you store the year and month as separate strings? There must be some reason you are doing this but I can't imagine what it is. All it does is make this kind of string matching less efficient than if you were actually using dates, reduces your ability to perform any type of date operations on the values, and makes it very difficult to validate the values passed in. Right now your stuff is going to choke later than it should have to if someone calls the following:
EXEC get_persons_by_search_criteria
#month_from = '97',
#year_from = 'Audi',
#month_to = 'TT',
#year_to = 'Oy!!';
Which they could do, because you perform no validation whatsoever. With DATE variables at least the error message that comes back would make sense. Right now with either of our versions they'll just get an empty result set.