Input CASE1=wen #type is NULL-> WHERE c.Date_Redeemed BETWEEN #Start AND #End-- this should execute
If CASE2=wen #start,#end is NULL->WHERE c.Type=#type-- this should execute
CASE 3=wen #value is null->WHERE c.Date_Redeemed BETWEEN #Start AND #End AND c.Type=#type this should execute
CASE4=wen #marketclass is NULL->WHERE c.Date_Redeemed BETWEEN #Start AND #End AND c.Type=#type AND c.ordervalue BETWEEN #price1 AND #price2
VAR=#type,#start,#price1,#marketclass if eithr of var is NULL dynamicaly other input where condition has to execute like diff combination of input comes
WHERE
(c.Type = #type AND o.Date_of_Purchase BETWEEN #start AND #end) OR
(#start IS NULL AND c.Type = #type) OR
(#type IS NULL and o.Date_of_Purchase BETWEEN #start AND #end) OR
(#start IS NULL AND #type IS NULL)
if both are provided, only rows where type and date match are returned,
if type is not provided, date is used,
if date not provided type is used and
if nothing provided all rows return. To make no rows return, remove the last predicate
Code:
CREATE PROCEDURE dbo.sample
#start DATE, #end DATE,
#type VARCHAR(5),
#price1 MONEY, #price2 MONEY
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQL VARCHAR(MAX)
SET #SQL= 'SELECT DISTINCT o.O_Id,o.Sale_Price,o.Order_Line_Id,Private_Band,c.Date_of_Purchase,c.Date_Redeemed,c.Credit_Memo,c.Credit_Memo_Created_Date,c.Credit_Memo_Approved_Date,
c.Type,c.Points_Issued,o.Date_of_Purchase FROM Order_Details o ,Transaction_Historys c WHERE -1='-1''
IF #type IS NOT NULL AND #type <> 0
SET #SQL = #SQL+ 'c.Type = #type'
ELSE
IF #start IS NOT NULL AND #start <> 0
SET #SQL = #SQL+ 'c.Date_redeemed BETWEEN #start AND #end'
EXECUTE dbo.sample #type='Earn',#start='2010-02-10',#end='2020-04-11'
END
Having looked through this, I'm actually kind of reluctant to post this answer, because there's so many things wrong with this:
prone to SQL injection;
using antiquated JOIN syntax;
clunky logic.
The simple answer is that sometimes just because you CAN do something in SQL doesn't mean you actually SHOULD.
Anyway, I think is closer to what you wanted?
CREATE PROCEDURE dbo.[sample] (
#start DATE,
#end DATE,
#type VARCHAR(5),
#price1 NUMERIC(9,2),
#price2 NUMERIC(9,2))
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql VARCHAR(1024);
SELECT #sql = N'
SELECT DISTINCT
o.O_Id,
o.Sale_Price,
o.Order_Line_Id,
Private_Band, --alias?
c.Date_of_Purchase,
c.Date_Redeemed,
c.Credit_Memo,
c.Credit_Memo_Created_Date,
c.Credit_Memo_Approved_Date,
c.Type,
c.Points_Issued,
o.Date_of_Purchase
FROM
Order_Details o,
Transaction_Historys c'; --Join condition?!
DECLARE #constraint VARCHAR(1024) = '';
IF #type IS NOT NULL AND #type != 0
BEGIN
SELECT #constraint = CONCAT(' WHERE c.Type = ''', #type, '''');
END;
IF #start IS NOT NULL AND #end IS NOT NULL
BEGIN
SELECT #constraint = CONCAT(#constraint,
CASE WHEN #constraint = '' THEN ' WHERE ' ELSE ' AND ' END,
' c.Date_redeemed BETWEEN ''',
CONVERT(VARCHAR(12), #start, 112),
''' AND ''',
CONVERT(VARCHAR(12), #end, 112)),
'''';
END;
SELECT #sql = CONCAT(#sql, #constraint);
EXEC sp_executesql #sql;
END;
GO
EXEC dbo.[sample] #type = 'Earn', #start = '20100210', #end = '20200411', #price1 = NULL, #price2 = NULL;
I made quite a few changes here:
I couldn't bring myself to use a MONEY type, so change it back if you really want it;
I added some comments to your script ;)
I sort of fixed the actual issue, that you need to cope with one or two or even no constraints being passed in.
Related
I'm trying to use a split function to add a range of values to a query. I'd also like to 'OR' the values together but first things first. I'm following an example I have found in a much larger query that uses the split function, so I've made a small query to try to figure out how it works. So far though, all I get is the error *"The name 'Select * from Country As sp WHERE (sp.CountryID in (SELECT [Value] FROM dbo.Split('2,22,', ',')))' is not a valid identifier."* I'm knew to DynamicSQL and I'm not quite sure how this split function is supposed to work.
DECLARE #Countries varchar(MAX);
DECLARE #FiltersOn bit;
DECLARE #Country int;
DECLARE #Query varchar(MAX);
Set #FiltersOn = 0;
Set #Query = 'Select * from Country As sp ';
Set #Countries ='2,22,'
IF ( #Countries IS NOT NULL )
BEGIN
IF ( #FiltersOn = 1 )
BEGIN
SET #Query = #Query + ' AND '
END
ELSE
BEGIN
SET #Query = #Query + ' WHERE '
SET #FiltersOn = 1
END
SET #Query = #Query
+ '(sp.CountryID in (SELECT [Value] FROM dbo.Split('''
+ #Countries + ''', '','')))'
END
EXEC #Query
This is the definition of the Country table:
CREATE TABLE [dbo].[Country](
[CountryID] [int] IDENTITY(1,1) NOT NULL,
[AgentID] [int] NULL,
[Name] [varchar](50) NULL,
[CountryLookupID] [int] NOT NULL
and this is the split function code:
CREATE FUNCTION [dbo].[Split]
(
#String varchar(8000),
#Delimiter varchar(10)
)
RETURNS #ValueTable table ([Value] varchar(255))
BEGIN
DECLARE #NextString varchar(4000)
DECLARE #Pos int
DECLARE #NextPos int
DECLARE #DelimiterCheck varchar(1)
-- initialise
SET #NextString = ''
SET #DelimiterCheck = RIGHT(#String, 1)
-- add trailing delimiter
IF (#DelimiterCheck <> #Delimiter)
SET #String = #String + #Delimiter
-- find position of first delimiter
SET #Pos = CHARINDEX(#Delimiter, #String)
SET #NextPos = 1
-- loop while there is a delimiter in the string
WHILE (#Pos <> 0)
BEGIN
SET #NextString = SUBSTRING(#String, 1, #Pos - 1)
INSERT INTO #ValueTable ([Value]) VALUES (#NextString)
SET #String = SUBSTRING(#String, #Pos + 1, LEN(#String))
SET #NextPos = #Pos
SET #Pos = CHARINDEX(#Delimiter, #String)
END
RETURN
END
You are not writing EXEC statement correctly.
use EXEC (#Query) instead of EXEC #Query
This is not a direct answer to your question, but something I can recomend reading through as I've seend these solutions create problems in systems before.
I would suggest not using the solution using the WHILE loop. While loops constantly create performance problems as SQL Server doesn't have the same ability to optimize loops as for example C# engines does.
Not sure why you are using dynamic SQL either. If you want to make sure that there is no SQL injection please use sp_executesql instead as it checks for harmful code but i would avoid using dynamic sql except when we don't know the structure of our underlying data (hence need for dynamic sql).
Just wrote a function for doing a string split using cte to keep perfomance up.
CREATE SCHEMA Util;
GO
CREATE FUNCTION Util.String_Split (
#Text varchar(MAX),
#SplitChar char
)
RETURNS TABLE
AS
RETURN(
WITH cte AS
(
SELECT
1 AS [RowNumber],
X.Text,
X.RemainingText
FROM
(
SELECT
SUBSTRING(#Text, 1, CHARINDEX(';', #Text) - 1) AS [Text],
SUBSTRING(#Text, CHARINDEX(';', #Text) + 1, LEN(#Text) - CHARINDEX(';', #Text)) AS [RemainingText]
) X
UNION ALL
SELECT
cte.RowNumber + 1,
X.Text,
X.RemainingText
FROM
cte
CROSS APPLY (
SELECT
SUBSTRING(cte.RemainingText, 1, ISNULL(NULLIF(CHARINDEX(';', cte.RemainingText) - 1, -1), LEN(cte.RemainingText))) AS [Text],
CASE
WHEN CHARINDEX(';', cte.RemainingText) = 0 THEN
''
ELSE
SUBSTRING(cte.RemainingText, CHARINDEX(';', cte.RemainingText) + 1, LEN(cte.RemainingText))
END AS [RemainingText]
) X
WHERE
X.Text ''
)
SELECT
cte.Text
FROM
cte
WHERE
cte.Text IS NOT NULL
);
You could then call make your query without the dynamic part using this call:
SELECT
*
FROM
Country sp
WHERE
#Countries IS NULL
OR
sp.CountryId IN (
SELECT * FROM Util.String_Split(#Countries, ',')
)
Using MS SQL 2012.
I am trying to query a set of tables using dynamic SQL and I am passing through the tablename, a start date in the format of dd/MM/yyyy and an end date in the format of dd/MM/yyyy.
My Code is as follows.
#tableName nvarchar(50),
#startDate date,
#endDate date
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Query varchar(max)
SET #Query = 'Select * from ''' + #tableName + ''' where Convert(date, Docdate, 103) >= ''' + CAST(#startDate AS VARCHAR(50)) + ''' and Convert(date, Docdate, 103) <= ''' + CAST(#endDate AS VARCHAR(50)) + ''
EXEC #Query
END
The Docdate field has a data type of Date and is in the format of yyyy-MM-dd.
I get the following error when I run this stored procedure.
Incorrect syntax near '/'.
What am I missing?
UPDATE
I am testing the query with the following entries for the variables and I still get the same error.
USE [TestDbs]
GO
DECLARE #return_value int
EXEC #return_value = [dbo].[getResultSet]
#tableName = Prices,
#startDate = 01/02/2016,
#endDate = 20/02/2016
SELECT 'Return Value' = #return_value
GO
You should use sp_executesql and pass proper date parameters to your query, and avoid the need for casting dates to strings at all:
CREATE PROCEDURE dbo.YourProcedure
#TableName SYSNAME,
#StartDate DATE,
#EndDate DATE
AS
BEGIN
DECLARE #Query NVARCHAR(MAX)
SET #Query = 'SELECT * FROM ' + QUOTENAME(#TableName) +
' WHERE Docdate >= #StartDateParam
AND Docdate <= #EndDateParam';
EXECUTE sp_executesql
#Query,
N'#startDateParam DATE, #endDateParam DATE',
#StartDateParam = #StartDate,
#EndDateParam = #EndDate;
END
I have made a couple of other minor tweaks too:
Change the datatype of #TableName from NVARCHAR(50) to SYSNAME (Synonym for `NVARCHAR(128), the maximum length of an object name)
Change the data type of #Query to NVARCHAR(MAX) since this is the type sp_executesql expects.
Wrapped #TableName with QUOTENAME to ensure any special characters do not cause an error.
If DocDate is already a date, then the explicit convert is not necessary so I have removed this.
ADDENDUM
You may also wish to add some validation to the table name:
-- CHECK TABLE NAME IS A VALID TABLE OF VIEW
IF ISNULL(OBJECT_ID(#TableName, 'U'), OBJECT_ID(#TableName, 'V')) IS NULL
BEGIN
-- HANDLE INVALID NAME
RETURN;
END
EDIT
I have changed the procedure slightly to cater for sending through a schema qualified table name
CREATE PROCEDURE dbo.getResultSet
#TableName SYSNAME,
#StartDate DATE,
#EndDate DATE
AS
BEGIN
DECLARE #Query NVARCHAR(MAX)
SET #Query = 'SELECT * FROM ' +
QUOTENAME(OBJECT_SCHEMA_NAME(OBJECT_ID(#TableName))) + '.' +
QUOTENAME(OBJECT_NAME(OBJECT_ID(#TableName))) +
' WHERE Docdate >= #StartDateParam
AND Docdate <= #EndDateParam';
EXECUTE sp_executesql
#Query,
N'#startDateParam DATE, #endDateParam DATE',
#StartDateParam = #StartDate,
#EndDateParam = #EndDate;
END
Then you would use this to execute it:
SET DATEFORMAT DMY;
DECLARE #return_value int
EXEC #return_value = [dbo].[getResultSet]
#tableName = 'dbo.Prices',
#startDate = '01/02/2016',
#endDate = '20/02/2016'
SELECT 'Return Value' = #return_value;
Or better still use a culture insensitive date format (yyyyMMdd) for your literals:
DECLARE #return_value int
EXEC #return_value = [dbo].[getResultSet]
#tableName = 'dbo.Prices',
#startDate = '20160201',
#endDate = '20160220'
SELECT 'Return Value' = #return_value;
I am trying to search several tables for a list of phones.
The problem is converting the single string into a valid comma delimited string to use in conjunction with the IN clause.
I tried using replace to fix the problem.
DECLARE #PhoneNumber VARCHAR(3000)
SET #PhoneNumber = '6725556666,2124444444'
SET #PhoneNumber = '''' + #PhoneNumber + ''''
SELECT #PhoneNumber
'6725556666','2124444444'
Finally the sample SQL does not recognize the string as expected:
SELECT Provider
,PhoneNumber
,ChangeType
,ChangeDate
FROM dbo.PhoneLog
WHERE PhoneNumber IN (#PhoneNumber)
There are several ways to handle this. One option is to use dynamic sql and inject your phone number string into a variable containing the statement and then executing that like this:
DECLARE #PhoneNumber VARCHAR(3000)
SET #PhoneNumber = '6725556666,2124444444'
DECLARE #SQL NVARCHAR(max)
SET #SQL = N'
SELECT Provider, PhoneNumber, ChangeType, ChangeDate
FROM dbo.PhoneLog
WHERE PhoneNumber IN (' + #PhoneNumber + ')'
EXEC sp_executesql #SQL
Please note that this approach can be vulnerable to SQL injection attacks, for instance feeding a string like
SET #PhoneNumber = '1);truncate table phonelog;--'
would effectively empty the table. So using a dynamic SQL approach like above should only be an option if it's certain that the string fed that in injected is sanitized and safe (or maybe it should never be used).
Another, possibly better, option is to use a user defined function to split the phonenumber variable and use that like this:
SELECT Provider, PhoneNumber, ChangeType, ChangeDate
FROM dbo.PhoneLog
WHERE PhoneNumber IN (
SELECT splitdata FROM dbo.fnSplitString(#PhoneNumber,',')
-- you could add a check here that the data returned from the function
-- is indeed numeric and valid
-- WHERE ISNUMERIC(splitdata) = 1
)
Here's the function used in the example:
CREATE FUNCTION [dbo].[fnSplitString]
(
#string NVARCHAR(MAX),
#delimiter CHAR(1)
)
RETURNS #output TABLE(splitdata NVARCHAR(MAX)
)
BEGIN
DECLARE #start INT, #end INT
SELECT #start = 1, #end = CHARINDEX(#delimiter, #string)
WHILE #start < LEN(#string) + 1 BEGIN
IF #end = 0
SET #end = LEN(#string) + 1
INSERT INTO #output (splitdata)
VALUES(SUBSTRING(#string, #start, #end - #start))
SET #start = #end + 1
SET #end = CHARINDEX(#delimiter, #string, #start)
END
RETURN
END
I did not write the function, I think I got it somewhere on the internet...
I need to accomplish the following in the stored procedure:
Pass parameterized column names.
Select the parameterized column names and provide total group by selected columns.
Code:
CREATE PROCEDURE sproc (
#column1 NVARCHAR(MAX),
#column2 NVARCHAR(MAX),
#startdate DATE,
#enddate DATE ) AS
BEGIN
DECLARE #sqlquery NVARCHAR(MAX) = 'SELECT #column1, #column2, SUM(amountcolumn)
FROM tablename
WHERE column3 = ''#value3'',
datecolumn BETWEEN ''#startdate'' AND ''#enddate''
GROUP BY #column1, #column2';
DECLARE #params NVARCHAR(MAX) = '#column1 VARCHAR(MAX),
#column2 VARCHAR(MAX),
#startdate DATE,
#enddate DATE';
EXEC sp_sqlexec #sqlquery, #params,
#column1 = #column1,
#column2 = #column2,
#startdate = #startdate,
#enddate = #enddate;
END
GO
Assuming #value3 is a string and is another parameter to the stored procedure, that datecolumn is in fact date, and ignoring the fact that I have no idea how you can have a schema where the grouping fields can be random like this (which you ignored in other recent questions here):
DECLARE #sql nvarchar(max) = N'SELECT '
+ QUOTENAME(#column1) + ', '
+ QUOTENAME(#column2) + ', SUM(amountcolumn)
FROM dbo.tablename
WHERE column3 = #value3
AND datecolumn BETWEEN #startdate AND #enddate
GROUP BY ' + QUOTENAME(#column1)
+ ', ' + QUOTENAME(#column2) + ';';
EXEC sys.sp_executesql #sql,
N'#value3 varchar(255), #startdate date, #enddate date',
#value3, #startdate, #enddate;
-- strongly recommend against sp_sqlexec
-- it is undocumented and unsupported
This also assumes you don't care about order (you probably do and will want to add ORDER BY as well as GROUP BY).
For more info on dynamic SQL and even further ways to protect yourself from user input:
sqlblog.org/dynamic-sql
What is wrong with this statement?
ALTER Function [InvestmentReturn].[getSecurityMinLowForPeriod](#securityid int,
#start datetime,
#end datetime)
returns xml
begin
declare #results varchar(500)
declare #low int
declare #adjustedLow int
declare #day varchar(10)
if #end is null
begin
set #end = getdate()
end
set #adjustedLow = (select min(adjLow)
from (
select Low * [InvestmentReturn].[fn_getCorporateActionSplitFactor](isq.securityid, #start, convert(varchar,day, 111)) as adjLow
from
securityquote isq
where isq.securityid = #securityid and isq.day >= convert(varchar(10), #start, 111) and convert(varchar(10), #end, 111) >= isq.day
and low != -1
) as x)
select
top 1 #low = low
, #day = day
, #adjustedLow
--select high
from
securityquote sq
where
day >= convert(varchar(10), #start, 111) and convert(varchar(10), #end, 111) >= day
and securityid = #securityid and low != -1
order by low asc
set #results= '<results type="debug_min">'
set #results = #results + '<periodStart>' + coalesce(cast(#start as varchar(20)), 'NULL') + '</periodStart>'
set #results = #results + '<periodEnd>' + coalesce(cast(#end as varchar(20)), 'NULL') + '</periodEnd>'
set #results = #results + '<securityID>' + coalesce(cast(#securityID as varchar(10)), 'NULL') + '</securityID>'
set #results = #results + '<periodMin>' + coalesce(cast(#low as varchar(10)), '-11111') + '</periodMin>'
set #results = #results + '<coraxAdjustedPeriodMin>' + coalesce(cast(#adjustedLow as varchar(10)), '-11111') + '</coraxAdjustedPeriodMin>'
set #results = #results + '<dayMinOcurred>' + coalesce(#day, 'NULL') + '</dayMinOcurred>'
set #results = #results + '</results>'
return #results
Just to explain the answer (after getting where the error was caused), I simply removed #adjustedLow from the second select statement.
Column values from the SELECT statement are assigned into #low and #day local variables; the #adjustedLow value is not assigned into any variable and it causes the problem:
The problem is here:
select
top 1 #low = low
, #day = day
, #adjustedLow -- causes error!
--select high
from
securityquote sq
...
Detailed explanation and workaround: SQL Server Error Messages - Msg 141 - A SELECT statement that assigns a value to a variable must not be combined with data-retrieval operations.
You cannot use a select statement that assigns values to variables to also return data to the user
The below code will work fine, because i have declared 1 local variable and that variable is used in select statement.
Begin
DECLARE #name nvarchar(max)
select #name=PolicyHolderName from Table
select #name
END
The below code will throw error "A SELECT statement that assigns a value to a variable
must not be combined with data-retrieval operations" Because we are retriving data(PolicyHolderAddress) from table, but error says data-retrieval operation is not allowed when you use some local variable as part of select statement.
Begin
DECLARE #name nvarchar(max)
select
#name = PolicyHolderName,
PolicyHolderAddress
from Table
END
The the above code can be corrected like below,
Begin
DECLARE #name nvarchar(max)
DECLARE #address varchar(100)
select
#name = PolicyHolderName,
#address = PolicyHolderAddress
from Table
END
So either remove the data-retrieval operation or add extra local variable. This will resolve the error.
declare #cur cursor
declare #idx int
declare #Approval_No varchar(50)
declare #ReqNo varchar(100)
declare #M_Id varchar(100)
declare #Mail_ID varchar(100)
declare #temp table
(
val varchar(100)
)
declare #temp2 table
(
appno varchar(100),
mailid varchar(100),
userod varchar(100)
)
declare #slice varchar(8000)
declare #String varchar(100)
--set #String = '1200096,1200095,1200094,1200093,1200092,1200092'
set #String = '20131'
select #idx = 1
if len(#String)<1 or #String is null return
while #idx!= 0
begin
set #idx = charindex(',',#String)
if #idx!=0
set #slice = left(#String,#idx - 1)
else
set #slice = #String
--select #slice
insert into #temp values(#slice)
set #String = right(#String,len(#String) - #idx)
if len(#String) = 0 break
end
-- select distinct(val) from #temp
SET #cur = CURSOR FOR select distinct(val) from #temp
--open cursor
OPEN #cur
--fetchng id into variable
FETCH NEXT
FROM #cur into #Approval_No
--
--loop still the end
while ##FETCH_STATUS = 0
BEGIN
select distinct(Approval_Sr_No) as asd, #ReqNo=Approval_Sr_No,#M_Id=AM_ID,#Mail_ID=Mail_ID from WFMS_PRAO,WFMS_USERMASTER where WFMS_PRAO.AM_ID=WFMS_USERMASTER.User_ID
and Approval_Sr_No=#Approval_No
insert into #temp2 values(#ReqNo,#M_Id,#Mail_ID)
FETCH NEXT
FROM #cur into #Approval_No
end
--close cursor
CLOSE #cur
select * from #tem