Is there a way to parameterize the arithmetic operators (<, >, =, >=, <=) in T-SQL?
Something like this:
DECLARE #Operator
SET #Operator = '>='
SELECT *
FROM Table
WHERE Date #Operator '7/1/2017'
Also, I am testing to add additional parameter using functions EXEC('SELECT SiteLongName, * FROM Reporting.Survey_Details WHERE CallDate ' + #Operator + '''7/1/2017''' + 'and SiteLongName in (select value from dbo.FnSplit(''' + #Site + ''''+'',''+'','')) , but it is erroring out.
You can if you use dynamic SQL.
Example :
DECLARE #Operator VARCHAR(2)
SET #Operator = '>='
EXEC('SELECT * FROM TABLE WHERE Date ' + #Operator + ' ''7/1/2017''')
As you can see in the example, handling quotes in dynamic SQL can be a pain. Though it's no big deal in your example.
Be aware that without proper care, dynamic SQL open a vulnerability in your system where user could use SQL Injection attacks against your program.
Related
I have a dynamic stored procedure in SQL Server that works well to pivot a table:
CREATE PROCEDURE dbo.DynamicPivotTableInSql
#ColumnToPivot NVARCHAR(255),
#ListToPivot NVARCHAR(255),
#SurveyID INT=10
AS
BEGIN
DECLARE #SqlStatement NVARCHAR(MAX)
SET #SqlStatement = N'SELECT *
FROM
(SELECT
[resp_id], [benefit], [weight]
FROM Segment_Responses) myResults
PIVOT
(SUM([weight])
FOR [' + #ColumnToPivot + ']
IN (' + #ListToPivot + ')) AS PivotTable';
EXEC (#SqlStatement)
END
and I call it like this
EXEC DynamicPivotTableInSql
#ColumnToPivot = 'benefit',
#ListToPivot = '[OBSERVABILITY], [COST], [EASE OF USE], [SERVICE]'
Here is where I run into problems. You'll notice I have hardcoded #SurveyID = 10 and if I try to add that as a where statement in the stored procedure like this:
FROM Segment_Responses
WHERE survey_id = ' + #SurveyID + '
and run the stored procedure again I get this error:
Conversion failed when converting the nvarchar value '
SELECT * FROM (
SELECT
[resp_id],
[benefit],
[weight]
FROM Segment_Responses where survey_id=' to data type int.
I've tried to solve this many ways (e.g., passed the Int variable instead of hard coding it) but always get the same result. Any ideas what is going on?
Just to try to add some clarity, when you add together two different types, SQL Server will (where it can) implicitly convert one to the other - the result must be a single type after all.
It decides which one to convert "to the other" based on an order of precedence.
So where you are trying to concatenate a varchar with an int, the int has the higher order of precedence. This is also a common cause of errors and bugs when using a case expression when mixing types in different execution paths of the expression.
You need to be explicit and cast the int to a varchar.
Ideally you would use a parameterised query which would also reuse the cached execution plan - this may be beneficial if the cardinality of the data is similar but sometimes making the value part of the query dynamically can be advantagous, it depends on the use-case.
This is why the syntax EXEC (#SQL) is strongly suggested against. Use sys.sp_executesql and parametrise your statement:
SET #SQL = N'SELECT ...
FROM ...
WHERE survey_id = #SurveyID ...;';
EXEC sys.sp_executesql #SQL, N'#SurveyID int',SurveyID;
The + only works with strings. If you use a number TSQL assumes you are trying to use the addition operator, and tries to convert the string argument to int.
eg this
select 1 + '2'
works and returns 3.
Use CONCAT instead of +, or use an explicit conversion on the int.
eg
WHERE survey_id = ' + cast(#SurveyID as varchar(20)) + '
I have this Openrowset Query where I'm able to pass a Path to get the correct Excel file. I can't figure out the exact number of quotes and/or the type conversion
I need to pass a WHERE statement to get a specific date in the table. I'm coming from VBA where you put 2 quotes and 2 & and you're all set. It seems SQL needs hundreds of them! (If someone has a link to an online university about SQL quotes that'd be much appreciated too)
set #sql1 = 'select *
FROM OPENROWSET(''Microsoft.ACE.OLEDB.12.0'',
''Excel 12.0;Database=' + #filePath1 + '' + ';' + '''' + ',
''Select * from [Sheet1$] '''+')'
exec(#sql1)
I'd like to add WHERE DATE = #PnLDate
The rule is very similar to what you know but it applies to single quote '. When you want one of them inside a string you escape it using two ''. Apart of them, you must have one for openning and one for closing.
The string you show, is a bit complicated just because it contains innecesary empty strings, and secuentialy contatenated stirngs that can be replaced just for only one resultant string. Below a simiplificated version of your string:
set #sql1 = 'select *
FROM OPENROWSET(''Microsoft.ACE.OLEDB.12.0'',
''Excel 12.0;Database=' + #filePath1 + ';'',
''Select * from [Sheet1$] '')'
With your requested where may be:
DECLARE #PnLDate datetime -- may be varchar instead, if so, must remove convert(....) and use straight #PnLDate
set #PnLDate = '2014/08/01'
set #sql1 = 'select *
FROM OPENROWSET(''Microsoft.ACE.OLEDB.12.0'',
''Excel 12.0;Database=' + #filePath1 + ';'',
''Select * from [Sheet1$] where DATE = #'+ -- hash # is MS Access delimiter for dates
convert(varchar(25), #PnLDate, 111)
+'#'')' -- results in #2014/08/01#
Be warned that when you use "dynamic SQL" your data can inject malicious code on the string. read about sql Injection
Please look at the below query..
select name as [Employee Name] from table name.
I want to generate [Employee Name] dynamically based on other column value.
Here is the sample table
s_dt dt01 dt02 dt03
2015-10-26
I want dt01 value to display as column name 26 and dt02 column value will be 26+1=27
I'm not sure if I understood you correctly. If I'am going into the wrong direction, please add comments to your question to make it more precise.
If you really want to create columns per sql you could try a variation of this script:
DECLARE #name NVARCHAR(MAX) = 'somename'
DECLARE #sql NVARCHAR(MAX) = 'ALTER TABLE aps.tbl_Fabrikkalender ADD '+#name+' nvarchar(10) NULL'
EXEC sys.sp_executesql #sql;
To retrieve the column name from another query insert the following between the above declares and fill the placeholders as needed:
SELECT #name = <some colum> FROM <some table> WHERE <some condition>
You would need to dynamically build the SQL as a string then execute it. Something like this...
DECLARE #s_dt INT
DECLARE #query NVARCHAR(MAX)
SET #s_dt = (SELECT DATEPART(dd, s_dt) FROM TableName WHERE 1 = 1)
SET #query = 'SELECT s_dt'
+ ', NULL as dt' + RIGHT('0' + CAST(#s_dt as VARCHAR), 2)
+ ', NULL as dt' + RIGHT('0' + CAST((#s_dt + 1) as VARCHAR), 2)
+ ', NULL as dt' + RIGHT('0' + CAST((#s_dt + 2) as VARCHAR), 2)
+ ', NULL as dt' + RIGHT('0' + CAST((#s_dt + 3) as VARCHAR), 2)
+ ' FROM TableName WHERE 1 = 1)
EXECUTE(#query)
You will need to replace WHERE 1 = 1 in two places above to select your data, also change TableName to the name of your table and it currently puts NULL as the dynamic column data, you probably want something else there.
To explain what it is doing:
SET #s_dt is selecting the date value from your table and returning only the day part as an INT.
SET #query is dynamically building your SELECT statement based on the day part (#s_dt).
Each line is taking #s_dt, adding 0, 1, 2, 3 etc, casting as VARCHAR, adding '0' to the left (so that it is at least 2 chars in length) then taking the right two chars (the '0' and RIGHT operation just ensure anything under 10 have a leading '0').
It is possible to do this using dynamic SQL, however I would also consider looking at the pivot operators to see if they can achieve what you are after a lot more efficiently.
https://technet.microsoft.com/en-us/library/ms177410(v=sql.105).aspx
SET #TempTable = 'UPDATE #TempTable SET AvgVal = ' + #AvgVal + '
WHERE PropertyName =''' + #PropertyName + '''' + ' AND CrudeName =''' + #crudeName + ''''
I have an update query which is dynamic. AvgVal is float. The variable has the same datatype in the table as well. Still I get an error saying -
Error converting data type varchar to numeric.
If i convert the value to varchar it works. Whats the workaround for this.
What you call the "workaround" is the answer. SQL Server uses the + operator as both addition and string concatenation. However, because the addition operator has a slightly higher precedence than the concatenation operator, the addition operator is assumed to apply first.
See the SQL Server BOL on Operator Precedence. At the 3rd level of precedence, the order goes:
+ (Positive)
- (Negative)
+ (Add)
+ (Concatenate)
- (Subtract)
& (Bitwise AND),
^ (Bitwise Exclusive OR)
| (Bitwise OR)
To see how this affects queries take the following example:
select '1' + 2
The result of this query is not an error. SQL Server interprets the + as addition. It sees the number 2 is an integer, so it tries to convert the string '1' into an integer and is successful. The result is the integer 3.
However, try this:
select '1.0' + 2
SQL Server again sees this as an addition, notes that 2 is an integer, and tries to convert the string '1.0' into an integer. This conversion fails with the following error:
Msg 245, Level 16, State 1, Line 1 Conversion failed when converting
the varchar value '1.0' to data type int.
In your example, SQL Server detects the float #AvgVal and attempts to convert the string 'UPDATE #TempTable SET AvgVal = ' into a float. The conversion fails spectacularly, and the error you reported occurs.
One possibility is to convert the #AvgVal into a string first:
SET #TempTable = 'UPDATE #TempTable
SET AvgVal = ' + CAST(#AvgVal AS VARCHAR) + '
WHERE PropertyName =''' + #PropertyName + '''' + '
AND CrudeName =''' + #crudeName + '''
Note: This query is vulnerable to a SQL injection attack! To avoid a SQL injection attack, always pass data to a dynamic query as parameters:
SET #TempTable = N'UPDATE #TempTable SET AvgVal = #AvgVal
WHERE PropertyName = #PropertyName AND CrudeName = #crudeName'
exec sp_executesql #TempTable,
N'#AvgVal float, #PropertyName varchar(255), #crudeName varchar(255)',
#PropertyName = #PropertyName,
#CrudeName = #crudeName
This both avoids a SQL injection attack and avoids the conversion error.
I see. Sorry, misunderstood.
Yes, because of the way SQL Server works. You are building a concatenated string. You will have to do the CAST. Type conversions in SQL Server are a little wonky. You would think it would inherently cast to the simplest data type, varchar, but in this instance it isn't. So you will have to do ... + CAST(#AvgVal AS VARCHAR(255)) + ...
Keep in mind some times variables like #PropertyName if is Nvarchar type you should do
in front of the string N'
DECLARE #TempTable NVARCHAR
SET #TempTable = N'UPDATE and so on for the rest
CREATE PROCEDURE [dbo].[sp_SelectRecipientsList4Test] --'6DBF9A01-C88F-414D-8DD9-696749258CEF','Emirates.Description','0','5'
--'6DBF9A01-C88F-414D-8DD9-696749258CEF',
--'121f8b91-a441-4fbf-8a4f-563f53fcc103'
(
#p_CreatedBy UNIQUEIDENTIFIER,
#p_SortExpression NVARCHAR(100),
#p_StartIndex INT,
#p_MaxRows INT
)
AS
SET NOCOUNT ON;
IF LEN(#p_SortExpression) = 0
SET #p_SortExpression = 'Users.Name Asc'
DECLARE #sql NVARCHAR(4000)
SET #sql='
DECLARE #p_CreatedBy UNIQUEIDENTIFIER
SELECT
Name,
POBox,
EmirateName,
TelephoneNo,
RecipientID,
CreatedBy,
CreatedDate,
ID
FROM
(
SELECT Users.Name, Users.POBox, Emirates.Description As EmirateName,
UserDetails.TelephoneNo, AddressBook.RecipientID,AddressBook.CreatedBy, AddressBook.CreatedDate,
AddressBook.ID,
ROW_NUMBER() OVER(ORDER BY '+ #p_SortExpression +') AS Indexing
FROM AddressBook INNER JOIN
Users ON AddressBook.RecipientID = Users.ID INNER JOIN
UserDetails ON Users.ID = UserDetails.UserID INNER JOIN
Emirates ON Users.EmiratesID = Emirates.ID
----WHERE (AddressBook.CreatedBy = #p_CreatedBy)
) AS NewDataTable
WHERE Indexing > '+ CONVERT(NVARCHAR(10), #p_StartIndex) +
' AND Indexing<=(' + CONVERT (NVARCHAR(10),#p_StartIndex ) + ' + '
+ CONVERT(NVARCHAR(10),#p_MaxRows)+') '
EXEC sp_executesql #sql
This query is not giving any error but also not giving any result
please help
Have you tried breaking down the statement, to check if intermediate results are as expected? That's what you do to debug a complex statement...
For example, there's a nested SELECT in there. If you commit that SELECT on its own, does it print the expected values?
Edit: There's a saying about teaching a man to fish. 'ck' and 'n8wrl' have given you fish to eat today, now please practice fishing to feed you tomorrow...
Well, a quick glance of this:
WHERE Indexing > '+ CONVERT(NVARCHAR(10), #p_StartIndex) + ' AND Indexing<=(' + CONVERT (NVARCHAR(10),#p_StartIndex ) +...
looks like you're looking for an impossible condition, not unlike this:
WHERE Indexing > 5 AND Indexing <= 5
So that might be why you're getting no rows, but this proc is ripe for SQL injection attacks too. Building SQL on the fly based on possibly-unvalidated parameters is very dangerous.
You are querying:
'WHERE Indexing > '+ CONVERT(NVARCHAR(10), #p_StartIndex) +
' AND Indexing<=(' + CONVERT (NVARCHAR(10),#p_StartIndex ) + ' + '
and then adding max rows as a string, you can do this much more easily like so:
'WHERE Indexing > '+ CONVERT(NVARCHAR(10), #p_StartIndex) +
' AND Indexing <='+ CONVERT(NVARCHAR(10),#p_StartIndex + #p_MaxRows)
EDIT
The problem with your inner WHERE is that you are passing in the parameter, you need to do
'WHERE (AddressBook.CreatedBy = ''' + CAST(#p_CreatedBy AS CHAR(36)) + ''')'
Are you sure all your joins should be inner joins?
Change sp_executesql to PRINT and see what gets generated (the poor man's debugger)
Besides what all the other people told you,
give me one good reason why you are using sp_executesql over exec? You are not using parameterized statements, you also are not protected from sql injections because you just execute the whole string
This will just bloat the procedure cache everytime this is run and some values change, you will get a new plan every time
Please take a look at Changing exec to sp_executesql doesn't provide any benefit if you are not using parameters correctly and Avoid Conversions In Execution Plans By Using sp_executesql Instead of Exec