Using SQL Server I am trying to inject a string into a SQL statement based on an if statement, note that am trying to accomplish this inside a stored procedure.
I am currently getting an error for this code:
Declare #topString varchar(240)
IF #topRecords > 0
SET #topString = 'top 500'
ELSE
SET #topString = ''
SELECT #topString * FROM( //incorrect syntax near FROM
SELECT top 500 c.Id as [Customer Id],....
UNION
SELECT top 500 c.Id as [Customer Id],....
)as table1
Order by 1 desc
Edit
if somethingTrue
#whereCondition = '1 = 1 '
else
#whereCondition = branch = #branch
select * from table
where #whereCondition AND etc...
Correct
for injection inside an if statement go with Jodrell
but if you need a dynamic top then go with what was suggested by Kaf.
thanks both for the help!
If you want to decide number of top records depending on #topRecords, you can do it using an INT or BIGINT depending on the number of records needed.
DECLARE #top INT --This is declared as an int here
IF #topRecords > 0
SET #top = 500
ELSE
SET #top = 5000000 --Make it more than records if you need all
--or make it 0 if no records needed.
--#top has to be >=0
--How to use it
SELECT TOP (#top) * FROM YourTable
EDIT: Your question had only a top injection initially. However, If you need more injections (as per your recent question edit) then I would suggest to use a dynamic query as per #Jordell's answer.
You can't inject statement parts as variables like that, however you can change most values for parameters.
Having a stored procedure perform operations that may require different query plans, based on a parameter is a bad idea, the results of this SP could vary wildly based on the value of the #topRecords parameter. You would need to use the RECOMPILE option to warn the query engine, mitigating much of the benefit of SPs. Have you considered just having two stored procedures?
If you want to do it dynamically, you could build the whole statement dynamically, making one big string, then execute that.
You should investigate using sp_executesql to execute the string/VarChar. Then similar queries will benefit from query plan reuse.
As ever Sommarskog is a good reference.
Something like this
DECLARE #topString varchar(240);
DECLARE #statement varchar(max);
IF #topRecords > 0
SET #topString = 'TOP 500';
ELSE
SET #topString = '';
SET #statement = 'SELECT ' + #topString + ' * FROM
(
SELECT TOP 500 c.Id as [Customer Id], ....
UNION
SELECT TOP 500 c.Id as [Customer Id], ....
) table1
ORDER BY 1 DESC'
/* Then execute #statement */
EXEC sp_executesql #statement
Related
In the following script, I want to set the WHERE statement only if the CASE is set.
DECLARE #cmdINSERT varchar(8000);
declare #dowhere binary;
declare #date varchar(10);
set #dowhere = 1;
set #date = '14.03.2020';
set #cmdINSERT =
'SELECT *
FROM [TABLE]
WHERE [Changed At] >=
CASE WHEN ' + #dowhere + '
when 1 THEN ''' + #date + '''
ELSE [Changed At]
end;'
EXEC(#cmdINSERT)
But I am getting the error:
The data types varchar and binary are incompatible in the add operator
It seems like you're overly complicating the matter. You don't need a dynamic statement here, nor do you need 2 parameters. Use a single parameter, and pass the value NULL if you don't want to consider it. This is what is known as a "catch up" or "kitchen sink" query:
DECLARE #Date date = '20200314'; --Notice it is NOT a varchar
SELECT {Your Columns} --Replace this with a list of your columns
FROM dbo.YourTable --Replace this with your actual schema and table
WHERE [Changed At] >= #Date --Ideally, don't use names that must be delimit Identified
OR #Date IS NULL
OPTION (RECOMPILE);
The OPTION (RECOMPILE) is there to stop caching of the wrong plan, as if #Date has a value of NULL a full table scan will be required, however, if not then (depending on your indexes) one may not be required, which could be far faster. As the query is incredibly simple, the expense of regenerating query plan will be negligible, and likely <=1ms in cost.
It seems that you have complicated the solution. Why you do not try this?
SELECT *
FROM [Table]
WHERE #dowhere = 0
OR [Changed At] >= #date;
However it would be better if you change the data type of #dowhere to BIT instead of BINARY
Your approach is silly. Get rid of the case in the where altogether if no filtering is desired:
set #cmdINSERT = '
SELECT *
FROM [TABLE]
[where]';
SET #cmdInsert = REPLACE(#cmdInsert,
'[where]',
(CASE WHEN #dowwhere = 1
THEN 'WHERE [Changed At] = #date'
ELSE ''
END)
);
exec sp_executesql #cmdInsert, N'#date date', #date=#date;
Note that this also properly passes in the date as a parameter rather than munging the query string. If the date parameter is not used, that is fine: SQL Server just ignores parameters that are not used.
I have been spending a fair amount of time researching a method to adapt an sql query, while in a loop in order to bring back from multiple tables.
The one method I came across that makes this possible would be executing the query as a loadstring, then you could adapt the query each time the loop runs ( as explained via this link: https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-executesql-transact-sql ).
To be more specific, I am attempting to run a rather large query, which loops through multiple databases - however each database has a branch number, such as A, B, C, D, E etc.. So each time I execute the query I am using joins to go to all the databases I need from A. In order to make this work, I would need to copy and paste this entire 500 line query, over 5 times in order to cover every branch.
The method using loadstring would end up being similar to this:
DECLARE process varchar(max) = 'select * from Vis_' + Branch[i] + '_Quotes' exec(process)
Is there a better method to adapt your query search while its running?
Here is one example of how this might be used. It's not clear if this fits your requirements, but it appears that dynamic SQL is new to you so I've supplied an example that includes both looping and passing in parameters safely. This is untested, but hopefully should get you on the right track.
This assumes you have an existing table of branches with the corresponding branch codes (ideal, as then the script doesn't need updating when adding/disabling/removing a branch). If you don't, then you could always create a table variable and insert branches at the top of your script:
declare #sql varchar(max),
#BranchCode nvarchar(10) = '',
#param1 int,
#param2 nvarchar(10);
while 1=1 begin
set #BranchCode =
(select top 1 Code from Branch where Active = 1 and Code > #BranchCode order by Code)
if #BranchCode is null break;
set #sql = #sql + 'select * from Vis_' + #BranchCode + '_Quotes
where col1 = #param1 and #col2 like #param2
' -- notice extra linebreak (or space) added to separate each query
end
exec sp_executesql #sql,
'#param1 int, #param2 nvarchar(10), ...', -- parameter definitions
#param1, #param2, ... -- any additional parameters you need to safely pass in
I am trying to create a procedure where the conditional statement changes based on the value that the user inputs. A simplified example of what I'm trying to achieve is below.
Create Procedure [LossRatioReport] (#construction AS VARCHAR(2), #state AS VARCHAR(2)) AS
DECLARE #construction_condition AS NVARCHAR(MAX)
DECLARE #construction AS VARCHAR(2)
SET #construction = '01'
SET #construction_condition = CASE #construction WHEN '01'
THEN #construction_condition = '('01', 'Frame Construction', 'Frame')'
ELSE #construction_condition = '00'
END
BEGIN
SELECT year, SUM(loss)
FROM Database_1
WHERE id IN (SELECT DISTINCT id FROM Database_2 WHERE construction = #construction_condition)
END
GO
I want to do it this way because there is a list of both integer and strings for each type and I don't want to rewrite the code over and over for each condition. But when I try and run my code, I keep getting incorrect syntax messages for the variables in the WHERE statement.
You'll need to use dynamic sql for this:
DECLARE #sql NVARCHAR(MAX)
SELECT #sql = 'SELECT year, SUM(loss)
FROM Database_1
WHERE id IN (SELECT DISTINCT id FROM Database_2 WHERE construction IN( ' + #construction_condition
EXEC sp_executesql #sql
As Sean Lange stated, something should probably be mentioned on SQL Injection. Be sure you are aware of the danger before implementing a solution with dynamic SQL. Here is an overview, and Google can tell you much, much more.
I have a SQL stored procedure of the form
SELECT [fields] FROM [table] WHERE #whereSql
I want to pass the procedure an argument (#whereSql) which specifies the entire WHERE clause, but the following error is returned:
An expression of non-boolean type specified in a context where a condition is expected
Can this be done?
The short answer is that you can't do it like this -- SQL Server looks at the contents of a variable as a VALUE. It doesn't dynamically build up the string to execute (which is why this is the correct way to avoid SQL injection attacks).
You should make every effort to avoid a dynamic WHERE as you're trying to do, largely for this reason, but also for the sake of efficiency. Instead, try to build up the WHERE clause so that it short-circuits pieces with lots of ORs, depending on the situation.
If there's no way around it, you can still build a string of your own assembled from the pieces of the command, and then EXEC it.
So you could do this:
DECLARE #mywhere VARCHAR(500)
DECLARE #mystmt VARCHAR(1000)
SET #mywhere = ' WHERE MfgPartNumber LIKE ''a%'' '
SELECT #mystmt = 'SELECT TOP 100 * FROM Products.Product AS p ' + #mywhere + ';'
EXEC( #mystmt )
But I recommend instead that you do this:
SELECT TOP 100 *
FROM Products.Product AS p
WHERE
( MfgPartNumber LIKE 'a%' AND ModeMfrPartNumStartsWith=1)
OR ( CategoryID = 123 AND ModeCategory=1 )
I believe this can be done using Dynamic SQL. See below:
CREATE PROCEDURE [dbo].[myProc]
#whereSql nvarchar(256)
AS
EXEC('SELECT [fields] FROM [table] WHERE ' + #whereSql)
GO
That said, you should do some serious research on dynamic SQL before you actually use it.
Here are a few links that I came across after a quick search:
http://www.sommarskog.se/dynamic_sql.html
http://msdn.microsoft.com/en-us/library/aa224806%28SQL.80%29.aspx
http://www.itjungle.com/fhg/fhg100505-story02.html
Make sure you read this fully
www.sommarskog.se/dynamic_sql.html
Dynamic SQL listed in some of the Answers is definitely a solution. However, if Dynamic SQL needs to be avoided, one of the solutions that I prefer is to make use of table variables (or temp tables) to store the parameter value that is used for comparison in WHERE clause.
Here is an example Stored Procedure implementation.
CREATE PROCEDURE [dbo].[myStoredProc]
#parameter1 varchar(50)
AS
declare #myTempTableVar Table(param1 varchar(50))
insert into #myTempTableVar values(#parameter1)
select * from MyTable where MyColumn in (select param1 from #myTempTableVar)
GO
In case you want to pass in multiple values, then the comma separated values can be stored as rows in the table variable and used in the same way for comparison.
CREATE PROCEDURE [dbo].[myStoredProc]
#parameter1 varchar(50)
AS
--Code Block to Convert Comma Seperated Parameter into Values of a Temporary Table Variable
declare #myTempTableVar Table(param1 varchar(50))
declare #index int =0, #tempString varchar(10)
if charindex(',',#parameter1) > 0
begin
set #index = charindex(',',#parameter1)
while #index > 0
begin
set #tempString = SubString(#parameter1,1,#index-1)
insert into #myTempTableVar values (#tempString)
set #parameter1 = SubString(#parameter1,#index+1,len(#parameter1)-#index)
set #index = charindex(',',#parameter1)
end
set #tempString = #parameter1
insert into #myTempTableVar values (#tempString)
end
else
insert into #myTempTableVar values (#parameter1)
select * from MyTable where MyColumn in (select param1 from #myTempTableVar)
GO
http://sqlmag.com/t-sql/passing-multivalued-variables-stored-procedure
try this it works!!
CHARINDEX (',' + ColumnName + ',', ',' +
REPLACE(#Parameter, ' ', '') + ',') > 0
execute syntax set #Parameter= 'nc1,nc2'
I am running into a very strange problem in a SQL Server stored procedure.
I have two databases. One contains the database for my billing system. The other is a reporting system with summarized data. Within this database there is a table with summarized job information. When this data is created, one of the fields, BilledToDate, is null. I wrote a stored procedure that creates a cursor that goes through this table and gets all of the job numbers. I then go through each job number and run a query against the billing database to get the total amount of billing that has been charged against job. Once I have this total, I update the BilledToDate column with this value.
The problem is that after running the stored procedure, some of the results are correct and some aren't. There doesn't appear to be any logical explanation as to why one is right and the next one is isn't. I put some print statements in the stored procedure and all of the values were correct. As an example, for one record the correct sum was 99,218.25 but the update put a value of 14,700.70 into the BilledToDate field. I added a varchar column to the table and populated that field. They are all correct. This leads me to believe that it is a casting problem but I checked and double checked my datatypes and they all look correct. I am pulling my hair out on this one (what little that is left).
My stored procedure is below. The InvoiceAmt field is a decimal(16,2) in the invchead table and I have kept it consistent throughout the process so I don't undertand why this is happening.
ALTER PROCEDURE [dbo].[sp_CalculateBilledToDate]
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #JobID varchar(10)
DECLARE #RecordID int
DECLARE #BilledToDate decimal(16,2)
DECLARE c1 CURSOR FOR
SELECT JobID, RecordID
FROM StructuralOpenBilling
OPEN c1
FETCH NEXT FROM c1
INTO #JobID, #RecordID
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #BilledToDate = CONVERT(money, CASE WHEN SUM(invoiceamt) > 0 THEN SUM(InvoiceAmt) ELSE 0 END)
FROM mfgsys803.dbo.invchead
WHERE shortchar01 = RTRIM(#JobID)
PRINT 'Record ID: ' + CONVERT(varchar(10), #RecordID) + ' JobID: ' + RTRIM(CONVERT(varchar(10), #JobID)) + ' Billed: ' + CONVERT(varchar(10), #BilledToDate)
UPDATE StructuralOpenBilling
SET BilledToDate = #BilledToDate, BilledCheck = CONVERT(varchar(50), #BilledToDate)
WHERE RecordID = #RecordID
PRINT 'Record ID: ' + CONVERT(varchar(10), #RecordID) + ' JobID: ' + RTRIM(CONVERT(varchar(10), #JobID)) + ' Billed: ' + CONVERT(varchar(10), #BilledToDate)
FETCH NEXT FROM c1
INTO #JobID, #RecordID
END
CLOSE c1
DEALLOCATE c1
END
Any ideas would be appreciated.
Thanks.
John
I notice a few things you might look at. BTW -- you're really over-thinking this -- a few ideas about that here as well.
SELECT #BilledToDate = CONVERT(money, CASE WHEN SUM(ISNULL(invoiceamt,0)) > 0 THEN SUM(ISNULL(InvoiceAmt,0)) ELSE 0 END)
Is the same as
SELECT #BilledToDate = CONVERT(money, SUM(ISNULL(invoiceamt,0)))
*NOTE the use of ISNULL() in both -- this would be important, as you can't do math on nulls.
Not necessary to use a cursor. Just join your two tables together in a single update statement and work on it as a batch.
UPDATE StructuralOpenBilling
SET S.BilledToDate = I.BilledToDate
FROM
StructuralOpenBilling S
INNER JOIN
(SELECT shortchar01, CONVERT(money, SUM(ISNULL(invoiceamt,0))) as BilledToDate
FROM mfgsys803.dbo.invchead) I
ON
S.JobID = I.shortchar01
Does this do what you are trying to do?
WITH inv AS
(
SELECT shortchar01,
CONVERT(MONEY, CASE WHEN SUM(invoiceamt) > 0 THEN
SUM(InvoiceAmt)
ELSE 0 END) AS BilledToDate
FROM mfgsys803.dbo.invchead
GROUP BY shortchar01
)
UPDATE StructuralOpenBilling
SET BilledToDate = inv.BilledToDate,
BilledCheck = CONVERT(VARCHAR(50), inv.BilledToDate)
FROM StructuralOpenBilling sob
JOIN inv ON inv.shortchar01 = RTRIM(sob.JobID)