Using a CASE with the IN clause in T-SQL - asp.net

In My WHERE Clause I am using a CASE that will return all rows if parameter is blank or null. This works fine with single valuses. However not so well when using the IN clause. For example:
This works very well, if there is not match then ALL records are returned!
AND
Name = CASE WHEN #Name_ != '' THEN #Name_ ELSE Name END
AND
This works also, but there is no way to use the CASE expression, so if i do not provide some value i will not see any records:
AND
Name IN (Select Names FROM Person Where Names like #Name_)
AND
Can I use a CASE with the 2nd example? So if there is not a match all Names will be returned?

Maybe coalesce will resolve your problem
AND
Name IN (Select Names FROM Person Where Names like coalesce(#Name,Name))
AND
As Mattfew Lake said and used, you can also use isnull function
Name IN (Select Names FROM Person Where Names like isnull(#Name,Name))

No need for a CASE statement, just use a nested OR condition.
AND ( Name IN (Select Names FROM Person Where Names like #Name_)
OR
#Name_ IS NULL
)
AND

Perhaps something like this?
AND
Name IN (Select Names FROM Person Where Names LIKE
CASE WHEN #Name_ = '' OR #Name_ IS NULL THEN '%' ELSE #Name_ END)
AND
This will use the pattern '%' (which will match everything) only when a null or blank #Name_ parameter is provided, otherwise it will use the pattern specified by #Name_.
Alternatively, something like this should work:
AND
Name IN (Select Names FROM Person Where Names LIKE
ISNULL( NULLIF( #Name_, '' ), '%' ))
AND

This works
DECLARE #Name NVARCHAR(100)
SET #Name = ''
DECLARE #Person TABLE ( NAME NVARCHAR(100) )
INSERT INTO #Person VALUES ( 'fred' )
INSERT INTO #Person VALUES ( 'jo' )
DECLARE #Temp TABLE
(
id INT ,
NAME NVARCHAR(100)
)
INSERT INTO #Temp ( id, NAME ) VALUES ( 1, N'' )
INSERT INTO #Temp ( id, NAME ) VALUES ( 5, N'jo' )
INSERT INTO #Temp ( id, NAME ) VALUES ( 2, N'fred' )
INSERT INTO #Temp ( id, NAME ) VALUES ( 3, N'bob' )
INSERT INTO #Temp ( id, NAME ) VALUES ( 4, N'' )
SELECT * FROM #Temp
WHERE name IN ( SELECT name
FROM #Person
WHERE name = CASE WHEN #name != '' THEN #Name
ELSE name
END )

You should almost definitely use an IF statement with two selects. e.g.
IF #Name IS NULL
BEGIN
SELECT *
FROM Person
END
ELSE
BEGIN
SELECT *
FROM Person
--WHERE Name LIKE '%' + #Name + '%'
WHERE Name = #Name
END
N.B. I've changed like to equals since LIKE without wildcards it is no different to equals,
, it shouldn't make any difference in terms of performance, but it stops ambiguity for the next person that will read your query. If you do want non exact
matches then use the commented out WHERE and remove wildcards as required.
The reason for the IF is that the two queries may have very different execution plans, but by combining them into one query you are forcing the optimiser to pick one plan or the other. Imagine this schema:
CREATE TABLE Person
( PersonID INT IDENTITY(1, 1) NOT NULL,
Name VARCHAR(255) NOT NULL,
DateOfBirth DATE NULL
CONSTRAINT PK_Person_PersonID PRIMARY KEY (PersonID)
);
GO
CREATE NONCLUSTERED INDEX IX_Person_Name ON Person (Name) INCLUDE (DateOfBirth);
GO
INSERT Person (Name)
SELECT DISTINCT LEFT(Name, 50)
FROM sys.all_objects;
GO
CREATE PROCEDURE GetPeopleByName1 #Name VARCHAR(50)
AS
SELECT PersonID, Name, DateOfBirth
FROM Person
WHERE Name IN (SELECT Name FROM Person WHERE Name LIKE ISNULL(#Name, Name));
GO
CREATE PROCEDURE GetPeopleByName2 #Name VARCHAR(50)
AS
IF #Name IS NULL
SELECT PersonID, Name, DateOfBirth
FROM Person
ELSE
SELECT PersonID, Name, DateOfBirth
FROM Person
WHERE Name = #Name;
GO
Now If I run the both procedures both with a value and without:
EXECUTE GetPeopleByName1 'asymmetric_keys';
EXECUTE GetPeopleByName1 NULL;
EXECUTE GetPeopleByName2 'asymmetric_keys';
EXECUTE GetPeopleByName2 NULL;
The results are the same for both procedures, however, I get the same plan each time for the first procedure, but two different plans for the second, both of which are much more efficient that the first.
If you can't use an IF (e.g if you are using an inline table valued function) then you can get a similar result by using UNION ALL:
SELECT PersonID, Name, DateOfBirth
FROM Person
WHERE #Name IS NULL
UNION ALL
SELECT PersonID, Name, DateOfBirth
FROM Person
WHERE Name = #Name;
This is not as efficient as using IF, but still more efficient than your first query. The bottom line is that less is not always more, yes using IF is more verbose and may look like it is doing more work, but it is in fact doing a lot less work, and can be much more efficient.

Related

Oracle 11g Triggers

I have create a table person(id, name ,samenamecount).The samenamecount attribute can be null but for each row can store the row count for same names.I am achieving this by calling a stored procedure inside a after insert trigger.Below is my code.
create or replace procedure automatic(s in person.name%type)
AS
BEGIN
update person set samenamecount=(select count(*) from person where name=s) where name=s;
END;
create or replace trigger inserttrigger
after insert
on person
for each row
declare
begin
automatic(:new.name);
end;
On inserting a row it is giving error like
table ABCD.PERSON is mutating, trigger/function may not see it.
Can somebody help me to figure out this?
If you have the table:
CREATE TABLE person (
id NUMBER
GENERATED ALWAYS AS IDENTITY
CONSTRAINT person__id__pk PRIMARY KEY,
name VARCHAR2(20)
NOT NULL
);
Then rather than creating a trigger, instead, you could use a view:
CREATE VIEW person_view (
id,
name,
samenamecount
) AS
SELECT id,
name,
COUNT(*) OVER (PARTITION BY name)
FROM person;
You can use the trigger:
CREATE TRIGGER inserttrigger
AFTER INSERT ON person
BEGIN
MERGE INTO person dst
USING (
SELECT ROWID AS rid,
COUNT(*) OVER (PARTITION BY name) AS cnt
FROM person
) src
ON (src.rid = dst.ROWID)
WHEN MATCHED THEN
UPDATE SET samenamecount = src.cnt;
END;
/
fiddle
If you want to make it more efficient then you could use a compound trigger and collate the names that are being inserted and only update the matching rows.

Create composed unique key field with trigger after insert in SQLITE

I have a table with multiple columns and one (unique key) should be a value composed from the values of other two columns.
CREATE TABLE batches (
id TEXT PRIMARY KEY UNIQUE,
name TEXT NOT NULL,
project_id INTEGER);
On each insert, I want to generate the id based on the value of 'name' and 'project_id' (this one can be null):
INSERT INTO batches (name,project_id) VALUES
('21.01',NULL),
('21.01',1),
('21.02',2);
So, I have created a table TRIGGER but doesn't execute.
CREATE TRIGGER create_batches_id
AFTER INSERT ON batches FOR EACH ROW
BEGIN
UPDATE batches
SET id = SELECT quote(name ||"_"|| (CASE project_id
WHEN NULL THEN '' ELSE project_id END )
FROM batches WHERE rowid = (SELECT MAX(rowid) FROM batches))
WHERE rowid = (SELECT MAX(rowid) FROM batches);
END;
Error:
SQL Error [1]: [SQLITE_ERROR] SQL error or missing database (near "SELECT": syntax error)
I expect:
id = 21.01_
id = 21.01_1
id = 21.01_2
What am I doing wrong? If I run only the SELECT/CASE statment it returns ok: '21.01_2'
I have also tried without the quote() function, no success.
UPDATE I:
I have managed to execute the whole create trigger statement (parenthesis were missing):
CREATE TRIGGER create_batch_id
AFTER INSERT ON batches FOR EACH ROW
BEGIN
UPDATE batches
SET id = (SELECT name ||"_"|| (CASE project_id WHEN NULL THEN 0 ELSE project_id END ) FROM batches WHERE rowid = (SELECT MAX(rowid) FROM batches) )
WHERE rowid = (SELECT MAX(rowid) FROM batches);
END;
It seems my editor (DBeaver) has a glitch with the following new line character. If it is inside the selection it runs into this exception (or I am missing something):
SQL Error [1]: [SQLITE_ERROR] SQL error or missing database (incomplete input)
If I manually select only the above lines (from CREATE to ;), the trigger is created, however, not the expected result. If value in project_id is NULL, no id value is created.
Don't add the column id in the table.
Instead define the combination of name and project_id as the PRIMARY KEY of the table, so that it is also UNIQUE:
CREATE TABLE batches (
name TEXT NOT NULL,
project_id INTEGER,
PRIMARY KEY(name, project_id)
)
Then, whenever you need that id you can run a query:
SELECT name || '_' || COALESCE(project_id, '') AS id,
name,
project_id
FROM batches
Or create a view:
CREATE VIEW v_batches AS
SELECT name || '_' || COALESCE(project_id, '') AS id,
name,
project_id
FROM batches
and query the view:
SELECT * FROM v_batches
See the demo.
Or if your version of SQLite is 3.31.0+ you can have the column id as a generated column:
CREATE TABLE batches (
name TEXT NOT NULL,
project_id INTEGER,
id TEXT GENERATED ALWAYS AS (name || '_' || COALESCE(project_id, '')),
PRIMARY KEY(name, project_id)
);

How to make a select query for multiple conditions including an all condition?

I have a query in a stored procedure as shown below
select empid
from tblname
where place = #place
and category = #category
I want to use this same procedure to get all results for place = 'calicut' ( category can be any). By doing this I can avoid writing seperate query if category selected is 'all' in my dropdownlist.
SELECT empid
FROM tblname
WHERE place = #place
AND (category = #category OR #category = 'all');
You should be able to check for NULL -- this assumes you pass NULL when the default 'all' value is selected. If not, replace NULL with all or whatever value you are passing to the SP:
select empid
from tblname
where place=#place and
(#category IS NULL OR category=#category)
This checks the #category parameter -- if it's NULL, then it selects any category. Else it searches for that specific category.
And if you only care where place = 'calicut', then use this instead:
select empid
from tblname
where place='calicut' and
(#category IS NULL OR category=#category)
One more thing that you can do is initialize the #category variable while writing the stored procedure.
ALTER PROCEDURE yourProcName
#place VARCHAR(100),
#category VARCHAR(50) = ''
AS
BEGIN
select empid
from tblname
where place = #place
and
(#category = '' OR category = #category)
END

How to use Split function in stored procedure?

I am selecting the values like below:
select browser, firstname, lastname from details
Here the browser value will be like this:
33243#Firefox#dsfsd
34234#google#dfsd
And separately I have used split function for single value as below:
select * from dbo.split('33243#Firefox#dsfsd','#')
Result is:
items
===========
33243
firefox
dsfsd
So I have used split function like below
select split(browser, '#'), firstname, lastname from details
but its not working...
What I need is
33243#Firefox#dsfsd
instead of displaying the value like this into the grid,
have to display only Firefox into the grid.
Since you know you want the second element each time, you could write a function like this:
CREATE FUNCTION [dbo].[fn_SplitElement]
(
#inputString nvarchar(2000), --The input string
#elem int, --The 1-based element index to return,
#delimiter nvarchar(1) --The delimiter char
)
RETURNS nvarchar(2000)
AS
BEGIN
-- Declare the return variable here
DECLARE #result nvarchar(2000)
-- Add the T-SQL statements to compute the return value here
SELECT #result = value
FROM
(
SELECT *,ROW_NUMBER() OVER(ORDER By Position) as rownum FROM dbo.split(#inputString,#delimiter)
) as t
WHERE rownum=#elem
-- Return the result of the function
RETURN #result
END
GO
Then you can call:
select [dbo].[fn_SplitElement](browser,2,'#') as 'BrowserName', firstname, lastname from details
You do not state which version of SQL Server you are using or which split function. However, most split functions return a table rather than rows so you'll need to use a JOIN to join the two tables (the first is the split, the second is the rest of the fields).
For more information in SQL split functions, see Erland Sommarskog's page at http://www.sommarskog.se/arrays-in-sql.html .
You can Create a Split function and that will be use when you want.
CREATE FUNCTION [dbo].[Split]
(
#List nvarchar(max),
#SplitOn nvarchar(1)
)
RETURNS #RtnValue table (
Id int identity(1,1),
Value nvarchar(max)
)
AS
BEGIN
While (Charindex(#SplitOn,#List)>0)
Begin
Insert Into #RtnValue (value)
Select
Value = ltrim(rtrim(Substring(#List,1,Charindex(#SplitOn,#List)-1)))
Set #List = Substring(#List,Charindex(#SplitOn,#List)+len(#SplitOn),len(#List))
End
Insert Into #RtnValue (Value)
Select Value = ltrim(rtrim(#List))
Return
END
after that you can call the function in your query as below.
SELECT * FROM anotherTable WHERE user_id IN(dbo.split(#user_list,','))
I Have used below query instead of using split function.. its working perfectly..
select
SUBSTRING(SUBSTRING(browser, CHARINDEX ('#', browser)+1,LEN(browser)-charindex('#', browser)),
0,
CHARINDEX('#',SUBSTRING(browser, CHARINDEX ('#', browser)+1,LEN(browser)-CHARINDEX('#', browser)))),
firstname, lastname from details

SQL server conditional select statement

Ok, this might be an easy one, but I just can't get it.
I am creating a page which will query a table with many columns and most items are not unique. I need to be able to get a list of records that match as many of the (up to 4) search criteria as possible.
Example:
I am user searching for the following items, I enter at least one and up to 4 of the items below in a text box:
Name, age, gender, weight (user may or may not fill in all of them).
If he just enters "F" for gender, then he will get a list of thousands of females with their name, age, gender and weight.
However if he enters "F" for gender and "300" for weight, he will get a much smaller list of returned records.
I need to be able to create a sql statement that can perform that search with that functionality.
advTHANKSance
I've used similar to the one below to do what you are trying:
DECLARE #Gender varchar(1)
DECLARE #Age int
DECLARE #Weight int
DECLARE #Name varchar(64)
SELECT * FROM MyTable
WHERE
(#Gender is null OR Gender = #gender)
AND (#weight is null OR Weight = #weight)
AND (#Age is null OR age = #Age)
and (#Name is null OR Name = #Name)
if you were to create a stored procedure (which i would recommend) it would look like this:
CREATE PROCEDURE SelectRecords
#Gender varchar(1),
#Age int,
#Weight int,
#Name varchar(64)
AS
SELECT * FROM MyTable
WHERE
(#Gender is null OR Gender = #gender)
AND (#weight is null OR Weight = #weight)
AND (#Age is null OR age = #Age)
and (#Name is null OR Name = #Name)
What this stored procedure is doing is checking to see if you passed a value in for the specific parameter. If you DID NOT then it will be null and the condition will be true. if you DID then it will not be null and the second condition must evaluate to true for the record to be returned.
I've often seen this done with the following SQL statement (where #gender, #weight, #age, and #name are filled in with data from the user, and gender, weight, age, and name are table fields):
SELECT * FROM MyTable
WHERE
gender = COALESCE(#gender, gender)
AND weight = COALESCE(#weight, weight)
AND age = COALESCE(#age, age)
and name= COALESCE(#name, name)
(Edit: I just wanted to add a short explanation of why this works for anyone not familiar with coalesce. The coalesce function takes the first not-null value of the 2 passed to it. So if there is something in the # parameter, which means the user entered data for that field, it will check if the field equals that user-entered value; if the user didn't enter anything and the # parameter is null, it will test against the second value, which is the field itself - and as the field is always equal to itself, this will return all records - it won't filter based on this field at all.)
I did stuff like this by combining a null check with the parameter. If it was null then everything got included, if not then the other part actually mattered
CREATE myFunnyProc ( #p1 nvarchar(10), #p2 nvarchar(10) ) AS BEGIN
SELECT * FROM dbo.myReallyLongTable table
WHERE
(#p1 is null or table.name LIKE #p1)
AND (#p2 is null or table.age = #p2)
END
#Abe - Your solution will work IF Age and Weight are not between Name and Gender in the SELECT statement. I know cuz I tried it :) on SQL Server 2008. I made a table with many records with NULLs scattered throughout. I also made a proc, and running it as you wrote it would not return rows when age or weight were entered as parameters. I moved the int based params in the SELECT statement to the top or bottom of list, both worked. But nesting the int params between the string params in the select statement and then specifying the int params made it fail...
So why did the original not work?? :) Anyone?
BTW, the COALESCE will not work at all (yes, I tested it and reordered the SELECT statement in the proc). It LOOKED like it should work...
Seeing as you are using ASP.NET, you could take a look at LINQ-to-SQL which solves this in a very elegant way:
var query = db.T_Persons;
if (txtGender.Text != string.Empty)
query = query.Where(x => x.Gender == txtGender.Text);
if (txtWeigth.Text != string.Empty)
query = query.Where(x => x.Weight == int.Parse(txtWeight.Text));
...
Of course, you'll need to be using .NET 3.5 or newer.
The answer is something most people try to stay away from it is dynamic sql.
I would suggest creating a stored procedure you can call for this, but here is the code. You need to put the name of your table in place of tablename.
Declare
#gender varchar(1),
#weight int,
#age int,
#name varchar(100),
#sql varchar(200),
#variableCount int
set #variableCount = 0
set #sql = 'select * from tablename'
if(#gender is not null)
Begin
#sql += ' where gender = #gender'
#vaiableCount = #VariableCount+1
End
if(#weight is not null)
Begin
if(#variableCount = 0)
Begin
#sql += ' Where '
End
else
#sql += ' And '
#sql += 'weight = #weight'
End
if(#age is not null)
Begin
if(#VariableCount = 0)
Begin
#sql += ' where '
End
else
#sql += ' And '
#sql += 'age = #age'
End
if(#name is not null)
Begin
if(#VariableCount = 0)
Begin
#sql += ' where '
End
else
#sql += ' And '
#sql += 'name = #name'
End
execute #sql

Resources