Think I am missing something really obvious but I want to use a parameter in a where statement but when I do I get the following error (This is being run as a stored procedure if that makes any difference)
Invalid column name 'John'
The where statement in question
USE [Reports]
GO
/****** Object: StoredProcedure [Reports].[Alarm_TestSignOffFull_Qry] Script Date: 25/10/2018 08:56:26 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [Reports].[CustomerSearch]
-- Add the parameters for the stored procedure here
#Database VARCHAR(20)
,#Schema VARCHAR(20)
,#Name VARCHAR(20)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
EXEC ('
USE ' + #Database + '
SELECT Customer.Name,
[Product Categories].[Product Number]
RIGHT JOIN ((' + #Schema +'.Customer INNER JOIN ' + #Schema +'.Client ON Customer.Name = Client.Name)
LEFT JOIN ' + #Schema +'.Product ON Client.Name = Product.Client)
ON [Product Categories].[Product Number] = Client.Product
WHERE (Customer.Name = ' + #Name + ' AND Customer.Order = ''Y'') OR (Client.Name = ' + #Name + ' AND Customer.Order = ''Y'');
' )
END
GO
The parameter #Name is declared as VARCHAR(20)
Dont think too much about it had to find and replace table names and column names and stuff to make sure I am allowed to post it.
The select on the real thing also brings through a lot more fields but not too worried about that for this question
To emphasize it is the where statements that are going wrong the rest of the query works and it all works when I do not use any parameters
I'm posting this as answer, as the comments aren't enough to explain, however, there is far too little detail here to actually answer the OP's question.
Firstly, the error the OP is getting would be impossible to get with the tiny amount of SQL they have provided. If we expand it into a full Pseudo-SQL query, we get:
SELECT {Columns}
FROM Customer
WHERE Customer.Name = (' + #Name + ');
This would return rows in the Customer table, where Name has the literal string value ' + #Name + '. Very unlikely.
I suspect that the OP simply needs to do:
SELECT {Columns}
FROM Customer
WHERE Customer.Name = #Name;
HOWEVER, the error the OP is getting strong implies they are using Dynamic SQL. This means they have a query like this:
DECLARE #SQL nvarchar(MAX);
DECLARE #Name varchar(20) = 'John';
SET #SQL = N'SELECT {Columns} FROM Customer WHERE Customer.Name = (' + #Name + ');';
EXEC (#SQL);
The problem here is that translates into the SQL:
SELECT {Columns} FROM Customer WHERE Customer.Name = (John);
Notice, no quotes around John, and why the error.
What you are doing here, however, is a very bad idea. Raw string concatenation like that leaves your SQL wide open to injection(SQL injection). Parametrise your SQL:
DECLARE #SQL nvarchar(MAX);
DECLARE #Name varchar(20) = 'John';
SET #SQL = N'SELECT {Columns} FROM Customer WHERE Customer.Name = #dName;'
EXEC sp_executesql #SQL, N'#dName varchar(20)',#dName = #Name;`
Edit: Ok, the OP has provided us with their query. Honestly, it's a mess, far worse that I wanted it to be. Like i said before, raw string concatenation is an awful idea, it leaves you open to injection. You can't get rid of concatenation for dynamic objects, but you can use QUOTENAME, which makes the code safe(r).
You're also missing a FROM; no idea what that needs to be, so i've left that as pseudo-sql. That RIGHT JOIN is very messy too, but I've no idea what you're trying to achieve there.
Anyway, this need to replace your EXEC command. Read the below, understand the below, and don't make the mistake of concatenating raw strings in your dynamic SQL:
DECLARE #SQL nvarchar(MAX)
SET #SQL = N' USE ' + QUOTENAME(#Database) + N';' + NCHAR(10) +
N'SELECT Customer.Name,' + NCHAR(10) +
N' [Product Categories].[Product Number]' + NCHAR(10) +
--where is your FROM?
N'FROM {Something}' + NCHAR(10) + --This needs fixing
N' RIGHT JOIN((' + QUOTENAME(#Schema) + N'.Customer' + NCHAR(10) + --This whole thing is messy, but i'm not fixing it, as I have no data
N' INNER JOIN ' + QUOTENAME(#Schema) + N'.Client ON Customer.Name = Client.Name)' + NCHAR(10) +
N' LEFT JOIN ' + QUOTENAME(#Schema) + N'.Product ON Client.Name = Product.Client)ON [Product Categories].[Product Number] = Client.Product' + NCHAR(10) +
N'WHERE (Customer.Name = #dName' + NCHAR(10) +
N' AND Customer.[Order] = ''Y'')' + NCHAR(10) +
N' OR (Client.Name = #dName' + NCHAR(10) +
N' AND Customer.[Order] = ''Y'');';
EXEC sp_executesql #SQL, N'#dName varchar(20)', #dName = #Name;
If you are directly write in sql then you can use as below
WHERE Customer.Name = #Name
You are using a Dynamic SQL, and if you print your query it will be like
WHERE Customer.Name = (John)
And that's wrong, you can do like
EXECUTE sp_executesql N' ..WHERE Customer.Name = #CName',
N'#CName VARCHAR(20)',
#CName = 'John';
Or even
--#Name should be declared and has 'John' value
EXECUTE sp_executesql N' ..WHERE Customer.Name = #CName',
N'#Name VARCHAR(20)',
#Name = #Name;
Why does SQL think that the parameter is a column and not just plain text?
Well, as you can see before, the parameter you pass will be John not 'John', thus SQL Server will think it's a column name.
try giving single quotes in where clause
WHERE (Customer.Name = ''' + #Name + ''' AND Customer.Order = ''Y'')
OR (Client.Name = ''' + #Name + ''' AND Customer.Order = ''Y'');
Related
i have a query that returns 1 column
this column is a string containing an update statement
how can i run all updates by runing this query?
SELECT 'UPDATE TOP (' + CAST([Deploy] AS varchar(10)) + ') TRV
SET TRV.SourceStationID = ' + CAST([Station_From] AS varchar(10)) + ',
TRV.MissingStationSourceCode = 888
FROM [Tickets].[dbo].TCK_STG_Fact_Contract_Magnetic_Travels1 TRV
LEFT OUTER JOIN [Tickets].[dbo].[TCK_DWH_Dim_Date] DAT ON CAST(TRV.[ExitDate] AS DATE) = CAST(DAT.Date AS DATE)
LEFT OUTER JOIN [Tickets].[dbo].[TCK_DWH_Dim_Manual_PartOfDay] POD ON DATEPART(HOUR,TRV.[ExitDate]) = POD.PartOfDay_Hour
WHERE DAT.Day_Type_Code = ' + CAST([Day_Type_Code] AS varchar(10)) + '
AND POD.PartOfDay_Code = ' + CAST([PartOfDay_Code] AS varchar(10)) + '
AND TRV.DestinationStation = ' + CAST([DestinationStation] AS varchar(10)) + '
AND TRV.SourceStationID IS NULL'
FROM [Tickets].[dbo].[TCK_TMP_Fact_ALL_Travels_History_FromMissing]
A few comments I want to make before you read this answer:
I still believe this is an XY Problem and that we should really be answering the real problem you have; what ever that is (you haven't told us).
I have answered the question literally. This will be far from performant and it's still not got an order for the TOP.
This is untested due to a lack of sample data.
Anyway, you can do this with a CURSOR and a parametrised dynamic statement. I have guessed your data types, and as I have no sample data I cannot test to see if this is valid. If the dynamic statement errors, you'll have to trouble shoot that on your own, as I can't run it (obviously after fixing the data types in all locations).
I've also removed the 3 part naming and enforced a connection to the Tickets database prior to the batch.
USE Tickets;
GO
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SET #SQL = N'UPDATE TOP (#Deploy) TRV' + #CRLF + --Don't forget, this translates to #Deploy Arbirtrary rows, not the "top" #Deploy rows, due to a lack of any ordering.
N'SET TRV.SourceStationID = #StationFrom,' + #CRLF +
N' TRV.MissingStationSourceCode = 888' + #CRLF +
N'FROM [dbo].TCK_STG_Fact_Contract_Magnetic_Travels1 TRV' + #CRLF +
N' LEFT OUTER JOIN [dbo].[TCK_DWH_Dim_Date] DAT ON CAST(TRV.[ExitDate] AS DATE) = CAST(DAT.Date AS DATE)' + #CRLF +
N' LEFT OUTER JOIN [dbo].[TCK_DWH_Dim_Manual_PartOfDay] POD ON DATEPART(HOUR,TRV.[ExitDate]) = POD.PartOfDay_Hour' + #CRLF +
N'WHERE DAT.Day_Type_Code = #DayTypeCode' + #CRLF +
N' AND POD.PartOfDay_Code = #PartOfDayCode' + #CRLF +
N' AND TRV.DestinationStation = #DestinationStation' + #CRLF +
N' AND TRV.SourceStationID IS NULL;';
DECLARE Missing_Cursor CURSOR FAST_FORWARD FOR
SELECT Deploy,
Station_From,
Day_Type_Code,
PartOfDay_Code,
DestinationStation
FROM [dbo].[TCK_TMP_Fact_ALL_Travels_History_FromMissing]
DECLARE #Deploy int, --Assumed data type, due to being used in TOP
#StationFrom int, --Guessed Datatype
#DayTypeCode int, --Guessed Datatype
#PartOfDayCode int, --Guessed Data Type
#DestinationStation varchar(10); --Guessed Data Type
OPEN Missing_Cursor;
FETCH NEXT FROM Missing_Cursor
INTO #Deploy, #StationFrom, #DayTypeCode, #PartOfDayCode, #DestinationStation;
WHILE ##FETCH_STATUS = 0 BEGIN
--Uses same assumed data types
EXEC sys.sp_executesql #SQL, N'#Deploy int, #StationFrom int, #DayTypeCode int, #PartOfDayCode int, #DestinationStation varchar(10)', #Deploy, #StationFrom, #DayTypeCode, #PartOfDayCode, #DestinationStation;
FETCH NEXT FROM Missing_Cursor
INTO #Deploy, #StationFrom, #DayTypeCode, #PartOfDayCode, #DestinationStation;
END
CLOSE Missing_Cursor;
DEALLOCATE Missing_Cursor;
suggestions here did it with the same time.
what solved the performance issues was adding 2 indexes
thx all for trying to help
I have a stored procedure that uses dynamic SQL. My company recently decided to get rid of dynamic code.
Here I greatly simplified it to explain my point; #PersonID and #MhnNum are parameters of the stored procedure:
Declare #sql Varchar(max)
Set #sql="Select from tableA p"
If #PersonID Is Not Null
Set #sql = #sql + ' Where p.[ID] = ' + cast(#PersonID as varchar(12))
If #MhnNum Is Not Null
Set #sql = #sql + ' Where p.[MhnNum] = ' + '''' + cast(#MhnNum as varchar(12)) + ''''
Is there an (easy) way to get rid of this dynamic SQL?
Right now my solution is to create 2 If's with repetitive code. There must be a more elegant way to do this.
Honestly, Dynamic SQL is probably the way to go, as you have a catch-all query, just not your dynamic SQL. It's a huge injection risk. Parametrise the statements:
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = CHAR(13) + CHAR(10);
SET #SQL = N'SELECT *' + #CRLF +
N'FROM TableA A' + #CRLF +
CASE WHEN #PersonID IS NOT NULL THEN N'WHERE A.ID = #PersonID;'
WHEN #MhnNum IS NOT NULL THEN N'WHERE A.MhnNum = #MhnNum;'
END;
EXEC sys.sp_executesql #SQL, N'#PersonID int, #MhnNum int', #PersonID, #MhnNum;
Using a non-dynamic approach will cause the caching of bad query plans, which isn't going to be desired.
If you must use non dynamic SQL (due to a pointless Company Policy, there is nothing wrong with Dynamic SQL if used correctly), then add OPTION RECOMPILE:
IF #PersonID IS NOT NULL AND #MhnNum IS NOT NULL
THROW 68542, N'Both #PersonID and #MhnNum cannot be non-NULL values.', 11;
SELECT *
FROM TableA A
WHERE (A.ID = #PersonID OR #PersonID IS NULL)
AND (A.MhnNum = #MhnNum OR #MhnNum IS NULL)
OPTION (RECOMPILE);
The THROW is in there, as your code will also error if you have 2 non-NULL values.
A simple combination of or and and can get you the same result:
Select *
From tableA
Where (#personId is null or id = #personId)
And (#MhnNum is null or whnNum =#mhnNum)
(writing in a comment would be a mess)
You could check the parameters and act accordingly. ie:
Select from tableA p
where (#PersonID IS NULL or p.[ID] = #PersonID) and
(#MhnNum IS NULL or p.[MhnNum] = #MhnNum);
I can't find a full solution to my problem. I have an existing stored procedure that takes dynamic input as a parameter value. I need to execute this procedure (with a dynamic variable) and would like to somehow SELECT * INTO #mytable without having to declare the schema of the temp table.
I've tried using OPENROWSET but it doesn't allow me to specify the variable (only hard-code it):
select * into #table from openrowset('SQLNCLI', 'Server=localhost;Trusted_Connection=yes;', 'exec SERVER..MYSTOREDPROCEDURE #parameter = 123')
The only other way I'm aware of is wrapping it in a string and using EXEC(#sql), but I can't figure out how to "SELECT * INTO #table" from that.
What are my options? Can I create a UDF table function that can return a dynamic table? Doubtful...
I'm guessing here, but you'll need to do the whole thing in dynamic SQL. So, instead, something like:
DECLARE #SQL nvarchar(MAX);
DECLARE #param nvarchar(10) = 123;
SET #SQL = N'SELECT *' + NCHAR(10) +
N'INTO #table' + NCHAR(10) +
N'FROM OPENROWSET(''SQLNCLI'', ''Server=localhost;Trusted_Connection=yes;'', ''exec SERVER..MYSTOREDPROCEDURE #parameter = ' + QUOTENAME(#param,N'''') +N''');' + NCHAR(10) +
N'SELECT *' + NCHAR(10) + --Of course, I assume you're doing something more complex that a SELECT *.
N'FROM #table;';
PRINT #SQL;
EXEC sp_executesql #SQL;
I think I figured it out. I really only had to modify the temp variable names to put the temp table into the global scope. This works for me with my actual tables (I renamed them for the purposes of this post).
IF OBJECT_ID ('tempdb..##mytemptable') is not null drop table ##mytemptable
declare #id int = 112
declare #sql nvarchar(max) ='SELECT * INTO ##mytemptable FROM OPENROWSET(''SQLNCLI'', ''Server=localhost;Trusted_Connection=yes;'', ''exec SERVER..MyStoredProcedure #ID =' + convert(varchar(10), id) + ''')'
-- invokes and inserts into ##mytemptable
exec sp_executesql #sql
-- now I can query from the global scope
select * from ##mytemptable
I have a SQL query string that is like this:
DECLARE #sql varchar(max)
SET #sql = ' INSERT INTO ' + #tempTable1 +
' SELECT 0 as Type1, 0 as Type2, ' +
'''' + #name + ''' as CompanyName ' +
' FROM #tempTable2 tt2'
The query runs fine except for two names that happen to contain a single quote (ex: Pete's Corner). When either one of these names becomes part of the query it breaks the query string. I thought the easiest thing to do would be to replace the single quote like this replace(#name,'''','') but it doesn't work because I'm already in a string and so its affecting the rest of the statement. Altering the table itself is not an option unfortunately.
How can I replace or remove these single quotes?
Addition: I apologize, I did not include the part where #name is actually being populated from another database table by a join so setting the value of #name before the string is created I think would be difficult for me.
Why do you need to do this at all? You should be passing strong parameters to sp_executesql instead of munging all of your parameters into a single string and using EXEC(). More info on that here.
DECLARE #sql NVARCHAR(MAX), #name NVARCHAR(32);
SET #name = 'Pete''s Corner';
SET #sql = 'INSERT INTO ' + #tempTable1 +
' SELECT 0 as Type1, 0 as Type2, #name as CompanyName ' +
' FROM #tempTable2 tt2';
EXEC sp_executesql #sql, N'#name NVARCHAR(32)', #name;
I presume the #name parameter actually gets populated from elsewhere, and if using proper parameterization you shouldn't have to deal with escaping the '.
Now I'm not quite sure what #tempTable1 is supposed to represent, or if you can access #tempTable2 from this scope, but whenever you find yourself running a replace that requires '''' or '''''' (or both), you should ask yourself if maybe there's a better way.
I think this should do it:
DECLARE #sql varchar(max)
SET #sql = ' INSERT INTO ' + #tempTable1 +
' SELECT 0 as Type1, 0 as Type2, ' + ''''+
replace( #name ,'''','''''')+''''+' as CompanyName
FROM #tempTable2 tt2'
You can use sp_executesql system procedure. sp_executesql will allow you to call dynamic SQL with #name parameter instead of embedding it into the SQL.
DECLARE #sql nvarchar(max),
#name varchar(50)
SET #name = 'qwe'''
SET #sql = 'INSERT INTO ' + #tempTable1 +
' SELECT 0 as Type1, 0 as Type2, ' +
'#name as CompanyName ' +
'FROM #tempTable2 tt2'
--PRINT #sql
EXEC sp_executesql #sql, N'#name varchar(50)', #name
I will preface this question by saying, I do not think it is solvable. I also have a workaround, I can create a stored procedure with an OUTPUT to accomplish this, it is just easier to code the sections where I need this checksum using a function.
This code will not work because of the Exec SP_ExecuteSQL #SQL calls. Anyone know how to execute dynamic SQL in a function? (and once again, I do not think it is possible. If it is though, I'd love to know how to get around it!)
Create Function Get_Checksum
(
#DatabaseName varchar(100),
#TableName varchar(100)
)
RETURNS FLOAT
AS
BEGIN
Declare #SQL nvarchar(4000)
Declare #ColumnName varchar(100)
Declare #i int
Declare #Checksum float
Declare #intColumns table (idRecord int identity(1,1), ColumnName varchar(255))
Declare #CS table (MyCheckSum bigint)
Set #SQL =
'Insert Into #IntColumns(ColumnName)' + Char(13) +
'Select Column_Name' + Char(13) +
'From ' + #DatabaseName + '.Information_Schema.Columns (NOLOCK)' + Char(13) +
'Where Table_Name = ''' + #TableName + '''' + Char(13) +
' and Data_Type = ''int'''
-- print #SQL
exec sp_executeSql #SQL
Set #SQL =
'Insert Into #CS(MyChecksum)' + Char(13) +
'Select '
Set #i = 1
While Exists(
Select 1
From #IntColumns
Where IdRecord = #i)
begin
Select #ColumnName = ColumnName
From #IntColumns
Where IdRecord = #i
Set #SQL = #SQL + Char(13) +
CASE WHEN #i = 1 THEN
' Sum(Cast(IsNull(' + #ColumnName + ',0) as bigint))'
ELSE
' + Sum(Cast(IsNull(' + #ColumnName + ',0) as bigint))'
END
Set #i = #i + 1
end
Set #SQL = #SQL + Char(13) +
'From ' + #DatabaseName + '..' + #TableName + ' (NOLOCK)'
-- print #SQL
exec sp_executeSql #SQL
Set #Checksum = (Select Top 1 MyChecksum From #CS)
Return isnull(#Checksum,0)
END
GO
It "ordinarily" can't be done as SQL Server treats functions as deterministic, which means that for a given set of inputs, it should always return the same outputs. A stored procedure or dynamic sql can be non-deterministic because it can change external state, such as a table, which is relied on.
Given that in SQL server functions are always deterministic, it would be a bad idea from a future maintenance perspective to attempt to circumvent this as it could cause fairly major confusion for anyone who has to support the code in future.
Here is the solution
Solution 1:
Return the dynamic string from Function then
Declare #SQLStr varchar(max)
DECLARE #tmptable table (<columns>)
set #SQLStr=dbo.function(<parameters>)
insert into #tmptable
Exec (#SQLStr)
select * from #tmptable
Solution 2:
call nested functions by passing parameters.
You can get around this by calling an extended stored procedure, with all the attendant hassle and security problems.
http://decipherinfosys.wordpress.com/2008/07/16/udf-limitations-in-sql-server/
http://decipherinfosys.wordpress.com/2007/02/27/using-getdate-in-a-udf/
Because functions have to play nicely with the query optimiser there are quite a few restrictions on them. This link refers to an article that discusses the limitations of UDF's in depth.
Thank you all for the replies.
Ron: FYI, Using that will throw an error.
I agree that not doing what I originally intended is the best solution, I decided to go a different route. My two choices were to use sum(cast(BINARY_CHECKSUM(*) as float)) or an output parameter in a stored procedure. After unit testing speed of each, I decided to go with sum(cast(BINARY_CHECKSUM(*) as float)) to get a comparable checksum value for each table's data.