views:

241

answers:

4

Hello,

I would like to check if there is a preferred design pattern for implementing search functionality with multiple optional parameters against database table where the access to the database should be only via stored procedures.

The targeted platform is .Net with SQL 2005, 2008 backend, but I think this is pretty generic problem.

For example, we have customer table and we want to provide search functionality to the UI for different parameters, like customer Type, customer State, customer Zip, etc., and all of them are optional and can be selected in any combinations. In other words, the user can search by customerType only or by customerType, customerZIp or any other possible combinations. There are several available design approaches, but all of them have some disadvantages and I would like to ask if there is a preferred design among them or if there is another approach.

  1. Generate sql where clause sql statement dynamically in the business tier, based on the search request from the UI, and pass it to a stored procedure as parameter. Something like @Where = ‘where CustomerZip = 111111’ Inside the stored procedure generate dynamic sql statement and execute it with sp_executesql. Disadvantage: dynamic sql, sql injection

  2. Implement a stored procedure with multiple input parameters, representing the search fields from the UI, and use the following construction for selecting the records only for the requested fields in the where statement.

WHERE

        (CustomerType = @CustomerType OR @CustomerType is null )

AND      (CustomerZip = @CustomerZip OR @CustomerZip is null )

AND   …………………………………………

Disadvantage: possible performance issue for the sql.

3.Implement separate stored procedure for each search parameter combinations. Disadvantage: The number of stored procedures will increase rapidly with the increase of the search parameters, repeated code.

A: 

The Query Object pattern.

RedFilter
I would agree - dynamic query building with something like Linq is perfect for this, but I don't think it fits with his requirement that it be done via a sproc.
Eric Petroelje
+2  A: 

This is the best article describing the subtle performance implications of how to do this in SQL: Dynamic Search Conditions in T-SQL by Erland Sommarskog. It covers every method and gives PROs and Cons of each method in great detail.

KM
+1  A: 

Method 1: dynamic SQL can take parameters, its pretty trivial to do and pretty much eliminates the risk of SQL injection. The best argument against dynamic SQL is how non-trivial statements can require some complex logic to generate, although this is a non-issue too if you're using a decent ORM.

NHiberante and LinqToSql construct dynamic SQL behind the scenes, and they aren't riddled with security holes. In my opinion, you're best considering one of these two technologies before rolling your own DAL.

Method 2: I have personally used method two in the past with no problems. You commented on the "possible performance issue for the sql", but have you profiled? Compared execution plans? In my own experience, their has been little to no performance hit using the @param is null OR col = @param approach. Remember, if it takes you 10 hours of developer time to optimize code to save 10 microseconds a year of execution time, your net savings is still almost -10 hours.

Method 3: Combinatorial explosion. Avoid at all costs.

Juliet
I'm surprised you say there's no performance hit with #2, because it will force a full index scan where an index seek might have been possible. If you can turn it into a range query instead, i.e. `CustomerType >= ISNULL(@CustomerType, MinValue) AND CustomerType <= ISNULL(@CustomerType, MaxValue)` then you'll usually avoid the perf hit.
Aaronaught
A: 

I posted this as a comment, but I realized it should probably be an answer.

It's not good to write predicates as WHERE @Param IS NULL OR Column = @Param, because the optimizer usually considers it to be non-sargable.

Try this experiment: Take your most populated table, and try to query just for the Primary Key field, which should be your clustered index:

DECLARE @PrimaryKey int
SET @PrimaryKey = 1

SELECT CoveredColumn
FROM Table
WHERE @PrimaryKey IS NULL
OR PrimaryKeyColumn = @PrimaryKey

SELECT CoveredColumn
FROM Table
WHERE PrimaryKeyColumn >= ISNULL(@PrimaryKey, 0)
AND PrimaryKeyColumn <= ISNULL(@PrimaryKey, 2147483647)

Both of these SELECT statements will produce identical results, assuming that the PK column is a non-negative int. But pull up the execution plan for this and you'll see a huge difference in cost. The first SELECT does a full index scan and typically takes up about 90% of the query cost.

When you want to have optional search conditions in SQL, and you can't use dynamic SQL, it's best for performance if you can turn it into a range query instead using ISNULL. Even if the range is huge (literally half the range of an int here), the optimizer will still figure it out when the optional parameter is used.

Aaronaught
Your approach is documented in the link I provide in my answer to this question (http://www.sommarskog.se/dyn-search-2005.html#Umachandar), and does not work with nullable columns. Also, if you are on the proper version of SQL Server 2008 and use `OPTION (RECOMPILE)` on your query, the first query version will actually optimize to `WHERE PrimaryKeyColumn = @PrimaryKey` or use no WHERE clause if the @PrimaryKey is NULL. http://stackoverflow.com/questions/2773232/sql-server-2008-conditional-query/2773268#2773268
KM
@KM: I'm using the "proper" version of SQL Server 2008 and adding `WITH RECOMPILE` does not change the plan at all. Also, what exactly do you mean when you say that this does not work with nullable columns? All you'd need to do is add another condition, `OR PrimaryKeyColumn IS NULL AND @PrimaryKey IS NULL`, which *will* get optimized into an index seek (if applicable).
Aaronaught