Is there a better way to obtain the following?
DECLARE #Desc VARCHAR(200) = ''
SELECT [id],
[Desc],
[Col1],
[Col2]
FROM [dbo].[tbl]
WHERE [Desc] LIKE CASE
WHEN #Desc LIKE ''
THEN [Desc]
ELSE '%'+ #Desc +'%'
END
This allows to return all values if the parameter is not defined (#Desc='') or return a subset of values (#Desc='test').
Use OR Operator instead of Case
DECLARE #Desc VARCHAR(200) = ''
SELECT [id],
[Desc],
[Col1],
[Col2]
FROM [dbo].[tbl]
WHERE
(
ISNULL(#Desc,'')=''
)
OR
(
ISNULL(#Desc,'')<>''
AND
[Desc] LIKE '%'+ #Desc +'%'
)
Execution Plan Difference using Both Logics
Using Case
Using Or
It's better for the execution engine to do as much parameter handling prior to the query.
DECLARE #Desc VARCHAR(200) = '';
DECLARE #SelectAll bit;
SET #SelectAll = CASE WHEN #Desc = '' THEN 1 ELSE 0 END;
SET #Desc = CASE WHEN #Desc = '' THEN #Desc ELSE ('%' + #Desc + '%') END;
SELECT [id],
[Desc],
[Col1],
[Col2]
FROM [dbo].[tbl]
WHERE
(#SelectAll = 1)
OR
(#SelectAll = 0 AND [Desc] LIKE #Desc);
If you don't mind code duplication, you can take it even further and do two separate queries split by IF / ELSE.
If you use null you save some steps
declare #userId int = null;
SELECT TOP 1000 [AuctionId]
,[UserId]
,[BiddingPrice]
,[DateTime]
FROM [Test].[dbo].[Bid]
WHERE isnull(#userId, [UserId]) = [UserId];
Well, As Aaron Bertrand commented, your current query can be written simply like this:
DECLARE #Desc VARCHAR(200) = ''
SELECT [id],
[Desc],
[Col1],
[Col2]
FROM [dbo].[tbl]
WHERE [Desc] LIKE '%'+ #Desc +'%'
Since if #Desc contains an empty string, it will result with [Desc] LIKE '%%' -so all records where [Desc] is not null will be returned anyway.
If #Desc can be passed as null, use Coalesce to convert null to an empty string:
...WHERE [Desc] LIKE '%'+ COALESCE(#Desc, '') +'%'
Please note that in both questions, records where the Desc column contains null will not be returned. If that is a nullable column and you want to also return the records where it's null and the #Desc parameter is also null or empty, then you should use OR:
SELECT [id],
[Desc],
[Col1],
[Col2]
FROM [dbo].[tbl]
WHERE [Desc] LIKE '%'+ #Desc +'%'
OR (COALESCE(#Desc, '') = '' AND [Desc] IS NULL)
Also, please note that this is only because of your use of LIKE - Should you try to evaluate conditions using a different operator (such as =, <, >etc') you should use the OR syntax like in the other answers.
Related
I want to be able to dynamically specify the column on which to filter the query and the value to filter by.
The following where clause is giving me the error on each case when line
an expression of non-boolean type specified in a context where a condition is expected
declare #filterColumn varchar(30) = 'Firstname'
declare #filterValue varchar(120) = 'Dave'
[select statement].....
where
case when #filterColumn = 'Firstname' THEN Firstname END,
case when #filterColumn = 'Lastname' THEN Lastname END,
case when #filterColumn = 'StatusId' THEN u.Status END,
case when #filterColumn = 'CreatedDate' THEN u.CreatedDate END,
like #filterValue
How can I fix this so that I can get the correct column to filter the results on.
You can do something like this. This will allow you to cast the FilterValue and use something other than Like since Like doesn't really work with date types
declare #filterColumn varchar(30) = 'Firstname'
declare #filterValue varchar(120) = 'Dave'
[select statement].....
where
((#filterColumn = 'Firstname' and Firstname like #filterValue) or #filterColumn <> 'Firstname')
and ((#filterColumn = 'Lastname' and Lastname like #filterValue) or #filterColumn <> 'Lastname')
and ((#filterColumn = 'StatusId' and u.Status like #filterValue) or #filterColumn <> 'StatusId')
and ((#filterColumn = 'CreatedDate' and u.CreatedDate = #filterValue) or #filterColumn <> 'CreatedDate')
option (recompile)
This is old, but very relevent to the area of SQL you're headed to:
http://www.sommarskog.se/dyn-search.html
For now though, the syntax you're trying to accomplish is...
WHERE
CASE #filterColumn
WHEN 'Firstname' THEN Firstname
WHEN 'Lastname' THEN Lastname
WHEN 'StatusId' THEN u.Status
WHEN 'CreatedDate' THEN u.CreatedDate
END
LIKE
#filterValue
This assumes that all the columns can be cast to the same relevant datatype; a string.
Be warned though, as per comments on your question, and the article linked at the top of my answer, this pattern is far from ideal, from a SQL engine perspective.
As I mentioned, I would avoid this idea, and instead use 4 parameters. As it seems, however, that you need at least 1 not NULL value, and likely will be only provided 1 parameter (though this works for 1+), I suggest a dynamic statement, so that you don't have poorly cached query plans. This ends up with a query like the following:
--These are your parameters
DECLARE #FirstName nvarchar(50),
#LastName nvarchar(50),
#StatusId int,
#CreatedDate date;
--NOw for the solution
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SET #SQL = N'SELECT {Your Columns}' + #CRLF + --obviously replace with your actual columns
N'FROM dbo.YourTable' + #CRLF +
N'WHERE ' +
STUFF(CONCAT(CASE WHEN #FirstName IS NOT NULL THEN #CRLF + N' AND FirstName = #FirstName' END,
CASE WHEN #LastName IS NOT NULL THEN #CRLF + N' AND FirstName = #FirstName' END,
CASE WHEN #StatusID IS NOT NULL THEN #CRLF + N' AND FirstName = #FirstName' END,
CASE WHEN #CreatedDate IS NOT NULL THEN #CRLF + N' AND FirstName = #FirstName' END),1,8,N'') + N';';
EXEC sys.sp_executesql #SQL, N'#FirstName nvarchar(50), #LastName nvarchar(50), #StatusId int, #CreatedDate date', #FirstName, #LastName, #StatusID, #CreatedDate;
I'm trying to create a searchstring that is a bit dynamic, and i'm trying to work around a large CASE WHEN scenario, but before i resort to doing a CASE WHEN i'm trying my luck here.
I've tried to execute it in string format "exec('code')" where it works, but then i get another issue with getdate() that i wont go into details with.
DECLARE #ProductLines nvarchar(50) = 'usr_author'
DECLARE #searchProductlines nvarchar(50) = 'hc'
SELECT TOP 20
Productid as Produktid,
usr_Author AS Author,
Header AS Title,
usr_Publisher AS Publisher,
CustomerId AS Customerid, FROM Products
WHERE
(#ProductLines Like '%' + #searchProductlines + '%')
I've scraped away all other code that isn't relevant here. What i want to do is declare #ProductLines as the column 'usr_author' so i in the WHERE clause can use #ProductLines as an dynamic column picker in a drop down menu later.
however, this doesnt work. if i write usr_Author instead of #ProductLines, i get the results i need but then it's a static solution, rather than a dynamic solution. what is best practice in this situation?
You can only replace constant values using parameters. You cannot replace identifiers -- including column names, table names, and so on.
You can do this dynamically as:
DECLARE #col nvarchar(50) = 'usr_author'
DECLARE #search nvarchar(50) = 'hc'
DECLARE #sql NVARCHAR(MAX);
SET #sql = '
SELECT TOP 20
Productid as Produktid,
usr_Author AS Author,
Header AS Title,
usr_Publisher AS Publisher,
CustomerId AS Customerid
FROM Products
WHERE #col Like ''%'' + #search + ''%''
';
SET #sql = REPLACE(#sql, '#col', #col);
EXEC sp_executesql #sql,
N'#search nvarchar(50)',
#search=#search;
By declaring this string, you're now comparing it as a string, not a column. Try a more dynamic SQL approach:
DECLARE #ProductLines nvarchar(50) = 'usr_author'
DECLARE #searchProductlines nvarchar(50) = 'hc'
DECLARE #sql1 nvarchar(500);
select #sql1 = 'SELECT TOP 20 Productid as Produktid, usr_Author AS Author, Header AS Title, usr_Publisher AS Publisher, CustomerId AS Customerid into #temptab1 FROM Products WHERE (' + #ProductLines + 'Like ''%' + #searchProductlines + '%'')'
exec( #sql1 )
select *
from #temptab1
Disclaimer: Not injection proof in the slightest, just a concept
create procedure dbo.uspSearch(
#searchProductId int = 0,
#searchAuthor nvarchar(50) = '',
#searchHeader nvarchar(50) = '',
#searchPublisher nvarchar(50) = '',
#searchCustomerId int = 0
) as
begin
set nocount on;
select top 20
Productid as Produktid,
usr_Author AS Author,
Header AS Title,
usr_Publisher AS Publisher,
CustomerId AS Customerid
from Products
where Productid = case when #searchProductId > 0 then #searchProductId else Productid end
and usr_Author like case when #searchAuthor <> '' then '%' + #searchAuthor + '%' else usr_Author end
and Header like case when #searchHeader <> '' then '%' + #searchHeader + '%' else Header end
and usr_Publisher like case when #searchPublisher <> '' then '%' + #searchPublisher + '%' else usr_Publisher end
and CustomerId = case when #searchCustomerId > 0 then #searchCustomerId else CustomerId end;
end;
go;
declare #searchProductId int = '1'
declare #searchAuthor nvarchar(50) = 'hc'
declare #searchHeader nvarchar(50) = 'test'
declare #searchPublisher nvarchar(50) = 'test'
declare #searchCustomerId int = '1';
exec dbo.uspSearch #searchProductId, #searchAuthor, #searchHeader, #searchPublisher, #searchCustomerId;
Besides the fact, that the whole approach has a certain smell, the best answer will be: Use dynamically created SQL (if you really have / want to stick to this). There are answers already...
Just for fun, there is a fully generic approach using XML like here:
I want to use a statement like this:
SELECT *
FROM sys.objects
WHERE [name]='sysrowsets';
...With generically defined parameters
DECLARE #ColumnName VARCHAR(100)='name';
DECLARE #SearchValue VARCHAR(100)='sysrowsets';
SELECT
(
SELECT *
FROM sys.objects
FOR XML PATH('o'),TYPE
).query('/*[*[local-name()=sql:variable("#ColumnName") and text()[1]=sql:variable("#SearchValue")]]')
The result is the same row as above, but as XML (which can be transformed into a tabular set again).
<o>
<name>sysrowsets</name>
<object_id>5</object_id>
<schema_id>4</schema_id>
<parent_object_id>0</parent_object_id>
<type>S </type>
<type_desc>SYSTEM_TABLE</type_desc>
<create_date>2012-09-02T23:08:12.370</create_date>
<modify_date>2012-09-02T23:08:15.340</modify_date>
<is_ms_shipped>1</is_ms_shipped>
<is_published>0</is_published>
<is_schema_published>0</is_schema_published>
</o>
As mentioned above, this is not the recommended approach, it will be very slow. I just felt the need to say something against the impossible statements :-)
UPDATE: Tabular result and still generic...
You can use something like this, just to articulate a predicate against a unique value (in this case object_id)
SELECT o.*
FROM sys.objects o
WHERE o.object_id=
(
SELECT *
FROM sys.objects
FOR XML PATH('o'),TYPE
).query('/*[*[local-name()=sql:variable("#ColumnName")
and text()[1]=sql:variable("#SearchValue")]]')
.value('(/o/object_id/text())[1]','bigint');
I want use Dynamic filter in sql Procedure
,like this
Select * from Table Where #Filter
Can I Write Like That Or Was Diffrent Ways to Use
I must Use this Syntax because I Want Remove Select in Application and Use Procedure.
CREATE PROCEDURE SP_DynamicFilter
(
-- Optional Filters for Dynamic Search
#COLUMN1 INT = NULL,
#COLUMN2 NVARCHAR(50) = NULL,
#COLUMN3 NVARCHAR(50) = NULL,
#COLUMN4 NVARCHAR(50) = NULL
)
AS
BEGIN
SELECT *
FROM TableName
WHERE
(#COLUMN1 IS NULL OR Column1 = #COLUMN1)
AND (#COLUMN2 IS NULL OR Column2 LIKE '%' + #COLUMN2 + '%')
AND (#COLUMN3 IS NULL OR Column3 LIKE '%' + #COLUMN3 + '%')
AND (#COLUMN4 IS NULL OR Column4 LIKE '%' + #COLUMN4 + '%')
END
CREATE PROCEDURE spProcedurName
#Filter datatype
AS
BEGIN
SELECT *
FROM Table
WHERE columnName= #Filter
END
You can paramater like that.
#Query='
Select * from table' + #Filter
exec #Query
What am I missing on my query below?
When I'm passing parameter for (#Status) 'All' , there were no results even though the #status parameter condition is satisfied.
Since when I pass 'All' it will be changed to (un-assigned, assigned). But when I'm passing Assigned/ Un-Assigned directly, it works fine.
ALTER PROCEDURE [dbo].[sp_File_Search]
#Uploaded AS INT = null,
#File_Name AS VARCHAR(150) = null,
#Upload_DT AS DATETIME = null,
#Status AS VARCHAR(150) = null,
#St1 AS VARCHAR(15) = null,
#St2 AS VARCHAR(15) = null
AS
If #Status = 'All'
BEGIN
SET #St1 = 'Un-Assigned'
SET #St2 = 'Assigned'
END
ELSE
BEGIN
SET #St1 = #Status
SET #St2 = #Status
END
--ON UPLOAD DATE
Begin
if #Uploaded= 1
Begin
SELECT [FILE_ID],[FILE_NAME], [UPLOAD_DT] FROM [dbo].[FILE]
WHERE (
[FILE_NAME] like '%' + #File_Name + '%'
OR #File_Name IS NULL
)
AND (
[UPLOAD_DT] between #Upload_DT + '00:00:00' and #Upload_DT + '23:59:59'
OR #Upload_DT IS NULL OR #Upload_DT = ''
)
AND (
[STATUS] IN (#St1,#St2)
Order by Upload_DT desc
END
-- ON OR BEFORE UPLOAD DATE
if #Uploaded=2
Begin
SELECT [FILE_ID],[FILE_NAME], [UPLOAD_DT]
FROM [cobind].[FILE]
WHERE (
[FILE_NAME] like '%' + #File_Name + '%'
OR #File_Name IS NULL
)
AND (
[UPLOAD_DT] between #Upload_DT + '00:00:00' and #Upload_DT + '23:59:59'
OR [UPLOAD_DT] < #Upload_DT
OR #Upload_DT IS NULL OR #Upload_DT = ''
)
AND (
[STATUS] IN (#St1,#St2)
)
Order by Upload_DT desc
END
-- ON OR AFTER UPLPOAD DATE
if #Uploaded=3
Begin
SELECT [FILE_ID], [FILE_NAME], [UPLOAD_DT]
FROM [dbo].[FILE]
WHERE (
[FILE_NAME] like '%' + #File_Name + '%'
OR #File_Name IS NULL
)
AND (
[UPLOAD_DT] between #Upload_DT + '00:00:00' and #Upload_DT + '23:59:59'
OR [UPLOAD_DT] > #Upload_DT
OR #Upload_DT IS NULL OR #Upload_DT = ''
)
AND (
[STATUS] IN (#St1,#St2)
)
Order by Upload_DT desc
END
END
The IN keyword works with either a subquery or a list. When you create the list of values in the SET #Status statement, you are actually creating a string. The query will try to look for the specific string, and since that string does not exist in the column being searched, there won't be any results.
You can work around this by using dynamic SQL. Basically, create a variable to hold the part related to #Status, create the query itself as an NVARCHAR string and append your variable clause to it. Finally, use sp_executesql to run the query, something like this:
If #Status = 'All'
SET #Status = '(''Un-Assigned'',''Assigned'')'
declare #query nvarchar(1000) = 'SELECT * FROM [dbo].[CoM_REPO] WHERE [STATUS] IN ' + #Status + ' Order by UploadDATE desc'
exec sp_executesql #query
Are you getting any error message ?
I guess its because your query says [STATUS] IN (#Status) , meaning () are already there, so if you pass Assigned , it would be coming as [STATUS] IN ('Assigned'), whereas when you are passing 'All', it may be coming as [STATUS] IN ((''Un-Assigned'',''Assigned'')) with double brackets, just try to remove the brackets.
I have a table with varbinary(max) column and nvarchar(max) column. One of them is null and the other has a value.
I would like to return the column that has the value as a varbinary(max) column. So far I have tried this, that does not work:
SELECT
A =
CASE A
WHEN NULL THEN B
ELSE A
END
FROM Table
SELECT COALESCE(A, CAST(B As varbinary(max)))
UPDATE: In response to comments (thanks) and assuming B is the nvarchar(max) column, I have moved the CAST inside the COALESCE
Try SELECT ISNULL(A, cast(B AS varbinary(max))) FROM TABLE
Your case statement evaluates to the dreaded A = NULL:
CASE A WHEN NULL THEN B ELSE A END
Is the same as:
CASE WHEN A = NULL then B ELSE A END
One way to fix this is to use A IS NULL, like:
CASE WHEN A IS NULL THEN B ELSE A END
Or even simpler:
COALESCE(A,B)
Both the when and the coalesce will assume the data type of the first argument. To cast the result to varbinary, you can place the varbinary column first, or explicitly cast:
COALESCE(CAST(A AS VARBINARY(MAX)),B)
here is the full code of create table and insert value and apply my code and only retrieve not null value
CREATE TABLE [dbo].[SUPPLIER](
[ID] [int] IDENTITY(1,1) NOT NULL,
[SUPPLIER_NAME] [varchar](100) NOT NULL,
[ADDRESS] [varchar](150) NULL,
[CREATE_DATE] [datetime] NULL,)
INSERT INTO [MyPayrol].[dbo].[SUPPLIER]
([SUPPLIER_NAME]
,[CREATE_DATE])
VALUES
('Khaled Nabil'
,GETDATE())
declare #inumberofcolumn int
select #inumberofcolumn= count(*)
from sys.columns where OBJECT_NAME(object_id) = 'supplier'
declare #nameofcolumn varchar(100)
set #nameofcolumn =''
declare #counter int
set #counter=1
declare #colname varchar(100)
declare #statment varchar(100)
declare #value varchar(100)
while #counter <=#inumberofcolumn
begin
select #colname= COL_NAME(object_id('[dbo].[SUPPLIER]'),#counter)
declare #data table ([value] varchar(100))
--set #statment = 'select '+#colname+' from [dbo].[SUPPLIER]'
insert #data exec ('SELECT top 1 '+ #colname +' from [dbo].[SUPPLIER]')
select #value = [value] from #data
if #value is not null
begin
if #counter = 1
begin
set #nameofcolumn = #nameofcolumn + #colname
end
else
begin
set #nameofcolumn = #nameofcolumn + ','+ #colname
end
end
set #counter = #counter+1
end
execute ('select '+#nameofcolumn+' from [dbo].[SUPPLIER]')