I have a stored procedure with 2 parameters: #Name and #Surname which have to be set by an external source.
I need the stored procedure to return values based on what parameter has been set. It should "ignore" null parameters and only take set parameters into account.
SELECT *
FROM ParticipantNames
WHERE -- The if below should go here...
if(#Name iS NOT NULL) - find the row whose [NAME] column LIKE #Name else skip name comparison.
if(#Surname iS NOT NULL) - find the row whose [SURNAME] column LIKE #Surname else skip surname comparison.
if(#Name AND #Surname is NOT NULL) - find the row whose [NAME] column LIKE #Name AND whose [SURNAME] column LIKE #Surname
The following works if all the parameters have been set but return 0 rows when one parameter has not been set.
;WITH tempSearch AS
(
SELECT *
FROM ParticipantNames f
WHERE ((f.Name LIKE CASE WHEN #Name IS NULL THEN NULL ELSE #Name END)
AND (f.Surname LIKE CASE WHEN #Surname IS NULL THEN NULL ELSE #Surname END)
...
I know it returns 0 rows because of the AND clause, but I don't know how to fix it.
Any advice on how to achieve this?
It seems you are trying to use a 'default' pattern on your queries.
The problem here is that you are projecting NULL out of the CASE WHEN, which then leads to WHERE Column LIKE NULL, which won't work.
What you can do is subtitute the actual column back in, like this:
SELECT *
FROM ParticipantNames f
WHERE ((f.Name LIKE CASE WHEN #Name IS NULL THEN Name ELSE #Name END)
AND (f.Surname LIKE CASE WHEN #Surname IS NULL THEN Surname ELSE #Surname END)
This leads to WHERE Column LIKE Column in the default case, which will work.
Note however that queries like these are hard on the query optimizer, given the conditional use of a predicate. There are several similar discussions on how best to approach the application of 'optional parameters'.
Related
We have a table LogicalTableSharing as follows:
For a specific requirement, we need to take PhysicalCompany of each TableCode into a variable.
We tried a case-based query as follows:
declare #tablecode varchar(50)
declare #inputcompany varchar(50)
declare #query nvarchar(2500)
set #inputcompany= 91
set #query = '
select '''+#inputcompany+''' AS inputcompany,
CASE WHEN lts.TableCode = ''tsctm005'' THEN lts.PhysicalCompany ELSE NULL END as tsctm005_company,
CASE WHEN lts.TableCode = ''tccom000'' THEN lts.PhysicalCompany ELSE NULL END as tccom000_company
from LogicalTableSharing lts
where lts.LogicalCompany = '''+#inputcompany+'''
'
EXEC sp_executesql #query
which obviously gives the result as
The desired output is
What is the right approach?
Try subqueries in a FROM-less SELECT. For performance you want an index on (logicalcompany, tablecode) (or the other way round depending on which is more selective).
SELECT #inputcompany inputcompany,
(SELECT TOP 1
physicalcompany
WHERE logicalcompany = #inputcompany
AND tablecode = 'tsctm005'
ORDER BY <some criteria>) tsctm005_company,
(SELECT TOP 1
physicalcompany
WHERE logicalcompany = #inputcompany
AND tablecode = 'tccom000'
ORDER BY <some criteria>) tccom000_company;
You should find <some criteria> to order by in case of multiple possible rows to decide which one takes precedence. Unless you just want a random one possibly each time you run the query another one, that is.
I have a stored procedure in SQL Server that inserts records for actual expenses into a table. When the procedure is invoked the month in question is specified as part of of a variable. For example:
exec dbo.upsert_actuals_load_01_load_data 4
When the code runs it's supposed to insert the records into the column that corresponds to the month. '1' inserts values into jan_amt, '2' inserts values into feb_amt, etc.
I have written this code:
IF #month = 1
INSERT INTO #actuals_b
([forecast_yr_id]
,[entry_type]
,[unit_cd]
,[proj_nbr]
,[jan_amt]
,[feb_amt]
,[mar_amt]
...])
SELECT forecast_yr_id
, entry_type
, unit_cd
, proj_nbr
, month_amt AS jan_amt
, 0 AS feb_amt
, 0 AS mar_amt
....
FROM #actuals;
It seems inefficient to have to write the INSERT INTO statement for each IF #month = condition. Is there a better way to do this?
To expand on my comment, the correct design of your table should be something along the lines of:
--All data types are complete guesses
CREATE TABLE actuals_b ([forecast_yr_id] int,
[entry_type] varchar(10),
[unit_cd] varchar(10),
[proj_nbr] int,
MonthNum int,
Amount decimal(12,2)
...)
Then, instead of an IF...ELSE or CASE expressions, your INSERT becomes a much simpler:
INSERT INTO actuals_b([forecast_yr_id],[entry_type],[unit_cd],[proj_nbr],MonthNum,Amount,...)
SELECT forecast_yr_id,
entry_type,
unit_cd,
proj_nbr,
#month,
month_amt,
...
FROM actuals;
(Note this is pseudo-SQL in the absence of a full table definition).
I agree with Larnu here... but you could build this out dynamically if you use a global temp table in both cases (or real tables)... something like:
declare #column varchar(64) =
case
when #month = 1 then '[jan_amt]'
when #month = 2 then '[feb_amt]'
...
end
create table ##actuals_b (...your table definition...)
declare #sql varchar(max) = '
INSERT INTO ##actuals_b
([forecast_yr_id]
,[entry_type]
,[unit_cd]
,[proj_nbr]
,' + #column = ') select * from ##actuals'
print(#sql)
This assumes ##actuals only has a single amt column, which seems to be the case based off your static values for the other months.
Looking to write a function where I can call from any two columns in my table, to get the resulting column in the same table.
Table: Acme
Columns: CustomerID, LastName, FirstName, EmailAdress, MailingAddress, City, State, Zip
Can I get EmailAddress from just FirstName and Last Name?
Can I get Zip from CustomerID and City?
What I have so far for EmailAddress:
CREATE FUNCTION fnEmailAddress
(#LastName varchar (255), #FirstName varchar (255))
RETURNS table
RETURN (SELECT EmailAddress
FROM Acme
WHERE FirstName = #FirstName AND LastName = #LastName);
END
So the following would give me brenda.chen#gmail.com:
EXEC fnEmailAddress ('Brenda'+'Chen')
But it doesn't work :(
If I understand you correctly, you want a function that can adapt to several possibilities of input combinations, because what columns you will have ready as input might differ in each function call. If so:
Clearly, your function must have as many parameters as column possibilities you have, i.e. for any column that can possibly become a "column 1" or "column 2", add a parameter.
Since you have many parameters, you need to decide which ones are to be used, or if the inputs are irrational to begin with. For example:
What to do if you have 4 out of 4 parameters provided? Refuse to work? Or can the function adapt to that?
What if parameters 1 and 4 are provided but your function isn't meant to work with that? It's meant to work with 1 + 2 or 3 + 4 for example.
Lastly, choose one of either:
Set any unnecessary inputs to NULL, then query once with a WHERE clause that auto-passes any NULL inputs (example: WHERE COL1 = ISNULL(#PAR1, COL1) AND COL2 = ISNULL(#PAR2, COL2), needs more logic for NULLable columns),
Or, utilizing IF/ELSE commands to switch between several different queries (different queries is probably maintenance headache, so avoid when possible).
Below is an example based on something I already have, although it uses the IF/ELSE approach. Try to do NULL/ISNULL instead:
CREATE FUNCTION [dbo].[SomeWackoName]
(#FirstArm tinyint, #SecondArm tinyint, #FirstLeg tinyint, #SecondLeg tinyint)
RETURNS #Results TABLE(
[Value] varchar(60)
) AS
BEGIN
-- For input, we need both arms, or both legs. It is acceptable to provide three inputs.
-- It is unacceptable to provide all four inputs (confusing), or failing to provide any full pair (like one input, or two unpaired inputs).
DECLARE #Validator tinyint = CASE WHEN #FirstArm + #SecondArm IS NULL THEN 0 ELSE 1 END + CASE WHEN #FirstLeg + #SecondLeg IS NULL THEN 0 ELSE 2 END
IF #Validator NOT BETWEEN 1 AND 2 RETURN --THROW 50000, 'INCORRECT INPUT WHYYYYYYYYYYYYYYY WHYYYYYYYYYYYYYYYYYY', 0;
-- Depending on input provided, decide how to behave.
IF #Validator = 1
INSERT INTO #Results SELECT 'I GOT THE ARMS. POPULATE WITH LEGS!!'
ELSE IF #Validator = 2
INSERT INTO #Results SELECT 'I GOT THE LEGS. POPULATE WITH ARMS!!'
-- Return results.
RETURN
END
CREATE FUNCTION fnEmailAddress
(
#LastName varchar (255)=null, #FirstName varchar (255)=null,
#streetNumber varchar(255)=null,#streetname varchar (255)=null
)
RETURNS table
Select case when #firstname+#lastname is not null then EMail Else LastName End Column3
from person
where
(
case when #firstname+#lastname is not null
then case when FirstName=#firstname and LastName=#lastname
then 1 else 0 End
when #StreetNumber+#streetname is not null
then case when StreetName=#Streetname and StreetNumber=#streetNumber
then 1 Else 0 End
End
)=1
The function should have 4 input parameters, pass first two or the last two parameters only.
CASE in the select statement will get 'Email' or 'LastName' depending upon input parameter null or not.
Similarly, CASE in the where statement will choose which filter should apply depending upon input parameter null or not, and results will be fetched based on that.
Facing problem for generating SQL Server Query
In the Following query dynamic conditions are added to check whether value is null or not
Select *
From tblEmployees
where EmployeeName = Case
When #EmployeeName Is Not Null
Then #EmployeeName
Else EmployeeName
End
But I need to add IN () Conditions and the parameter with in the IN () could be null or blank also ,if the parameter /string which is passed to the IN condition is blank then i donot want to add that condition in the query.
So how can i Achieve this.A helping hand will be very useful for me.
Thanks and Regards,
D.Mahesh
Depending on value of your parameter (blank of not), you can create SQL string accordingly.
DECLARE #sqlCommand VARCHAR(1000)
IF(ISNULL(#YourParameter,'')='')
#sqlCommand = 'your query goes here'
ELSE
#sqlCommand = 'your query goes here'
and then, run it using dynamic query execution
EXEC (#sqlCommand)
If not dynamic query then,
SELECT ....
FROM ....
WHERE CASE WHEN ISNULL(#YourParameter,'')='' THEN '' ELSE EmployeeName END IN (ISNULL(#YourParameter,''))
See if this works...
I think the Dynamic query is the best solution, however you could put the "IS NULL" and "IS BLANK" condition in OR with your IN clause.
Something like that
Select *
From tblEmployees
where #EmployeeName is null or EmployeeName in (#EmployeeName)
When #EmployeeName is null, your IN clause will be ignored
If i get this right you have #EmployeeName = 'Name1,Name2,Name3' and you want to get the employees that is named Name1 or Name2 or Name3, also the variable #EmployeeName can be null or contain an empty string.
Instead of using IN you can split the string #EmployeeName on , and store it in a table variable or temporary table. Then you can use that table in a join against tblEmployees to get the rows you need.
There are a lot of posts in S.O. about how to split a string. Here is one recent variant.
Group by sql query on comma joined column
This will work for SQL Server 2005 or later.
declare #EmployeeName varchar(100) = 'Name2,Name3,Name5'
-- Null or empty will have a comma
set #EmployeeName = coalesce(#EmployeeName, '') + ','
-- cteNames splits the string to rows
;with cteNames
as
(
select
left(#EmployeeName, charindex(',', #EmployeeName)-1) as Name,
right(#EmployeeName, len(#EmployeeName)-charindex(',', #EmployeeName)) as EmployeeName
union all
select
left(EmployeeName, charindex(',', EmployeeName)-1) as Name,
right(EmployeeName, len(EmployeeName)-charindex(',', EmployeeName)) as EmployeeName
from cteNames
where charindex(',', EmployeeName) > 1
)
select E.*
from tblEmployees as E
inner join cteNames as N
on E.Name = N.Name or
#EmployeeName = ','
-- #EmployeeName = ',' will give you all names when #EmployeeName is null of empty
I'm creating a stored procedure to return search results where some of the parameters are optional.
I want an "if statement" in my where clause but can't get it working. The where clause should filter by only the non-null parameters.
Here's the sp
ALTER PROCEDURE spVillaGet
-- Add the parameters for the stored procedure here
#accomodationFK int = null,
#regionFK int = null,
#arrivalDate datetime,
#numberOfNights int,
#sleeps int = null,
#priceFloor money = null,
#priceCeil money = null
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
select tblVillas.*, tblWeeklyPrices.price from tblVillas
INNER JOIN tblWeeklyPrices on tblVillas.villaId = tblWeeklyPrices.villaFK
where
If #accomodationFK <> null then
accomodationTypeFK = #accomodationFK
#regionFK <> null Then
And regionFK = #regionFK
IF #sleeps <> null Then
And sleeps = #sleeps
IF #priceFloor <> null Then
And price >= #priceFloor And price <= #priceCeil
END
Any ideas how to do this?
select tblVillas.*, tblWeeklyPrices.price
from tblVillas
INNER JOIN tblWeeklyPrices on tblVillas.villaId = tblWeeklyPrices.villaFK
where (#accomodationFK IS null OR accomodationTypeFK = #accomodationFK)
AND (#regionFK IS null or regionFK = #regionFK)
AND (#sleeps IS null OR sleeps = #sleeps)
AND (#priceFloor IS null OR (price BETWEEN #priceFloor And #priceCeil))
We've used a lot of COALESCE here in the past for "dynamic WHERE clauses" like you're talking about.
SELECT *
FROM vehicles
WHERE ([vin] LIKE COALESCE(#vin, [vin]) + '%' ESCAPE '\')
AND ([year] LIKE COALESCE(#year, [year]) + '%' ESCAPE '\')
AND ([make] LIKE COALESCE(#make, [make]) + '%' ESCAPE '\')
AND ([model] LIKE COALESCE(#model, [model]) + '%' ESCAPE '\')
A big problem arises though when you want to optionally filter for a column that is also nullable... if the data in the column is null for a given row AND the user didn't enter anything to search by for that column (so the user input is also null), then that row won't even show up in the results (which, if your filters are optional, is incorrect exclusionary behavior).
In order to compensate for nullable fields, you end up having to do messier looking SQL like so:
SELECT *
FROM vehicles
WHERE (([vin] LIKE COALESCE(#vin, [vin]) + '%' ESCAPE '\')
OR (#vin IS NULL AND [vin] IS NULL))
AND (([year] LIKE COALESCE(#year, [year]) + '%' ESCAPE '\')
OR (#year IS NULL AND [year] IS NULL))
AND (([make] LIKE COALESCE(#make, [make]) + '%' ESCAPE '\')
OR (#make IS NULL AND [make] IS NULL))
AND (([model] LIKE COALESCE(#model, [model]) + '%' ESCAPE '\')
OR (#model IS NULL AND [model] IS NULL))
Just so you understand, IF is procedural code in T-SQl. It canot be used in an insert/update/delete/select statement it can only be used to determine which of two statements you want to run. When you need different possibilities within a statement, you can do as above or use a CASE statement.
You can also use IsNull or Coalesce function
Where accomodationTypeFK = IsNull(#accomodationFK, accomodationTypeFK)
And regionFK = Coalesce(#regionFK,regionFK)
And sleeps = IsNull(#sleeps,sleeps )
And price Between IsNull(#priceFloor, Price) And IsNull(priceCeil, Price)
This does the same thing as Michael's suggestion above...
IsNull(), and Coalesce() work more or less the same way, they return the first non-Null argument in the list, except iSNull only allows 2 arguments, and Coalesce can take any number...
http://blogs.msdn.com/sqltips/archive/2008/06/26/differences-between-isnull-and-coalesce.aspx
Try putting your IF statement around the entire SQL statement. That means will have one SQL statement for each condition. That worked for me.