Construct Dynamic Complex Where Clause in Stored Procedure - sql

I need to write a stored procedure that uses different where-clauses based on given input parameters. Specifically I need evaluate two date-time parameters, and based on these parameters, I need to construct different where-clauses. So far I am having problems making it work.
The stored procedures takes start-date and end-date parameters as given by users, and use these two parameters to find records that have their start-date and end-date overlapping these user-input parameters.
Below is the pseudo-code of the stored procedure I want to create:
CREATE PROCEDURE sp_MyReport #organisation nvarchar(255) = NULL, #start_date nvarchar(255) = NULL, #end_date nvarchar(255) = NULL
AS
DECLARE #dateTimeOverlapWhereClause nvarchar(1000)
-- Construct the appropriate where-condition based on the user input
if #start_date is not null and #end_date is not null
#dateTimeOverlapWhereClause = (cast(start_date as datetime) <= prj.PlanEnd) and (cast(end_date as datetime) >= prj.PlanStart)
else if #start_date is not null and #end_date is null
#dateTimeOverlapWhereClause = (prj.PlanStart >= cast(#start_date as datetime))
else if #start_date is null and #end_date is not null
#dateTimeOverlapWhereClause = (prj.PlanEnd <= cast(#end_date as datetime))
else -- both input dates are null so we use open-ended dates instead
#dateTimeOverlapWhereClause = ((cast('1900-01-01' as datetime) <= prj.PlanEnd) and (cast('9999-12-31' as datetime) >= prj.PlanStart))
-- This is the query that need to use the dynamic where condition
select
prj.SrcGISID,
prj.PlanStart,
prj.PlanEnd,
prj.WorksClass,
prj.Description,
prj.JobStatus,
prj.Stage
from PROJECT prj
inner join ORGANISATION org on prj.OrgID = org.ID and org.OrganisationName = #organisation
and #dateTimeOverlapWhereClause
go
How do I make the #dateTimeOverlapWhereClause dynamically constructed? I have done some research but most of the answers are based on simple variables. SQL-injection is not so much a concern here as this stored procedure is used internally by organisation. I am using MS SQL Server 2012.
Thanks in advance.

Related

Compare datetime value in stored procedure

I am having trouble writing SQL Server queries/procedures with DateTime format in the tables.
My application runs on a standard ASP.NET MVC4 stack with SQL Server.
My table Bookings has this structure:
CREATE TABLE [dbo].[Bookings]
(
[BookingId] INT IDENTITY (1, 1) NOT NULL,
[ShowId] INT NOT NULL,
[RowId] INT NULL,
[Username] VARCHAR(100) NULL,
[PaymentId] INT NULL,
[ShowDate] DATETIME NULL,
.....
....
);
I have written two stored procedures where I am trying to compare table column ShowDate with different date parameters declared in stored procedure.
Procedure #1:
CREATE PROCEDURE [dbo].[GetBookingsByDate]
#venueid int,
#fromdate datetime,
#todate datetime
AS
BEGIN
SELECT
City, Title, ScreenTitle, ShowDate,
SUM([Qty]) AS Quantity,
SUM([Charges]) AS TotalAmount,
SUM([OtherCharges]) AS OtherCharges
FROM
ShowBookings
WHERE
Venueid = #venueid
AND ShowDate BETWEEN #fromdate AND #todate
GROUP BY
ScreenId, ShowDate, Venueid, Title, ScreenTitle, City
END
Procedure #2:
CREATE PROCEDURE [dbo].[GetAudienceReportsHistory]
#state varchar,
#city varchar,
#theaterName varchar,
#showdate datetime
AS
BEGIN
SELECT
b.BookingId, b.MobileNo, b.SeatNumbers, b.EmailId,
sc.ScreenTitle, sh.ShowTime, a.Title,
b.Username, b.SMSStatus
FROM
Bookings b
JOIN
Shows sh ON b.ShowId = sh.Id
JOIN
Venues AS v ON sh.Venue_Id = v.Id
JOIN
Artifacts a ON sh.Artifact_Id = a.Id
JOIN
Screens AS sc ON sh.Screen_ScreenId = sc.ScreenId
WHERE
b.ShowDate = #showdate
AND b.IsBooked = 'true'
AND b.TimeSolt = '0'
AND v.Title = #theaterName
AND v.City = #city
END
As you can see procedure #1 takes two datetime parameters, fromdate and todate. The second procedure takes only one datetime parameter showdate.
Procedure #1 returns the correct set of results, however procedure #2 returns no results at all. But I have crosschecked in the tables that I have proper data which should be returned for the Proc2 query. There seems to be some DateTime format mismatch.
I'm sending datetime parameters to the queries in "yyyy-mm-dd" format (eg: 2017-05-30). Inside the table the ShowDate column is stored in "dd-mm-yyyy" (eg: 30-05-2017) format.
I have tried sending the parameter in different date formats but I'm not getting any results for Proc2. Kindly help me in solving this. Thanks in advance. Let me know if you need more info.
you have to note that datetime includes time so when you equate that to a datetime field it will never be equal due to time difference... what you can do is cast both dates... meanwhile between captures time within the date
cast(showdate as date) = cast(#showdate as date)
or DateDIFF
datediff(day,#showdate,showdate) = 0
You Need to convert Date Proper Formate like this
CONVERT(date, b.ShowDate) = CONVERT(date,#showdate )

How can you get the number of parameters

How can i get the number of parameters that weree given to a MS SQL function or stored procedure?
Lets say mu function is:
CREATE FUNCTION dbo.tst
(
#idINT ,
#StartDate DATETIME ,
#EndDate DATETIME
)
...
When i call the function using SELECT dbo.tst(1, '2015-11-11 23:14:45') is there a way to determine that the function was called with only two parameters?
Edit:
Idealy I would like to have a funtion/sp that can coop with an unknow number of parameters, but as far as I know this is not possible.
The idea was to create a funtion with lets say 20 params and discover (count) the number of params passed into the function, so we can process only the values that were actually given to the funtion.
The bigger picture is implementing a hash funtion voor Data Vault 2.0 to create a hash value per record to discover changes. Since every table has a different number of fields, this can be chalanging. You do not want to create a funtion per table...
I hope this makes sense.
First of all with function like:
CREATE FUNCTION dbo.tst(
#idINT ,
#StartDate DATETIME ,
#EndDate DATETIME)
SELECT dbo.tst(1, '2015-11-11 23:14:45')
You will get:
error An insufficient number of arguments were supplied for the
procedure or function dbo.tst.
One way is to add default parameter value like:
CREATE FUNCTION dbo.tst(
#id INT,
#StartDate DATETIME = NULL,
#EndDate DATETIME = NULL)
but still you need to call it as:
SELECT dbo.tst(1, '2015-11-11 23:14:45', default)
with scalar function you can call it as:
EXECUTE dbo.tst 1, '2015-11-11 23:14:45'
I guess you want something like overloaded function in other programming languages. With functions you always need to specify all parameters.
With stored procedures you can use:
CREATE PROCEDURE dbo.tst(
#idINT ,
#StartDate DATETIME = NULL,
#EndDate DATETIME = NULL)
AS
BEGIN
IF #StartDate IS NULL
-- user does not provide start date use default or do some operations
-- SET #StartDate = ...
...
IF #EndDate IS NULL
...
END;
Then you can reason about it:
EXEC dbo.tst 1, NULL, NULL
will be the same as:
EXEC dbo.tst 1
More info: CREATE FUNCTION:
When a parameter of the function has a default value, the keyword
DEFAULT must be specified when the function is called to retrieve the
default value. This behavior is different from using parameters with
default values in stored procedures in which omitting the parameter
also implies the default value. However, the DEFAULT keyword is not
required when invoking a scalar function by using the EXECUTE
statement.

How do I properly SELECT WHERE Effective_Date >= 'Given_Date' in a stored procedure?

I have this select statement that returns the results I'm looking for:
SELECT *
FROM Database.dbo.Table
WHERE Effective_Date >= '04/01/2014'
AND Chain = 'MCD'
I'm looking to turn this into a stored procedure with the following variables, #EffectiveDate and #Chain so that I can simply replace the date and chain to get different results. Here is the stored procedure I've made that doesn't work correctly:
CREATE PROCEDURE Database.dbo.StoredProc
#Chain VARCHAR(255),
#EffectiveDate VARCHAR(255)
AS
SELECT *
FROM Database.dbo.Table
WHERE Effective_Date >= '+#EffectiveDate+'
AND Chain = '+#Chain+'
GO
I'd like to execute this stored procedure like this:
EXEC Database.dbo.StoredProc
#PharmacyChain = N'MCD',
#EffectiveDate = N'04/01/2014'
;
GO
In this example, Table.Effective_Date is in datetime format. When I run the SELECT statement w/o the stored proc, the date comparison works fine to only select records with effective date after '04/01/2014'. However, when it's run using the variables int he stored proc, it doesn't convert the date correctly to compare. I've tried changing the EffectiveDate variable to datetime format, but still had no luck. Any help would be greatly appreciated. Thank you
Parameters should match the column datatype
#Chain VARCHAR(255) -- what is Chain?
#EffectiveDate datetime -- or date etc
And simply do this
SELECT *
FROM dbo.Table
WHERE Effective_Date >= #EffectiveDate
AND Chain = #Chain;
You don't need 3 part object names either

How to pass multiple values to single parameter in stored procedure

I'm using SSRS for reporting and executing a stored procedure to generate the data for my reports
DECLARE #return_value int
EXEC #return_value = [dbo].[MYREPORT]
#ComparePeriod = 'Daily',
#OverrideCompareDate = NULL,
#PortfolioId = '5,6',
#OverrideStartDate = NULL,
#NewPositionsOnly = NULL,
#SourceID = 13
SELECT 'Return Value' = #return_value
GO
In the above when I passed #PortfolioId = '5,6' it is giving me wrong inputs
I need all records for portfolio id 5 and 6 also is this correct way to send the multiple values ?
When I execute my reports only giving #PortfolioId = '5' it is giving me 120 records
and when I execute it by giving #PortfolioId = '6' it is giving me 70 records
So when I will give #PortfolioId = '5,6' it should have to give me only 190 records altogether, but it is giving me more no of records I don't understand where I exactly go wrong .
Could anyone help me?
thanks
all code is too huge to paste , i'm pasting relevant code please suggest clue.
CREATE PROCEDURE [dbo].[GENERATE_REPORT]
(
#ComparePeriod VARCHAR(10),
#OverrideCompareDate DATETIME,
#PortfolioId VARCHAR(50) = '2', --this must be multiple
#OverrideStartDate DATETIME = NULL,
#NewPositionsOnly BIT = 0,
#SourceID INT = NULL
) AS
BEGIN
SELECT
Position.Date,
Position.SecurityId,
Position.Level1Industry,
Position.MoodyFacilityRating,
Position.SPFacilityRating,
Position.CompositeFacilityRating,
Position.SecurityType,
Position.FacilityType,
Position.Position
FROM
Fireball_Reporting.dbo.Reporting_DailyNAV_Pricing POSITION WITH (NOLOCK, READUNCOMMITTED)
LEFT JOIN Fireball.dbo.AdditionalSecurityPrice ClosingPrice WITH (NOLOCK, READUNCOMMITTED) ON
ClosingPrice.SecurityID = Position.PricingSecurityID AND
ClosingPrice.Date = Position.Date AND
ClosingPrice.SecurityPriceSourceID = #SourceID AND
ClosingPrice.PortfolioID IN (
SELECT
PARAM
FROM
Fireball_Reporting.dbo.ParseMultiValuedParameter(#PortfolioId, ',') )
This can not be done easily. There's no way to make an NVARCHAR parameter take "more than one value". What I've done before is - as you do already - make the parameter value like a list with comma-separated values. Then, split this string up into its parts in the stored procedure.
Splitting up can be done using string functions. Add every part to a temporary table. Pseudo-code for this could be:
CREATE TABLE #TempTable (ID INT)
WHILE LEN(#PortfolioID) > 0
BEGIN
IF NOT <#PortfolioID contains Comma>
BEGIN
INSERT INTO #TempTable VALUES CAST(#PortfolioID as INT)
SET #PortfolioID = ''
END ELSE
BEGIN
INSERT INTO #Temptable VALUES CAST(<Part until next comma> AS INT)
SET #PortfolioID = <Everything after the next comma>
END
END
Then, change your condition to
WHERE PortfolioId IN (SELECT ID FROM #TempTable)
EDIT
You may be interested in the documentation for multi value parameters in SSRS, which states:
You can define a multivalue parameter for any report parameter that
you create. However, if you want to pass multiple parameter values
back to a data source by using the query, the following requirements
must be satisfied:
The data source must be SQL Server, Oracle, Analysis Services, SAP BI
NetWeaver, or Hyperion Essbase.
The data source cannot be a stored procedure. Reporting Services does
not support passing a multivalue parameter array to a stored
procedure.
The query must use an IN clause to specify the parameter.
This I found here.
I spent time finding a proper way. This may be useful for others.
Create a UDF and refer in the query -
http://www.geekzilla.co.uk/view5C09B52C-4600-4B66-9DD7-DCE840D64CBD.htm
USE THIS
I have had this exact issue for almost 2 weeks, extremely frustrating but I FINALLY found this site and it was a clear walk-through of what to do.
http://blog.summitcloud.com/2010/01/multivalue-parameters-with-stored-procedures-in-ssrs-sql/
I hope this helps people because it was exactly what I was looking for
Either use a User Defined Table
Or you can use CSV by defining your own CSV function as per This Post.
I'd probably recommend the second method, as your stored proc is already written in the correct format and you'll find it handy later on if you need to do this down the road.
Cheers!
I think, below procedure help you to what you are looking for.
CREATE PROCEDURE [dbo].[FindEmployeeRecord]
#EmployeeID nvarchar(Max)
AS
BEGIN
DECLARE #sqLQuery VARCHAR(MAX)
Declare #AnswersTempTable Table
(
EmpId int,
EmployeeName nvarchar (250),
EmployeeAddress nvarchar (250),
PostalCode nvarchar (50),
TelephoneNo nvarchar (50),
Email nvarchar (250),
status nvarchar (50),
Sex nvarchar (50)
)
Set #sqlQuery =
'select e.EmpId,e.EmployeeName,e.Email,e.Sex,ed.EmployeeAddress,ed.PostalCode,ed.TelephoneNo,ed.status
from Employee e
join EmployeeDetail ed on e.Empid = ed.iEmpID
where Convert(nvarchar(Max),e.EmpId) in ('+#EmployeeId+')
order by EmpId'
Insert into #AnswersTempTable
exec (#sqlQuery)
select * from #AnswersTempTable
END

Stored Procedure with optional "WHERE" parameters

I have a form where users can specify various parameters to dig through some data (status, date etc.).
I can produce a query that is:
SELECT * FROM table WHERE:
status_id = 3
date = <some date>
other_parameter = <value>
etc. Each WHERE is optional (I can select all the rows with status = 3, or all the rows with date = 10/10/1980, or all the rows with status = 3 AND date = 10/10/1980 etc.).
Given a large number of parameters, all optional, what is the best way to make up a dynamic stored procedure?
I'm working on various DB, such as:
MySQL, Oracle and SQLServer.
One of the easiest ways to accomplish this:
SELECT * FROM table
WHERE ((#status_id is null) or (status_id = #status_id))
and ((#date is null) or ([date] = #date))
and ((#other_parameter is null) or (other_parameter = #other_parameter))
etc.
This completely eliminates dynamic sql and allows you to search on one or more fields. By eliminating dynamic sql you remove yet another security concern regarding sql injection.
Create your procedure like this:
CREATE PROCEDURE [dbo].[spXXX]
#fromDate datetime = null,
#toDate datetime = null,
#subCode int = null
as
begin
set NOCOUNT ON
/* NOCOUNT limits the server feedback on select results record count */
SELECT
fields...
FROM
source
WHERE
1=1
--Dynamic where clause for various parameters which may or may not be passed in.
and ( #fromDate is null or [dateField] >= #fromDate)
and ( #toDate is null or [dateField] <= #toDate)
and ( #subCode is null or subCode= #leaveTypeSubCode)
order by fields...
This will allow you to execute the procedure with 0 params, all params, or any # of params.
This is the style I use:
t-sql
SELECT *
FROM table
WHERE
status_id = isnull(#status_id ,status_id)
and date = isnull(#date ,date )
and other_parameter = isnull(#other_parameter,other_parameter)
oracle
SELECT *
FROM table
WHERE
status_id = nval(p_status_id ,status_id)
and date = nval(p_date ,date )
and other_parameter = nval(p_other_parameter,other_parameter)
A readable and maintainable way to do it (even usable with JOIN/APPLY) :
where
(#parameter1 IS NULL OR your_condition1)
and (#parameter2 IS NULL OR your_condition2)
-- etc
However it's a bad idea on most big tables (even more using JOIN/APPLY), since your execution plan will not ignore NULL values and generates massive performance loophole (ex : scaning all a table searching for NULL values).
A roundabout way in SQL Server is to use WITH(RECOMPILE) options in your query (available since SQL 2008 SP1 CU5 (10.0.2746)).
The best way to implements this (performance wise) is to use IF ... ELSE block, one for each combination possible. Maybe it's exhausting but you will have the best performances and it doesn't matter your database settings.
If you need more details, you can look for KM. answer here.
You can do something like
WHERE
(
ParameterA == 4 OR ParameterA IS NULL
)
AND
(
ParameterB == 12 OR ParameterB IS NULL
)
If you want to avoid dynamically building up SQL strings (which is often best avoided), you can do this in stored procs by comparing each critera in your where claused with a default value, which equates to "ignore". E.g.:
select * from Table where
(#Col1 IS NULL OR Col1 = #Col1) /*If you don't want to filter in #col, pass in NULL*/
AND
(#Col2 IS NULL OR Col2 = #Col2)