Parameterized WHERE clause in SQL Server Stored Procedure - sql

I have a Stored Procedure to get the details of Invoices
Some occasions I get the list of invoices by sending only the InvoiceID
But in some other occasions I need to get the list of invoices as per the search fields supplied by the user. To do this I send all the fields to the Stored Procedure and use those parameters as below. I included only 2 columns but there are more.
SELECT * FROM INVOICES I
WHERE
(#InvoiceNumber is null or I.InvoiceNumber = #InvoiceNumber)
and
(#PONo is null or I.PONo = #PONo)
Is there a way to send the condition for the WHERE clause as one parameter?

Yes, it is possible with Dynamic SQL, but I highly discourage to do that.
SELECT * FROM tbl WHERE #condition:
If you are considering to write the procedure
CREATE PROCEDURE search_sp #condition varchar(8000) AS
SELECT * FROM tbl WHERE #condition
Just forget it. If you are doing this, you have not completed the transition to use stored procedure and you are still assembling your
SQL code in the client.
It will also open your application to SQL Injection attacks.

You can use custom type to pass table as parameter https://msdn.microsoft.com/pl-pl/library/bb510489(v=sql.110).aspx or you can use default parameters

If you're using SQL Server 2016 or similar (check by calling select compatibility_level, name from sys.databases and seeing that your DB is 130 or higher) then you can use the string_split builtin function.
I found it works best like this (spread out for clarity)
CREATE PROCEDURE [dbo].[GetInvoices]
#InvoiceNumber int = NULL
#PONo nvarchar(1024) = NULL
AS
SELECT * from [Invoices] AS [i]
WHERE
i.InvoiceNumber = ISNULL(#InvoiceNunber, i.InvoiceNunber)
AND CASE
WHEN #PONo is null
THEN 1
ELSE (CASE
WHEN i.PONo IN (select value from string_split(#PONo, ','))
THEN 1
ELSE 0
END)
END
= 1
So if you pass in a null to either parameter it gets translated as where x = x which is always true, and if you pass in a CSV value, it selects it from a split table of values that, if present, results in the where clause being where 1=1, which is true or 0=1 if the value is not present in the input list.
So here you can pass in an invoice number, or PO number, or both, or neither and it should return what you expect.

Related

case statement with wildcard in where clause

I have a large dataset that is being returned from a view in SQL Server 2012. I need to filter the data based on 6 different criteria. I have set up parameters in the report and want to use the parameters in the query to reduce the size.
So I have the following situation:
select * from vw_ABC where p1 in #p1 and p2 in #p2 ...
The tricky part is how to handle a situation where the user does not want to filter on one of the criteria. I am trying to avoid dynamic sql but if that is the only way so be it.
When the report is called the user will have all 6 parameters listed. The default values will be 'ALL'. They can create the report with one or all of the parameters. Most cases it will be 2 or 3 (name city zip orderid things like this)
Any help would be greatly appreciated.
I suppose you are not talking about IN but rather about =. That would be:
select *
from vw_ABC
where (p1 = #p1 or #p1 = 'ALL')
and (p2 = #p2 or #p2 = 'ALL')
...
Set up the parameters as optional and check NULLs....let me dig up an example for you
declare #id int = 1
select * from users u
where (#id IS NULL and u.id=u.id) or (u.id=#id)
One approach is to create a second set of parameters to indicate whether or not the corresponding parameter needs to be used:
SELECT *
FROM vw_ABC
WHERE
(#u1=0 OR p1=#p1)
AND (#u2=0 OR p2=#p2)
AND (#u3=0 OR p3=#p3)
This way you do not need to reserve a special value to mean all: if you want to use #pX, set #uX to 1, where X is parameter number.

SELECT Query selecting values based on a value in another table

I have 2 tables
Account(AccountId, Encoding)
DeviceAccountMap(AccountId, DeviceId)
Now I need to fetch the devices from the DeviceAccountMap. I pass a list of AccountId to a stored procedure and while fetching the DeviceId from the DeviceAccountMap table I need to compare the Encoding value for each account with a particular value.
Which is the easy way to do this? I am totally lost.
The select clause in the stored procedure will look something like this:
DECLARE #Accounts [usp].[Array]
and [usp].[Array] is defined as below
CREATE TYPE [usp].[Array] AS TABLE
(
Value VARCHAR(36) NULL
)
SELECT
DeviceId,
AccountEncoding = A.Encoding
FROM
usp.DeviceControllerAccountMap DCAM
INNER JOIN
usp.Account A ON (DCAM.AccountId = A.AccountId)
WHERE
DCAM.AccountId IN (SELECT Value From #AccountIds)
AND DCAM.IsShared = 1
AND AccountEncoding LIKE A.Encoding + '.%'
In other words I need to fetch the encoding value for each account and use that in this where clause.
So you can look up information on Table-Valued Parameters (TVPs) in T-SQL.
Here is an article by Erland Sommarskog.
You can refer to this StackOverflow answer to see an example of C# code calling a stored procedure that uses a TVP. I believe TVPs require SQL Server 2008 or higher.
TVPs, as far as I understand, provide a way to make your own data type in sql server that gets treated as if it was a table. You're doing this when you declare your Array type and then when you use the #AccountIds in your stored procedure's select statement.
CREATE TYPE [usp].[Array] AS TABLE -- maybe choose a more descriptive name than 'Array'
(
Value VARCHAR(36) NULL -- choose a more descriptive name than 'Value'
)
CREATE PROCEDURE [usp].[your_procedure_name]
#AccountIds [usp].[Array] READONLY -- use TVP as a parameter
AS
SELECT …
It is not clear form your question details whether you also mean to have a parameter in the stored procedure for the Encoding. It seems like you're looking for accounts whose Encodings start with a period '.'.
So first, create your type, like you're doing.
Then create your stored procedure.
Then test your stored procedure, something like this:
DECLARE #mylist Array -- make TVP sample data
INSERT #mylist(Value) VALUES(1),(11),(27),(123) -- insert some values
exec your_procedure_name #mylist -- run stored procedure
The following line is completely unnecessary. The JOIN to Account does this filter for you.
DCAM.AccountId IN (SELECT Value From #AccountIds)
Or am I missing something?

Dynamically Select from different DB's based on input to sproc

I'm trying to alter a stored procedure in our DB from a hard-coded select from 1 specific DB to be able to select from any of our DB's based on an id that's passed into the sproc. Here's the stub of what the sproc is doing for us:
ALTER PROCEDURE [dbo].[GetByAdId]
(
#AdId int,
#UserCompanyId int
)
AS
SET NOCOUNT ON
SELECT
[User].[UserId],
UserMappings.IsActive,
IsAccountOwner = ( SELECT Count(*) FROM DB1_SetUp.dbo.ad Adv WHERE Adv.AdID = UserMappings.AdID AND Adv.PrimaryAccountOwnerID = [User].[UserId] )
FROM
[User] INNER JOIN UserMappings ON
(
UserMappings.UserID = [User].UserID
AND UserMappings.AdID = #AdId
AND UserMappings.UserCompanyId = #UserCompanyId
)
Basically the "IsAccountOwner" variable is hardcoded to select from DB1_SetUp every time, but we have a number of SetUp db's for different groups, so like DB2_SetUp, DB3_SetUp, etc. The UserCompanyId variable being passed into the sproc functions like a group Id and can be used to point to the particular SetUp DB I want it to select from, but I'm not sure how to do this. I basically wanted something on the ilk of:
SELECT * FROM (
CASE #UserCompanyId
WHEN 3 THEN 'DB3_SetUp'
WHEN 4 THEN 'DB4_SetUp'
)
Is there a clean way to do this, or will I have to setup this sproc on each group DB and call the specific one over on each DB?
I've done this in the past by dynamically building the SQL I wanted to execute (based on parameters passed in) and then executing the SQL using sp_executesql
see: http://msdn.microsoft.com/en-us/library/ms188001.aspx

How to pass variable as a parameter in Execute SQL Task SSIS?

I have ssis package in that I'm taking values from flat file and insert it into table.
I have taken one Execute SQL Task in that creating one temptable
CREATE TABLE [tempdb].dbo.##temptable
(
date datetime,
companyname nvarchar(50),
price decimal(10,0),
PortfolioId int,
stype nvarchar(50)
)
Insert into [tempdb].dbo.##temptable (date,companyname,price,PortfolioId,stype)
SELECT date,companyname,price,PortfolioId,stype
FROM ProgressNAV
WHERE (Date = '2011-09-30') AND (PortfolioId = 5) AND (stype in ('Index'))
ORDER BY CompanyName
Now in above query I need to pass (Date = '2011-09-30') AND (PortfolioId = 5) AND (stype in ('Index'))
these 3 parameter using variable name I have created variables in package so that I become dynamic.
In your Execute SQL Task, make sure SQLSourceType is set to Direct Input, then your SQL Statement is the name of the stored proc, with questionmarks for each paramter of the proc, like so:
Click the parameter mapping in the left column and add each paramter from your stored proc and map it to your SSIS variable:
Now when this task runs it will pass the SSIS variables to the stored proc.
The EXCEL and OLED DB connection managers use the parameter names 0 and 1.
I was using a oledb connection and wasted couple of hours trying to figure out the reason why the query was not working or taking the parameters. the above explanation helped a lot
Thanks a lot.
Along with #PaulStock's answer, Depending on your connection type, your variable names and SQLStatement/SQLStatementSource Changes
https://learn.microsoft.com/en-us/sql/integration-services/control-flow/execute-sql-task
SELECT, INSERT, UPDATE, and DELETE commands frequently include WHERE clauses to specify filters that define the conditions each row in the source tables must meet to qualify for an SQL command. Parameters provide the filter values in the WHERE clauses.
You can use parameter markers to dynamically provide parameter values. The rules for which parameter markers and parameter names can be used in the SQL statement depend on the type of connection manager that the Execute SQL uses.
The following table lists examples of the SELECT command by connection manager type. The INSERT, UPDATE, and DELETE statements are similar. The examples use SELECT to return products from the Product table in AdventureWorks2012 that have a ProductID greater than and less than the values specified by two parameters.
EXCEL, ODBC, and OLEDB
SELECT* FROM Production.Product WHERE ProductId > ? AND ProductID < ?
ADO
SELECT * FROM Production.Product WHERE ProductId > ? AND ProductID < ?
ADO.NET
SELECT* FROM Production.Product WHERE ProductId > #parmMinProductID
AND ProductID < #parmMaxProductID
The examples would require parameters that have the following names:
The EXCEL and OLED DB connection managers use the parameter names 0 and 1. The ODBC connection type uses 1 and 2.
The ADO connection type could use any two parameter names, such as Param1 and Param2, but the parameters must be mapped by their ordinal position in the parameter list.
The ADO.NET connection type uses the parameter names #parmMinProductID and #parmMaxProductID.
A little late to the party, but this is how I did it for an insert:
DECLARE #ManagerID AS Varchar (25) = 'NA'
DECLARE #ManagerEmail AS Varchar (50) = 'NA'
Declare #RecordCount AS int = 0
SET #ManagerID = ?
SET #ManagerEmail = ?
SET #RecordCount = ?
INSERT INTO...

How would you build one Select stored procedure to handle any request from a table?

I want to build a single select stored procedure for SQL 2005 that is universal for any select query on that table.
**Columns**
LocationServiceID
LocationID
LocationServiceTypeID
ServiceName
ServiceCode
FlagActive
For this table I may need to select by LocationServiceID, or LocationID, or LocationServiceTypeID or ServiceName or a combination of the above.
I'd rather not have a separate stored procedure for each of them.
I assume the best way to do it would be to build the 'WHERE' statement on NOT NULL. Something like
SELECT * FROM LocationServiceType WHERE
IF #LocationID IS NOT NULL (LocationID = #LocationID)
IF #LocationServiceID IS NOT NULL (LocationServiceID = #LocationServiceID)
IF #LocationServiceTypeID IS NOT NULL (LocationServiceTypeID = #LocationServiceTypeID)
IF #ServiceName IS NOT NULL (ServiceName = #ServiceName)
IF #ServiceCode IS NOT NULL (ServiceCode = #ServiceCode)
IF #FlagActive IS NOT NULL (FlagActive = #FlagActive)
Does that make sense?
here is the most extensive article I've ever seen on the subject:
Dynamic Search Conditions in T-SQL by Erland Sommarskog
here is an outline of the article:
Introduction
The Case Study: Searching Orders
The Northgale Database
Dynamic SQL
Introduction
Using sp_executesql
Using the CLR
Using EXEC()
When Caching Is Not Really What You Want
Static SQL
Introduction
x = #x OR #x IS NULL
Using IF statements
Umachandar's Bag of Tricks
Using Temp Tables
x = #x AND #x IS NOT NULL
Handling Complex Conditions
Hybrid Solutions – Using both Static and Dynamic SQL
Using Views
Using Inline Table Functions
Conclusion
Feedback and Acknowledgements
Revision History
First of all, your code will not work. It should look like this:
SELECT * FROM LocationServiceType WHERE
(#LocationID IS NULL OR (LocationID = #LocationID)
... -- all other fields here
This is totally valid and known as 'all-in-one query'. But from a performance point of view this is not a perfect solution as soon as you don't allow SQL Server to select optimal plan. You can see more details here.
Bottom line: if your top priority is 'single SP', then use this approach. In case you care about the performance, look for a different solution.
SELECT *
FROM LocationServiceType
WHERE LocationServiceID = ISNULL(#LocationServiceID,LocationServiceID)
AND LocationID = ISNULL(#LocationID,LocationID)
AND LocationServiceTypeID = ISNULL(#LocationServiceTypeID,LocationServiceTypeID)
AND ServiceName = ISNULL(#ServiceName,ServiceName)
AND ServiceCode = ISNULL(#ServiceCode,ServiceCode)
AND FlagActive = ISNULL(#FlagActive,FlagActive)
If a null value is sent in it will cancel out that line of the where clause, otherwise it will return rows that match the value sent in.
What I've always done is is set the incoming parameters to null if should be ignored in query
then check variable for null first, so if variable is null condition short circuits and filter is not applied. If variable has value then 'or' causes filter to be used. Has worked for me so far.
SET #LocationID = NULLIF(#LocationID, 0)
SET #LocationServiceID = NULLIF(#LocationServiceID, 0)
SET #LocationServiceTypeID = NULLIF(#LocationServiceTypeID, 0)
SELECT * FROM LocationServiceType WHERE
(#LocationID IS NULL OR LocationID = #LocationID)
AND (#LocationServiceID IS NULL OR LocationServiceID = #LocationServiceID)
AND (#LocationServiceTypeID IS NULL OR #LocationServiceTypeID = #LocationServiceTypeID)
etc...