If Variable is Blank Then No Where Clause [Multiple] - sql

This question is similar to this one
The only difference is I have 3 sql parameter which I set default value as null,
and if user provides any of these values, they should be included in where clause
here is the stored procedure:
create procedure [dbo].[SearchCareer]
#country int =null,
#state int =null,
#position int=null
as
begin
SELECT * FROM Careers where Location=#country and #state=#state and #position=#position
end
I have tried this approach:
begin
SELECT * FROM Careers where
((#position is null and Position is null)
or (Position = #position)) and
((#country is null and Location is null)
or (Location = #country)) and
((#state is null and StateID is null)
or (StateID = #state))
end
It works fine when user only enter position but wont work on other cases.
I know I can use IF , ELSE IF To check and then build my query, but I want to avoid that.

This is probably close to what you are thinking. If the table is large the performance of this type of approach can become problematic. I had to make some guesses here because you where doing things like checking to see if a variable was equal to the same variable.
SELECT * --You really should use only the columns you need instead of *
FROM Careers
where (Location = #country OR #country is null)
and ([state] = #state OR #state is null)
and (position = #position or #position is null)

This will do what you want:
SELECT * FROM Careers where case #country when is null THEN #country ELSE Location end=#country and case #state when is null THEN #state ELSE State end=#state and case #position when is null THEN #position ELSE Position end=#position
I assumed that two other fields are State and Position.

Related

t-sql string unique ID (Northwind database)

I've been trying to get this right for some time now with no use.
I have a table in mssql database and I want to insert new row using stored procedure
CREATE TABLE "Customers" (
"CustomerID" NCHAR(5) NOT NULL,
"CompanyName" NVARCHAR(40) NOT NULL,
"ContactName" NVARCHAR(30) NULL,
"ContactTitle" NVARCHAR(30) NULL,
"Address" NVARCHAR(60) NULL,
"City" NVARCHAR(15) NULL,
"Region" NVARCHAR(15) NULL,
"PostalCode" NVARCHAR(10) NULL,
"Country" NVARCHAR(15) NULL,
"Phone" NVARCHAR(24) NULL,
"Fax" NVARCHAR(24) NULL,
PRIMARY KEY ("CustomerID")
);
The problem is CustomerID field which contains unique string for each record (ALFKI, BERGS, BERGS, etc.)
I want to make a stored procedure which will insert a row with new data and create an unique CustomerID. Build in functions are out of a question as I need the string to be 5 chars long.
I have a procedure which generates 5 chars ID as follows
begin
declare #chars char(26) = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
declare #i int = 0
declare #id varchar(max) = ''
while #i < 5
begin
set #id = #id + substring(#chars, cast(ceiling(rand() * 26) as int), 1)
set #i = #i + 1
end
Select (cast(#id as nvarchar(400)))
end
And the one that I tried to make work with no use. It is supposed to select an unique id (set #id = 'ANATR' is there on purpose to make it go into the loop
begin
declare #randID varchar(5) = ''
declare #selectID varchar(20) = ''
declare #chars char(26) = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
declare #i int = 0
declare #id varchar(10) = ''
while #i < 5
begin
set #id = #id + substring(#chars, cast(ceiling(rand() * 26) as int), 1)
set #i = #i + 1
end
select #id
set #id = 'ANATR'
SET #selectID = (SELECT CustomerID FROM CUSTOMERS WHERE CustomerID = #id)
while #selectID <> 'NULL'
begin
set #id = ''
while #i < 5
begin
set #id = #id + substring(#chars, cast(ceiling(rand() * 26) as int), 1)
set #i = #i + 1
end
SET #selectID = (SELECT CustomerID FROM CUSTOMERS WHERE CustomerID = #id)
SELECT #id
end
end
Here is the insert procedure I have at the moment
CREATE PROCEDURE [dbo].[InsertCustomers]
(
#CustomerID nchar(5),
#CompanyName nvarchar(40),
#ContactName nvarchar(30) = NULL,
#ContactTitle nvarchar(30) = NULL,
#Address nvarchar(60) = NULL,
#City nvarchar(15) = NULL,
#Region nvarchar(15) = NULL,
#PostalCode nvarchar(10) = NULL,
#Country nvarchar(15) = NULL,
#Phone nvarchar(24) = NULL,
#Fax nvarchar(24) = NULL
)
AS
SET NOCOUNT OFF;
INSERT INTO [dbo].[Customers] ([CustomerID], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax]) VALUES (#CustomerID, #CompanyName, #ContactName, #ContactTitle, #Address, #City, #Region, #PostalCode, #Country, #Phone, #Fax);
The main problem here is that the incremental cost of detecting collisions from the generated string, and try again, increases as you generate more and more strings (since you have to read all of those strings to make sure you didn't generate a duplicate). At the same time, the odds of hitting a duplicate goes up, meaning the bigger the table gets, the slower this process will get.
Why do you need to generate the unique string at runtime? Build them all in advance. This article and this post are about random numbers, but the basic concept is the same. You build up a set of unique strings and pull one off the stack when you need one. Your chance of collisions stays constant at 0% throughout the lifetime of the application (provided you build up a stack of enough unique values). Pay for the cost of collisions up front, in your own setup, instead of incrementally over time (and at the cost of a user waiting for those attempts to finally yield a unique number).
This will generate 100,000 unique 5-character strings, at the low, one-time cost of about 1 second (on my machine):
;WITH
a(a) AS
(
SELECT TOP (26) number + 65 FROM master..spt_values
WHERE type = N'P' ORDER BY number
),
b(a) AS
(
SELECT TOP (10) a FROM a ORDER BY NEWID()
)
SELECT DISTINCT CHAR(b.a) + CHAR(c.a) + CHAR(d.a) + CHAR(e.a) + CHAR(f.a)
FROM b, b AS c, b AS d, b AS e, b AS f;
That's not enough? You can generate about 1.12 million unique values by changing TOP (10) to TOP (20). This took 18 seconds. Still not enough? TOP (24) will give you just under 8 million in about 2 minutes. It will get exponentially more expensive as you generate more strings, because that DISTINCT has to do the same duplicate checking you want to do every single time you add a customer.
So, create a table:
CREATE TABLE dbo.StringStack
(
ID INT IDENTITY(1,1) PRIMARY KEY,
String CHAR(5) NOT NULL UNIQUE
);
Insert that set:
;WITH
a(a) AS
(
SELECT TOP (26) number + 65 FROM master..spt_values
WHERE type = N'P' ORDER BY number
),
b(a) AS
(
SELECT TOP (10) a FROM a ORDER BY NEWID()
)
INSERT dbo.StringStack(String)
SELECT DISTINCT CHAR(b.a) + CHAR(c.a) + CHAR(d.a) + CHAR(e.a) + CHAR(f.a)
FROM b, b AS c, b AS d, b AS e, b AS f;
And then just create a procedure that pops one off the stack when you need it:
CREATE PROCEDURE dbo.AddCustomer
#CustomerName VARCHAR(64) /* , other params */
AS
BEGIN
SET NOCOUNT ON;
DELETE TOP (1) dbo.StringStack
OUTPUT deleted.String, #CustomerName /* , other params */
INTO dbo.Customers(CustomerID, CustomerName /*, ...other columns... */);
END
GO
No silly looping, no needing to check if the CustomerID you generated just exists, etc. The only additional thing you'll want to build is some type of check that notifies you when you're getting low.
As an aside, these are terrible identifiers for a CustomerID. What is wrong with a sequential surrogate key, like an IDENTITY column? How is a 5-digit random string with all this effort involved, any better than a unique number the system can generate for you much more easily?
Muhammed Ali 's answer works, but will prove rather ressource intensive (especially when there aren't many combinations of 5 letters left to use) : your function uses the random generator, and it will take it a while to find a combination that isn't used, especially since it has a very limited memory of its previous results.
This means it will try, and might give you something of the sort (exaggerating a bit) : BAGER the first time, then ANSWE the second time, then again BAGER the third time. You see you will lose a good amount of time with the generator giving you the same answer over and over again (especially over 12M possible combinations).
If you are looking for a fixed length ID (since you use NCHAR(5), I guess that's a good assumption), I would rather look into building a table that contains all the possible combinations, and pick one value of this table every time you need one. You would delete it once it got used, or mark it as used (which I would prefer, for reuseability reasons).
This leads to my final comment (which I cannot put as comment 'cause I don't have enough reputation) : why not use the IDENTITY function provided by MS-SQL ? This provides a much better handling of the Primary key generation...
I believe you can do something like this to make sure you all get a unique id
begin
declare #chars char(26) = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
declare #i int = 0
declare #id varchar(max) = ''
while (1=1)
begin
set #id = #id + substring(#chars, cast(ceiling(rand() * 26) as int), 1)
set #i = #i + 1
IF (NOT EXISTS(SELECT * FROM Customers WHERE CustomerID = #id) AND LEN(#id) = 5)
BREAK
ELSE
CONTINUE
end
Select (cast(#id as nvarchar(400)))
end
Set the while condition to be always true and break out of while loop only when both of your requirements are TRUE i.e Length of new ID is 5 and it does not exist in the customers table already.

SQL SERVER: Check if variable is null and then assign statement for Where Clause

I am trying to achieve something like the below in WHERE clause in sql.
if (#zipCode ==null)
begin
([Portal].[dbo].[Address].Position.Filter(#radiusBuff) = 1)
end
else if(#zipCode !=null)
begin
([Portal].[dbo].[Address].PostalCode=#zipCode )
end
I tried the following:
WHERE ((#zipCode IS NOT NULL AND ([Portal].[dbo].[Address].PostalCode=#zipCode)) OR (#zipCode IS NULL AND ([Portal].[dbo].[Address].Position.Filter(#radiusBuff) = 1)))
which is wrong. Can anyone help in framing the exact statement. Thanks!
is null is the syntax I use for such things, when COALESCE is of no help.
Try:
if (#zipCode is null)
begin
([Portal].[dbo].[Address].Position.Filter(#radiusBuff) = 1)
end
else
begin
([Portal].[dbo].[Address].PostalCode=#zipCode )
end
Isnull() syntax is built in for this kind of thing.
declare #Int int = null;
declare #Values table ( id int, def varchar(8) )
insert into #Values values (8, 'I am 8');
-- fails
select *
from #Values
where id = #Int
-- works fine
select *
from #Values
where id = isnull(#Int, 8);
For your example keep in mind you can change scope to be yet another where predicate off of a different variable for complex boolean logic. Only caveat is you need to cast it differently if you need to examine for a different data type. So if I add another row but wish to specify int of 8 AND also the reference of text similar to 'repeat' I can do that with a reference again back to the 'isnull' of the first variable yet return an entirely different result data type for a different reference to a different field.
declare #Int int = null;
declare #Values table ( id int, def varchar(16) )
insert into #Values values (8, 'I am 8'), (8, 'I am 8 repeat');
select *
from #Values
where id = isnull(#Int, 8)
and def like isnull(cast(#Int as varchar), '%repeat%')
is null can be used to check whether null data is coming from a query as in following example:
declare #Mem varchar(20),#flag int
select #mem=MemberClub from [dbo].[UserMaster] where UserID=#uid
if(#Mem is null)
begin
set #flag= 0;
end
else
begin
set #flag=1;
end
return #flag;
Try a case statement
WHERE
CASE WHEN #zipCode IS NULL THEN 1
ELSE #zipCode
END
Try the following:
if ((select VisitCount from PageImage where PID=#pid and PageNumber=5) is NULL)
begin
update PageImage
set VisitCount=1
where PID=#pid and PageNumber=#pageno
end
else
begin
update PageImage
set VisitCount=VisitCount+1
where PID=#pid and PageNumber=#pageno
end

How can I use <> operator and || operator inside a stored procedure

I have created an database file inside my asp.net application. Now from server explorer I tried to write a Stored procedure as follows
CREATE PROCEDURE insertData
(
#ID int,
#Name varchar(50),
#Address varchar(50),
#bit BIT OUTPUT
)
as
begin
declare #oldName as varchar(45)
declare #oldAddress as varchar(45)
set #oldName=(select EmployeeName from Employee where EmployeeName=#Name)
set #oldAddress=(select Address from Employee where Address=#Address)
if(#oldName <> #Name | #oldAddress <> #Address)
insert into Employee(EmpID,EmployeeName,Address)values(#ID,#Name,#Address)
SET #bit = 1
END
But this is giving me an error when I am saving it like Incorrect syntax near <..
There are several things wrong here
if(#oldName <> #Name | #oldAddress <> #Address)
Won't work - maybe try
if #oldName <> #Name OR #oldAddress <> #Address
Of course, this will never be true because of the way the two queries above (which could and should have just been one query assigning both variables) make sure that the variables are always equal.
I.e.:
set #oldName=(select EmployeeName from Employee where EmployeeName=#Name)
What can #oldName be, if not equal to #Name? (Okay, it could be NULL, but then <> is the wrong operator to use if NULL is what you're checking for)
I think that what you wanted to write here was:
select #oldName=EmployeeName,#oldAddress = Address from Employee where EmpID = #ID
You should use OR and not |
You can also do this instead of querying and checking each value separately, this will insert new row if name and/or address do not match for given empid
IF NOT EXISTS (
select * from Employee
where EmpID = #ID AND EmployeeName = #Name AND Address = #Address)
insert into Employee(EmpID,EmployeeName,Address)values(#ID,#Name,#Address)
SET #bit = 1
END
You can use != instead of "<>" and you can use Or instead of "|".

SQL, how to use Dynamic Condition logics?

When you need Dynamic WHERE Clause I can use;
CREATE PROCEDURE [dbo].[sp_sel_Articles]
#articleId INT = NULL
, #title NVARCHAR(250) = NULL
, #accessLevelId INT = NULL
AS
BEGIN
SELECT *
FROM table_Articles Art
WHERE
(Art.ArticleId = #articleId OR #articleId IS NULL)
AND (Art.Title LIKE '%' + #title + '%' OR #title IS NULL)
AND (Art.AccessLevelId = #accessLevelId OR #accessLevelId IS NULL)
END
So, I am able to invoke this procedure -for example- ONLY by ArticleId
EXEC [sp_sel_Articles] #articleId = 3
But, sometimes I'll need to invoke by AccessLevelId and sometimes NOT by an EXACT VALUE. For example, I'll need MORE THAN the given accesslevelId or LESS THAN.
Current procedure can ONLY handle the EXACT value by using
Art.AccessLevelId = #accessLevelId
Could also be possible to give the CONDITION type as well as the value into the procedure? It may seem very odd in this example but please just bear with me:
CREATE PROCEDURE [dbo].[sp_sel_Articles]
#articleId INT = NULL
, #title NVARCHAR(250) = NULL
, #accessLevelId INT = NULL
, **#accessLevelIdCondition**
AS
BEGIN
SELECT *
FROM table_Articles Art
WHERE
(Art.ArticleId = #articleId OR #articleId IS NULL)
AND (Art.Title LIKE '%' + #title + '%' OR #title IS NULL)
AND (Art.AccessLevelId **#accessLevelIdCondition** #accessLevelId OR #accessLevelId IS NULL)
END
Perhaps an Function can be used, I don't know. Since, there will be at least 20 Procedure that will require this flexibility, I'll need a better, more global solution as much as possible rather than writing IF ELSE condition in every procedure.
Thanks in advance,
You'd probably need to use dynamic SQL to pass in the operator. Or you could pass in two values, e.g.
#MinAccessLevelID INT,
#MaxAccessLevelID INT
...
WHERE (
(#MinAccessLevelID IS NULL AND #MaxAccessLevelID IS NULL)
OR
(AccessLevelID >= #MinAccessLevelID AND AccessLevelID <= #MaxAccessLevelID)
)
When you want exact (e.g. only 3), just pass 3 into both values. When you want anything above 3, pass 20000000000 into the #Max param, or 0 if you want everything below 3.
But you'll find as these permutations get more complex, you are going to be better off just using dynamic SQL (and with optimize for ad hoc workloads set, this will be better for plan cache reuse and thwarting parameter sniffing as well).
Read this www.sommarskog.se/dynamic_sql.html before applying
CREATE PROCEDURE [dbo].[sp_sel_Articles]
#articleId INT = NULL
, #title NVARCHAR(250) = NULL
, #accessLevelId INT = NULL
, #accessLevelIdCondition varchar(100)
AS
BEGIN
DECLARE #SQL varchar(8000)
SET #SQL='
SELECT *
FROM table_Articles Art
WHERE
(Art.ArticleId = '+cast(#articleId as varchar(100))+' OR '+cast(#articleId as varchar(100))+'IS NULL)
AND (Art.Title LIKE ''%'' + #title + ''%'' OR #title IS NULL)
AND (Art.AccessLevelId '+#accessLevelIdCondition+ cast(#accessLevelId as varchar(100))+' OR '+cast(#accessLevelId as varchar(100))+' IS NULL) '
EXEC(#sql)
END
You can always make a dynamic query with just making a querystring
execute ('select count(*) from table' )
So with the params entered in your stored procedure, you can also form up a querystring which you can execute.
You could use a case statement - it can look a little funny if not formatted correctly but you can try something like:
SELECT Columns FROM SomeTable
WHERE 1 = CASE
WHEN #SomeOption = '<>' AND SomeValue >= #SomeMinParam AND SomeValue <= SomeMaxParam THEN 1
WHEN #SomeOption '=' AND SomeValue = #SomeMinParam THEN 1
ELSE 0
END
(though as Aaron pointed out - the <> you pass in doesn't really reflect the comparison operators in the statement - change this to something meaningful :))
in your case:
CREATE PROCEDURE [dbo].[sp_sel_Articles]
#articleId INT = NULL,
#title NVARCHAR(250) = NULL,
#MinaccessLevelId INT = NULL,
#MaxaccessLevelId INT = NULL,
#accessType varchar(5) = '<>'
AS
BEGIN
SELECT *
FROM table_Articles Art
WHERE
(Art.ArticleId = #articleId OR #articleId IS NULL)
AND (Art.Title LIKE '%' + #title + '%' OR #title IS NULL)
AND 1 = CASE
WHEN #accessType = '<>' AND (Art.AccessLevelId = #MinaccessLevelId OR #accessLevelId IS NULL) THEN 1
WHEN #accessType = '=' AND (Art.AccessLevelId >= #MinaccessLevelId OR Art.AccessLevelId <= #MaxaccessLevelId) THEN 1
ELSE 0
END
END
Maybe use a bit #CompareAccessLevelToMin instead of a varchar() for the #accessType param. Still has the trouble of not telling you what setting it to 'false' means though.

T-SQL Stored Procedure NULL input values cause select statement to fail

Below is a stored procedure to check if there is a duplicate entry in the database based upon checking all the fields individually (don't ask why I should do this, it just has to be this way).
It sounds perfectly straightforward but the SP fails.
The problem is that some parameters passed into the SP may have a null value and therefore the sql should read "is null" rather than "= null".
I have tried isnull(),case statements,coalesce() and dynamic sql with exec() and sp_executesql and failed to implement any of these. Here is the code...
CREATE PROCEDURE sp_myDuplicateCheck
#userId int,
#noteType char(1),
#aCode char(3),
#bCode char(3),
#cCode char(3),
#outDuplicateFound int OUT
AS
BEGIN
SET #outDuplicateFound = (SELECT Top 1 id FROM codeTable
WHERE userId = #userId
AND noteType = #noteType
AND aCode = #aCode
AND bCode = #bCode
AND cCode = #cCode
)
-- Now set the duplicate output flag to a 1 or a 0
IF (#outDuplicateFound IS NULL) OR (#outDuplicateFound = '') OR (#outDuplicateFound = 0)
SET #outDuplicateFound = 0
ELSE
SET #outDuplicateFound = 1
END
I think you need something like this for each possibly-null parameter:
AND (aCode = #aCode OR (aCode IS NULL AND #aCode IS NULL))
If I understand your question correctly, then I encourage you to do a little research on:
SET ANSI_NULLS OFF
If you use this command in your stored procedure, then you can use = NULL in your comparison. Take a look at the following example code to see how this works.
Declare #Temp Table(Data Int)
Insert Into #Temp Values(1)
Insert Into #Temp Values(NULL)
-- No rows from the following query
select * From #Temp Where Data = NULL
SET ANSI_NULLS OFF
-- This returns the rows where data is null
select * From #Temp Where Data = NULL
SET ANSI_NULLS ON
Whenever you SET ANSI_NULLS Off, it's a good practice to set it back to ON as soon as possible because this may affect other queries that you run later. All of the SET commands only affect the current session, but depending on your application, this could span multiple queries, which is why I suggest you turn ansi nulls back on immediately after this query.
I think this should work with COALESCE function. Try this:
CREATE PROCEDURE sp_myDuplicateCheck
#userId int,
#noteType char(1),
#aCode char(3),
#bCode char(3),
#cCode char(3),
#outDuplicateFound int OUT
AS
BEGIN
SET #outDuplicateFound = (SELECT Top 1 id FROM codeTable
WHERE userId = #userId
AND noteType = #noteType
AND COALESCE(aCode,'NUL') = COALESCE(#aCode,'NUL')
AND COALESCE(bCode,'NUL') = COALESCE(#bCode,'NUL')
AND COALESCE(cCode,'NUL') = COALESCE(#cCode,'NUL')
)
-- Now set the duplicate output flag to a 1 or a 0
IF (#outDuplicateFound IS NULL) OR (#outDuplicateFound = '') OR (#outDuplicateFound = 0)
SET #outDuplicateFound = 0
ELSE
SET #outDuplicateFound = 1
END
Good Luck!
Jason
Try this :
CREATE PROCEDURE sp_myDuplicateCheck
#userId int = 0,
#noteType char(1) = "",
#aCode char(3) = "",
#bCode char(3) = "",
#cCode char(3) = "",
#outDuplicateFound int OUT
AS
BEGIN
SET #outDuplicateFound = (SELECT Top 1 id FROM codeTable
WHERE #userId in (userId ,0)
AND #noteType in (noteType,"")
AND #aCode in (aCode , "")
AND #bCode in (bCode , "")
AND #cCode in (cCode ,"")
)
-- Now set the duplicate output flag to a 1 or a 0
IF (#outDuplicateFound IS NULL) OR (#outDuplicateFound = '') OR (#outDuplicateFound = 0)
SET #outDuplicateFound = 0
ELSE
SET #outDuplicateFound = 1
END
What this basically does is to provide default values to the input parameters in case of null and then in the where condition checks only if the values are not equal to the default values.
I would first add a check to see if all of the parameters were null at run time, i.e.,
IF(COALESCE(#userId, #noteType, #aCode, #bCode, #cCode) IS NULL)
BEGIN
-- do something here, log, print, return, etc.
END
Then after you've validated that the user passed something in you can use something like this in your WHERE clause
WHERE userId = COALESCE(#userId, userId)
AND noteType = COALESCE(#noteType, noteType)
AND aCode = COALESCE(#aCode, aCode)
AND bCode = COALESCE(#bCode, bCode)
AND cCode = COALESCE(#cCode, cCode)
EDIT: I may have missed the intent that if the parameter was passed in as null that means you explicitly want to test the column for null. My above where clause assumed that the null parameter meant 'skip the test on this column.'
Alternatively, I believe you can use your original query and add the ANSI_NULLS set option at the stored procedure create time. For example,
SET ANSI_NULLS OFF
GO
CREATE PROC sp_myDuplicateCheck....
Effectively this should allow your code to then evaluate column=null as opposed to column is null. I think Kalen Delaney once coined the ANSI_NULLS and QUOTED_IDENTIFIER options as 'sticky options' because if they're set at procedure create time they stay with the procedure at run time, regardless of how the connection at that time is set.
SET ANSI_NULLS OFF/On
That way you can do colName = null