I try to select some special records contain special characters but SQL Server changes my string characters before it running the query.
For example:
DECLARE #param NVARCHAR(30)
SET #param=N'¤÷þ'--this is my special string that i want to be searched exactly.
DECLARE #TSQL varchar(8000)
SET #TSQL = 'SELECT * FROM MyTable WHERE MyFieldName LIKE %' + #param + '% '
PRINT #TSQL
--EXECUTE (#TSQL)
But in the result(print) I see:
SELECT * FROM MyTable WHERE MyFieldName LIKE '%¤÷þ?%'
As you see some part of string converted to (?) character, this problem cause my SELECT command return null value.
I try to change collation of the database that I run the query to:
SQL_Latin1_General_CP1_CI_AS
It work fine with some special string but it also does not support all of my strings.
So, question is here: how can I tell SQL Server, please don't change my string ascii codes?
Is there any way (or any collation) to say SQL Server that see an string exactly as it is in reality?
PS: I am using SQL Server 2008 R2.
If you have special characters that need to be preserved, use Unicode strings of type NVARCHAR instead of VARCHAR - it's that simple .....
DECLARE #param NVARCHAR(30)
SET #param = N'¤÷þ'--this is my special string that i want to be searched exactly.
DECLARE #TSQL NVARCHAR(4000) -- <=== use NVARCHAR here
SET #TSQL = N'SELECT * FROM MyTable WHERE MyFieldName LIKE %' + #param + N'% '
PRINT #TSQL
Then your special characters will be preserved as entered ....
And as others have pointed out: concatenating together your SQL statements like this is never a good idea - it opens up your code to potential SQL injection attacks. You should use parameterized queries and sp_executesql which allows you to define and supply paramters to your queries.
DECLARE #TSQL varchar(8000)
varchar(8000) cannot represent ¤÷þ. Just keep doing what you're doing with #param; use something NVARCHAR based.
As usr correctly points out, you should really be using sp_executesql and its ability to specify parameters. From the documentation:
DECLARE #IntVariable int;
DECLARE #SQLString nvarchar(500);
DECLARE #ParmDefinition nvarchar(500);
/* Build the SQL string one time.*/
SET #SQLString =
N'SELECT BusinessEntityID, NationalIDNumber, JobTitle, LoginID
FROM AdventureWorks2012.HumanResources.Employee
WHERE BusinessEntityID = #BusinessEntityID';
SET #ParmDefinition = N'#BusinessEntityID tinyint';
/* Execute the string with the first parameter value. */
SET #IntVariable = 197;
EXECUTE sp_executesql #SQLString, #ParmDefinition,
#BusinessEntityID = #IntVariable;
Related
I am attempting to create a stored procedure that allows values to be passed to it, however, when I try to use Dynamic T-SQL I raise an error MSG 207
I know that the error message is supposed to indicate that the column name was misspelled in my statement thanks to the following site:
https://www.tsql.info/error/msg-207-level-16-invalid-column-name.php
This, however, does not make sense as the following hard coded statement works just fine.
INSERT INTO [dbo].[tDriversLicense] (DriversLicensePrefix, DriversLicenseSuffix, DateAdded, DriversLicenseNumber)
VALUES ('shockc', '11653798', GETDATE(), 'GAD4859');
ALTER PROCEDURE [dbo].[spAddDriversLicense]
-- Add the parameters for the stored procedure here
#DriversLicensePrefix NCHAR(8),
#DriversLicenseSuffix NCHAR(10)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #sqlInsertDriversLicense NVARCHAR(MAX)
SET #sqlInsertDriversLicense = 'INSERT INTO [dbo].[tDriversLicense] (DriversLicensePrefix, DriversLicenseSuffix, DateAdded, DriversLicenseNumber)
VALUES (' + #DriversLicensePrefix + ', ' + #DriversLicenseSuffix + ', GETDATE(), GPC4859);'
EXECUTE sp_executesql #sqlInsertDriversLicense
END
I've been spilling over this code for hours and I'm thoroughly confused as to why there is any difference between the two INSERT statements. The values being passed are the same but an error is raised when I attempt to pass them dynamically. How is this raising an error??
I've used some code from this video to attempt to learn how pass Scalar Values dynamically and have had luck in other stored procedures.
https://www.youtube.com/watch?v=RHHKG65WEoU
When working with dynamic strings, select them first, so you can check that the resulting syntax is workable.
You need single quotes in the result, so you need more than single, single quotes, in the code:
declare #DriversLicensePrefix nchar(8) = 'A2345678'
declare #DriversLicenseSuffix nchar(10) = 'abcdefghij'
DECLARE #sqlInsertDriversLicense nvarchar(max)
SET #sqlInsertDriversLicense = 'INSERT INTO [dbo].[tDriversLicense] (DriversLicensePrefix, DriversLicenseSuffix, DateAdded, DriversLicenseNumber)
VALUES (N''' + #DriversLicensePrefix + ''', N''' + #DriversLicenseSuffix + ''', ''' + convert(char(8),GETDATE(),112) + ''', ''GPC4859'');'
select #sqlInsertDriversLicense
+-------------------------------------------------------------------------------------------------------------------+
| result: |
+-------------------------------------------------------------------------------------------------------------------+
| INSERT INTO [dbo].[tDriversLicense] (DriversLicensePrefix, DriversLicenseSuffix, DateAdded, DriversLicenseNumber) |
| VALUES (N'A2345678', N'abcdefghij', '20181109', 'GPC4859'); |
+-------------------------------------------------------------------------------------------------------------------+
NB You should use convert(char(8),getdate(),112) SQL Server will recognize the YYYYMMDD format as a date value regardless of server default settings.
The result see above demonstrates what the insert statement MUST be, note that it it contains several single quotes.
When you are concatenating the SQL statement, you are also dealing with strings, and every part of that has to be contained within single quotes.
So there are multiple needs for single quotes.
And; So you need multiple single quotes throughout out the concatenation, some to help form the SQL statement, and others to be INSIDE that statement.
/* get this into #sql */
select 'Y' as col1;
declare #SQL as nvarchar(max)
set #SQL = N'select ''Y'' as col1;'
select #SQL;
+---------------------+
| #SQL |
+---------------------+
| select 'Y' as col1; |
+---------------------+
In the larger query 2 variables are defined a NCHAR(8) or (10) as you have defined them a Nchar then when inserting data into those you should prefix that input by N
As posted in a comment already there is no need for this dynamic approach at all.
The following code will show a straight and a dynamic approach. Try it out:
USE master;
GO
CREATE DATABASE testDB;
GO
USE testDB;
GO
CREATE TABLE TestTable(SomeContent VARCHAR(100),SomeDate DATETIME,SomeFixString VARCHAR(100));
GO
--This procedure will use the parameters directly. No need for any dynamic SQL:
CREATE PROCEDURE TestStraight(#content VARCHAR(100))
AS
BEGIN
INSERT INTO TestTable(SomeContent,SomeDate,SomeFixString)
VALUES(#content,GETDATE(),'Proc Straight');
END
GO
--this procedure will use real parameters. You should never use parameters in string concatenation. One day you might meet bobby tables...
CREATE PROCEDURE TestDynamic(#content VARCHAR(100))
AS
BEGIN
DECLARE #cmd NVARCHAR(MAX)=
N'INSERT INTO TestTable(SomeContent,SomeDate,SomeFixString)
VALUES(#DynamicContent,GETDATE(),''Proc Dynamic'');'
EXEC sp_executesql #cmd,N'#DynamicContent VARCHAR(100)',#DynamicContent=#content;
END
GO
--Test it
EXEC TestStraight 'yeah!';
EXEC TestDynamic 'oh yeah!';
SELECT * FROM TestTable;
GO
--Clean up
USE master;
GO
--careful with real data!
--DROP DATABASE testDB;
sorry I am uploading a pic of my query as I dont know to format my text...as a newbie its confusing.
So you want to execute:
select * from File_20170703 -- where your table name is a variable.
It is not possible to use variables for table or column names, what you need to do is to build a dynamic sql and execute it using sp_executesql.
here is an example:
DECLARE #sql nvarchar(4000)
SELECT #sql = 'SELECT * FROM File_20170703'
EXEC sp_executesql #sql
More info about dynamic sql
A simple TSQL "dynamic sql" looks like this:
DECLARE #file_name AS VARCHAR(100)
DECLARE #query AS VARCHAR(MAX)
SET #file_name = 'file_20170101'
SET #query = 'SELECT * FROM ' + #file_name
execute(#query)
Basically you need to create a valid sql query by concatenating various parts of the query together, then you can execute that whole big string as your query.
You can use SQL Cursor along with while loop. Examples are given here:
https://learn.microsoft.com/en-us/sql/t-sql/language-elements/while-transact-sql
I want to make a EXEC in my sql with Transact-SQL, like:
set #name = 'test'
set #sql = 'insert into TempTable values('+#name+')'
EXEC( #sql)
but i can't insert a varchar in the sql string because i can't put ' into the string so it to knows that #name is a varchar.
first declare the variable first with declare , set and then u can use it.
below the complete code :
DECLARE #name VARCHAR (500)
SET #name = 'test';
set #sql = 'insert into TempTable values('''+#name+''')'
EXEC( #sql)
use (''') three quote to add your varchar.
A simple insert statement like this does not need to executed using Dynamic SQL. you can easily achieve the same using the query below.
DECLARE #name VARCHAR (500)
SET #name = 'test';
insert into TempTable values(#name)
You can refer to the link below for more details on issues related to dynamic sql.
http://www.sommarskog.se/dynamic_sql.html
Please forget using EXEC when you are working with dynamic queries. In SQL Server you should use sp_executesql
DECLARE #name VARCHAR(500);
SET #name = 'test';
DECLARE #query = NVARCHAR(MAX) = 'INSERT INTO TempTable VALUES (#name)';
EXEC sp_executesql
#stmt = #query,
#params = N'#name VARCHAR(500)',
#name = #name
With this
You can avoid unnecessary type conversions (INT -> VARCHAR, etc)
Native data types could be passed
A bit more secure (will not solve all the problems, but a bit bette, than injecting variable values into the query string)
I have a SQL function that takes a variable called attribute, which is the xml attribute I want to get the value from. xmlPath is the full XML string.
My xml looks like this:
<EventSpecificData>
<Keyword>
<Word>myWord</Word>
<Occurences>1</Occurences>
<Context>context</Context>
</Keyword>
</EventSpecificData>
I want to get the value for <Word>, so I pass in /Keyword/Word and set a variable to:
set #value = #xmlPath.value('(/EventSpecificData/#attribute)[1]', 'varchar(max)')
However, I don't think #attribute is actually inserting the variables string. Is there another way to do this?
Here are a couple of solutions for you.
Sample data:
declare #xml xml
set #xml =
'<EventSpecificData>
<Keyword>
<Word>myWord</Word>
<Occurences>1</Occurences>
<Context>context</Context>
</Keyword>
</EventSpecificData>'
Get the first value from node named Word regardless of parents. Use // to do a deep search and use local-name() to match node name.
declare #Attribute varchar(max)
set #Attribute = 'Word'
select #xml.value('(//*[local-name() = sql:variable("#Attribute")])[1]', 'varchar(max)')
Provide parent node name and attribute in separate variables using local-name() in two levels.
declare #Node varchar(max)
declare #Attribute varchar(max)
set #Attribute = 'Word'
set #Node = 'Keyword'
select #xml.value('(/EventSpecificData
/*[local-name() = sql:variable("#Node")]
/*[local-name() = sql:variable("#Attribute")])[1]', 'varchar(max)')
Since the parameter to nodes have to be a string literal it invites to use dynamic sql to solve this. It could look something like this to make it work with your original variable content.
set #Attribute = 'Keyword/Word'
declare #SQL nvarchar(max)
set #SQL = 'select #xml.value(''(/EventSpecificData/'+#Attribute+')[1]'', ''varchar(max)'')'
exec sp_executesql #SQL, N'#xml xml', #xml
But you should be aware of that if you use this you are wide open to SQL Injection attacks. Some devious end-user might come up with a attribute string that looks like this:
set #Attribute = 'Keyword/Word)[1]'', ''varchar(max)'') select ##version --'
Executing the dynamic SQL with that will give you two result sets. The select ##version is just there to show some harmless code but it might be much worse stuff in there.
You can use quotename() to prevent the SQL injection attack. It will at least prevent the attempt made by me.
set #Attribute = 'Keyword/Word'
set #SQL = 'select #xml.value('+quotename('(/EventSpecificData/'+#Attribute+')[1]', '''')+', ''varchar(max)'')'
exec sp_executesql #SQL, N'#xml xml', #xml
Is the last version using quotename()safe? Have a look at this article by Erland Sommarskog The Curse and Blessings of Dynamic SQL.
Quote:
So with quotename() and quotestring(), do we have as good protection
against SQL injection as we have with parameterised commands? Maybe. I
don't know of any way to inject SQL that slips through quotename() or
quotestring(). Nevertheless, you are interpolating user input into the
SQL string, whereas with parameterised commands, you don't.
Try concatenating the string.
set #value = #xmlPath.value('(/EventSpecificData/' + #attribute + ')[1]', 'varchar(max)')
Updated answer:
Let's try CASE'ing the operation.
SELECT #value = CASE #attribute
WHEN 'word' THEN [word]
WHEN 'occurrence' THEN [occurrence]
WHEN 'context' THEN [context]
END AS [value]
FROM
(
SELECT x.u.value('(/EventSpecificData/Keyword/Word)[1]', 'varchar(max)') AS [word]
, x.u.value('(/EventSpecificData/Keyword/Occurrence)[1]', 'varchar(max)') AS [word]
, x.u.value('(/EventSpecificData/Keyword/Context)[1]', 'varchar(max)') AS [word]
FROM #xmlPath.nodes('/EventSpecificData') x(u)
) a
Due to the constraints within the workplace I have to use a local stored procedure to call another remote stored proc on a linked sql server, however the problem lies in passing a necessary parameter to the remote stored proc.
This is the query I constructed:
select *
from OPENQUERY([REMOTE_SRVR],'exec db.dbo.dwStoredProc_sp ''#id''')
In order to pass #id to the remote stored proc I understand I could concatenate the above as a string and then use exec
Something along the lines of:
set #query = 'select * from OPENQUERY([REMOTE_SRVR], ''EXEC db.dbo.dwStoredProc_sp '' #id '''''
exec(#query)
I cannot get the local stored proc to successfully call the other. The single quote mess doesn't help!
I get the error: Could not find stored procedure 's'
To help with the quote mess I like to do this in steps. It is more code but easier to understand. I am not sure from your example if #id is an integer. In that case you can lose the double quotes around __ID__.
set #query = 'EXEC db.dbo.dwStoredProc_sp ''__ID__'''
set #query = REPLACE(#query,'__ID__',#id)
set #query = REPLACE(#query,'''','''''')
set #query = REPLACE('SELECT * FROM OPENQUERY([REMOTE_SRVR], ''__REMOTEQUERY__'')','__REMOTEQUERY__',#query)
You could avoid dynamic queries by simply by using EXEC (..., ParamValue) AT LinkedServer (see product's documentation, example [L. Using a parameter with EXECUTE and AT linked_server_name]):
1) On target server:
CREATE PROCEDURE dbo.Proc1( #id NVARCHAR(50) )
AS
SELECT #id AS [id];
GO
2) On the source server you create the linked server and then you can call the stored procedure using EXEC ... AT ... syntax:
DECLARE #p1 NVARCHAR(50);
SET #p1 = N'DROP TABLE dbo.CocoJambo'
EXECUTE (N'dbo.Proc1 ? ' , #p1 ) AT LOCALINKEDSEREV
Output:
id
------------------------
DROP TABLE dbo.CocoJambo