0

We have a requirement to implement search function in a web page. So we have created a stored procedure to retrieve the records. The logic is, when i select a input parameter Sp returns filtered record for the parameter else it retrieve all the available records.

enter image description here

for eg: if i give,

EXEC [WB_GetClassesByLocation_new2] null,null,null,'null',null,NULL,'N','N','N','N','N','N','N',NULL,null,null,null,null,null

(N is the default value for Days field) SP Will return all the available records. if I give,

EXEC [WB_GetClassesByLocation_new2] 1000,null,null,'null',null,NULL,'N','N','N','N','N','N','N',NULL,null,null,null,null,null . SP Will return records for district 1000. i have implemented the below logic

Select distinct c.classID, co.fCourseName as CourseName, StreetAddress + ', ' + l.City as LocationAddress, s.SessionName, sh.fShift as shift, StartTime, EndTime, c.classname, s.SessionID,
        c.StartDate,c.enddate 
        From dbo.vw_Class c 
        Inner Join dbo.lk_Session s 
        On (s.SessionID = c.sessionID) 
        Inner Join dbo.lk_Course co 
        On (co.CourseID = c.CourseID )
        Inner Join dbo.vw_Location l 
        On (l.locationid = c.locationid) 
        Inner Join lk_District d
        On (d.districtID = c.districtId) 
        Inner Join lk_Province p 
        On (p.provik = d.provik) 
        Inner Join lk_Shift sh 
        On (c.shiftid = sh.shiftid)
       where 
          c.DistrictID       =  case  when @Districtid is null   then c.DistrictID   else  @Districtid  end 
         and c.LocationID    =  case  when @locationid is null   then c.LocationID   else  @locationid  end 
         and s.SessionID     =  case  when @sessionid is null    then s.SessionID    else  @sessionid   end 
         and c.CourseID      =  case  when @levelid  is null     then c.CourseID     else  @levelid     end 
         and c.ShiftID       =  case  when @shiftid   is null    then c.ShiftID      else  @shiftid     end 
         and c.StartDate    >=  case  when @startdate is null    then c.StartDate    else  @startdate   end
         and c.EndDate      <=  case when  @enddate is null      then c.EndDate      else  @enddate     end
         and convert(time,c.StartTime) >= case when @starttime is null then convert(time,c.StartTime) else convert(time,@starttime) end
         and convert(time,c.endtime)   <= case when @endtime is null then convert(time,c.endtime) else convert(time,@endtime) end
         and c.Monday    = case  when @day1 = 'N' then c.monday     else  @day1  end 
         and c.Tuesday   = case  when @day2 = 'N' then c.Tuesday        else  @day2  end 
         and c.Wednesday = case  when @day3 = 'N' then c.Wednesday  else  @day3  end 
         and c.Thursday  = case  when @day4 = 'N' then c.Thursday       else  @day4  end 
         and c.Friday    = case  when @day5 = 'N' then c.Friday     else  @day5  end 
         and c.Saturday  = case  when @day6 = 'N'then c.Saturday        else  @day6  end 
         and c.Sunday    = case  when @day7 = 'N' then c.Sunday     else  @day7  end 
         and c.RowStatus    = 'A' 
         ORDER BY co.fCourseName, s.SessionID ,c.ClassName

But the Sp takes too much time to execute. IS this the correct way to implement the "All IF null" logic in sql server? Any other way to do the same?

Baiju Mohan
  • 59
  • 1
  • 8
  • Yes, logically, this is correct. Performance issues can come from a lot of directions. The first thing you should do is look at your query plan (which will be quite large with all those joins) and look for table scans and clustered index scans. What to do next depends on what you find. Also, try commenting out the 2 `and` clauses for `c.StartTime` and `c.endtime`. Because you're converting them to time, those clauses make the query, "non-sargable". That basically means that searching for those clause elements cannot use an index. So they will be slow. – DeadZone Jun 23 '15 at 13:54
  • but those fields are in varchar. thats why i convert it to time for time comparison – Baiju Mohan Jun 23 '15 at 14:01
  • Why are those fields varchar if they are time? – paparazzo Jun 23 '15 at 14:16
  • the application design is like that. the value is " 7:00 AM" etc.. – Baiju Mohan Jun 23 '15 at 14:17
  • Changing those fields to be time would be best, but if you can't do that, then try converting the `@starttime` and `@endtime` to varchar, instead. That way the query optimizer can still try to use an index for those fields if one exists. – DeadZone Jun 23 '15 at 14:22
  • it is possible to make comparisons, when the value is in varchar? For eg if i give start time as "7 AM" and end time as "7 pm", I need all there records between these two timings. – Baiju Mohan Jun 23 '15 at 14:24
  • Well... if the times were stored in 24-hour time, then you could do it. But with the AM and PM monikers, no, it would not work. Sorry. You'll either have to change the column to be a time column or live with those elements being non-sargable. – DeadZone Jun 23 '15 at 14:28
  • Just because time is displayed that way does not mean you have to store it as a varchar. You ask about performance and that is a big performance hit. – paparazzo Jun 23 '15 at 15:21

2 Answers2

2

Here's how I do it:

( @Districtid is null OR c.DistrictID = @Districtid )
AND
( @Locationid is null OR c.LocationID = @Locationid )
AND
...

However, when you have a really large number of these filters, and you're close enough to the tipping point that I would experiment, you're better off creating a dynamic SQL query.

Tab Alleman
  • 31,483
  • 7
  • 36
  • 52
  • I have implemented the logic u mentioned before. but my code didn't work. how can i do it using dynamic query? – Baiju Mohan Jun 23 '15 at 14:03
  • Well if a parameter is NULL, and you don't need to filter by it, just don't include it in the dynamic query at all. But what do you mean your code didn't work? Did you get an error? – Tab Alleman Jun 23 '15 at 14:04
  • 2
    @BaijuMohan, this is (in my opinion) the best approach in terms or readability and maintenance. If it didn't work first time out it's not because what Tab has suggested is incorrect - it's probably due to a problem with your SQL. I'd suggest you post your modified SQL and the error message you get when you try to run it. – amcdermott Jun 23 '15 at 14:07
  • This is an excellent approach to this problem. Gail Shaw has blogged about this very topic and demonstrates a few options for this type of query. http://sqlinthewild.co.za/index.php/2009/03/19/catch-all-queries/ – Sean Lange Jun 23 '15 at 14:13
  • @TabAlleman i got no output when i used the method u suggested – Baiju Mohan Jun 23 '15 at 14:21
  • @sean thanks for the link. let me try if any of the option help me out – Baiju Mohan Jun 23 '15 at 14:22
  • This suggestion has very bad performance , sql thinks that there is an "or" statement and do not use indexes. – Elisheva Wasserman Jun 23 '15 at 14:45
  • @BaijuMohan, if you got no results using this logic, you probably made a mistake in the implementation. If you edit your original post with the query that returned no results, maybe one of us will spot the bug. – Tab Alleman Jun 23 '15 at 15:06
  • @ElishevaWasserman SQL will think there is an OR statement because there is one. An "or" statement causes the query optimizer to not use indexes? Did you test that? Why would an "OR" statement cause SQL to not use indexes? – paparazzo Jun 23 '15 at 15:33
  • Yes, I tried this couple of times, SQl use index when he can easily locate the needed value, in OR statement, the process will have to search again from the beginning to get th other value, so it prefers to scan the table instead. you can see this in Explain Plan tool. – Elisheva Wasserman Jun 23 '15 at 17:06
  • Explain plan? What RDBMS did you test on? – Tab Alleman Jun 23 '15 at 17:27
  • SQL Server query execution plans – Elisheva Wasserman Jun 23 '15 at 17:35
  • @ElishevaWasserman Really, it degrades to TABLE scan? I don't think so. It will degrade to index (not table) scan. And it this is still better than syntax in the OP. – paparazzo Jun 23 '15 at 18:45
  • @Tab Alleman, Yes it was a mistake in my code. I have used in this way. (C.DistrictID is null or (C.DistrictID = @districtid)) . rearrange code as u suggested works but the performance remains same. – Baiju Mohan Jun 24 '15 at 04:47
2

When you have query with lot's of filters, I know 2 options:

  1. Use OR (as suggested above), which can cause the query optimizer to scan the table. See this post for further information :How to Optimize the Use of the "OR" Clause When Used with Parameters (SQL Server 2008)

  2. Use dynamic SQL - which can cause the query optimizer to calculate the execution plan on every single change in every parameter A good option is to use dynamic sql with Bind Variables, this way the optimizer will cache the query and the execution plan.

    declare @sql varchar(500)
    DECLARE @ParmDefinition nvarchar(500);
    
    SET @sql='select * from a where 1=1 '
     if @Locationid  is null
    set @sql=@sql +' and @LocationId is null'
     else
     set @sql=@sql +' and LocationID= @LocationId'
    
      EXECUTE sp_executesql @sql, N'@LocationId int',
                  @LocationID ;
    
Community
  • 1
  • 1
  • can we implement the same in where clause too? – Baiju Mohan Jun 24 '15 at 05:29
  • @BaijuMohan, see the where clause in the beginning of the sentence 'select * from a where 1=1' – Elisheva Wasserman Jun 24 '15 at 05:44
  • what is asked is, when we use multiple filters in where clause using 'and' then is it possible to use dynamic query. or do i need to write separate blocks each condition (for district id, location id, session id ) etc.. – Baiju Mohan Jun 24 '15 at 11:22
  • You need separate block for every condition to build 1 long dynamic query, if you want shorter code you can use XML as parameter and extract name,values and type from it – Elisheva Wasserman Jun 24 '15 at 11:34