Use Open Query with quoted items - sql

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.

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;

Issues with equal sign when assigning statement to # variable for EXEC sp_executesql

Hello: I am trying to encapsulate the following Insert and select statement into a variable for SQL to run.
The issue occurs when the equals sign is included for evaluation (or right around there)
The CustomPollerAssignmentID Column had to be cast since it natively a PK and a uniqueidentifier (see image)
What must be done to have the statement evaluate properly when passes to the sp_executesql?
I receive a syntax error as illustrated below
declare
#date datetime,
#query nvarchar(Max)
set #date = getdate()-4
set #query = --'Insert into [SolarWindsOrion].[dbo].[BWC_VPN_Hourly_Long_Store]
'SELECT * FROM [SolarWindsOrion].[dbo].[CustomPollerStatistics_Hourly]
where Cast(CustomPollerAssignmentID as nvarchar(max)) = 63FEB60-4516-4C1A-9A11-DB30ACA44301'
EXEC sp_executesql #query
UPDATE-->
I tried adding the single quotes.
While the statement does execute.. no results are returned. See Image below
you need to put it in quote like this '63FEB60-4516-4C1A-9A11-DB30ACA44301' , so your adhoc query would look like this :
declare
#date datetime,
#query nvarchar(Max)
set #date = getdate()-4
set #query = --'Insert into [SolarWindsOrion].[dbo].[BWC_VPN_Hourly_Long_Store]
'SELECT * FROM [SolarWindsOrion].[dbo].[CustomPollerStatistics_Hourly]
where Cast(CustomPollerAssignmentID as nvarchar(max)) = ''63FEB60-4516-4C1A-9A11-DB30ACA44301'''
EXEC sp_executesql #query

How can I create a dynamic Select statement within a SQL Select statement?

I have a SELECT statement that can produce a list of values:
DECLARE #ValueList varchar(Max);
SELECT #ValueList = COALESCE(#ValueList + ',', '') + CAST(Val AS varchar(max))
FROM
(SELECT TOP (100) PERCENT tblSampleTable.SomeIDNumber AS Val
FROM tblSampleTable) AS ValuesThisYear
PRINT #ValList
This returns a list with values something like
val1,val2,val4,val9,
etc., ehich I can then feed into a stored procedure, or manage some other way.
Now I want to have the query that gets assessed for the list of values to be dynamic, maybe passed in or from another stored procedure, similar to this:
DECLARE #ValueList varchar(Max);
DECLARE #TSQL varchar(Max);
SET #TSQL = {stored proc to get base query}
SELECT #ValueList = COALESCE(#ValueList + ',', '') + CAST(Val AS varchar(max))
FROM
(#TSQL) AS ValuesThisYear
PRINT #ValList
I know that's the wrong syntax for including #TSQL, and that's what I'm trying to find out. I've viewed a number of threads and tried a number of methods, but am still not able to incorporate this dynamic part.
The tricky part seems to be the making of the list (the COALESCE and CAST statements), where I incorporate #ValList as part of the returned string.
Any help would be appreciated!
Dynamic SQL is usually about
Creating a variable that contains the exact SQL you want to run
Then using EXEC (#SQLvariable) to run that code
For example (not for production yet!) I've added a new variable #CustomSQL
DECLARE #ValueList varchar(Max);
DECLARE #TSQL varchar(Max);
DECLARE #CustomSQL varchar(Max);
SET #TSQL = {stored proc to get base query}
SET #CustomSQL =
'SELECT COALESCE(#ValueList + '','', '''') + CAST(Val AS varchar(max))
FROM (
' + #TSQL + '
) As ValuesThisYear;'
PRINT #CustomSQL
EXEC (#CustomSQL)
Notice that adding text/strings (e.g., the #TSQL variable) have to be entered as exact strings rather than their variable names. Also note apostrophes - you need to use '' every time you wish to refer to a '.
I also removed the variable name from the SELECT #ValueList = ... because the dynamic SQL cannot actually reference the variables - it has its own scope (?cannot remember the correct word) and doesn't have access to the variables. Solutions to this include
Using a temporary table e.g., #temp which can be referenced
Using the OUTPUT clause
Personally, I would approach it a different way - use the T-Sql provided to put data into a temporary table. Then use the temporary table in the other statement e.g.,
DECLARE #ValueList varchar(Max);
DECLARE #TSQL varchar(Max);
SET #TSQL = {stored proc to get base query}
DECLARE #CustomSQL varchar(Max)
CREATE TABLE #temp (Val varchar(1000))
SET #CustomSQL = 'INSERT INTO #temp (Val) ' + #TSQL
EXEC (#CustomSQL)
SELECT #ValueList = COALESCE(#ValueList + ',', '') + CAST(Val AS varchar(max))
FROM #temp As ValuesThisYear;
PRINT #ValList
I almost never get my dynamic SQL correct first try. Suggestions
Keep it as simple as possible
Before having a version that runs (e.g., EXEC (#CustomSQL)), comment the EXEC out and PRINT it instead.
Here are some examples from previous posts I've done recently
Query for R Machine Learning Services - Filtering Categories in Where Clause
Bottom of Dynamic columns depend on previous dynamic columns - TSQL

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.

Difficulty using a CHAR in Dynamic SQL

I'm trying to pass in #accountType, a char value to a stored procedure that uses dynamic SQL. It is declared as char(4) in the procedure. The current error is Incorrect syntax near 'D' if I try to change it I get invalid column: D.
I cannot figure out how dynamic SQL wants me to indicate that the variable is a char. I've tried it many ways, here is the most recent:
set #q = 'Update ' + #statementTable +
' SET Account = '+ #padding + #accountNumber +
' WHERE ClosingDate BETWEEN CAST('''+CONVERT(VARCHAR(20),#proc_dateStart)+''' AS DATE) AND CAST('''+CONVERT(VARCHAR(20),#proc_dateEnd)+''' AS DATE)' +
' AND AccountType =' + ''''+ #accountType +''''
The value is coming from my C# code exactly like this: D
No single quotes or anything around the letter. Any ideas? I'm more than a bit stuck with this.
Something like this, you need to have the parameters actually within the string statement, then when you execute sp_executesql, you then pass what each of those parameters are.
DECLARE #q VARCHAR(MAX)
DECLARE #statementTable VARCHAR(50)
DECLARE #padding VARCHAR(50)
DECLARE #accountNumber CHAR(4)
DECLARE #proc_dateStart VARCHAR(50)
DECLARE #proc_dateEnd VARCHAR(50)
DECLARE #accountType VARCHAR(50)
SET #q = 'Update #statementTable
SET Account = ''#accountNumber''
WHERE ClosingDate BETWEEN CAST(''+CONVERT(VARCHAR(20),#proc_dateStart)+'' AS DATE) AND CAST(''+CONVERT(VARCHAR(20),#proc_dateEnd)+'' AS DATE)
AND AccountType = ''#accountType'''
EXEC sys.sp_executesql #sql, N'#statementTable VARCHAR(50),#accountNumber CHAR(4),#proc_dateStart VARCHAR(50), #proc_dateEnd VARCHAR(50),#accountType VARCHAR(50)',
#statementTable,#accountNumber,#proc_dateStart,#proc_dateEnd,#accountType;