"Dynamic" WHERE clause in Query

Arg81

Well-known member
Joined
Mar 11, 2005
Messages
949
Location
Midlands, UK
Programming Experience
1-3
I've been requested by users of one of my apps to allow them to search on a number of fields.
This isn't dymanic in the sense that the parameter can change, it's dynamic in the sense that what they want to search for can change.

I.E.
I have 5 fields that are searchable, all comboboxes with different values. I know how to get the SQL parameter query to work using whatever value is selected.

However, User A might want to select values for only 2 fields and leave the other 3 blank.
User B might want to select values for 3 fields and leave the other 2 blank.
User C might want to select values for all 5 fields


I've tried following Implementing Dynamic WHERE-Clause in Static SQL - CodeProject

but none of them seem to work.

Any advice greatly appriciated.
 
Sorry to come to the party late, but I really don't recommend what's been done here..

All that has been achieved with the stored procedure is shifting the string concatenation to the database, and youre still reliant on the database converting parameters to a string datatype. Further youre building a query that forces wildcards on the start and end of the term, which can be slow, much slower than letting the users specify the wildcard themselves if they need it, and youre including all the search terms. Adding the stored procedure is just a level of indirection; it's not needed to enable the query.

I'd have code like this:

VB.NET:
'i'll explain why 1=1 in a moment
Dim cmd As New SqlCommand
cmd.CommandText = "SELECT * FROM tblPerson WHERE 1=1"

If Not String.IsNullOrEmpty(firstNameTextBox) Then
  cmd.CommandText = cmd.CommandText & " AND firstname = @firstname"
  cmd.Parameters.AddWithValue("@firstname", firstNameTextBox.Text)
End If

If Not String.IsNullOrEmpty(lastNameTextBox) Then
  cmd.CommandText = cmd.CommandText & " AND lastname = @lastname"
  cmd.Parameters.AddWithValue("@lastname", lastNameTextBox.Text)
End If

If isAliveCombo.Value <> "" Then
  cmd.CommandText = cmd.CommandText & " AND isAliveBoolean = @isAlive"
  cmd.Parameters.AddWithValue("@isAlive", isAliveCombo.Value = "Yes")
End If

We need the 1=1 because every other clause is ANDed on. and its a syntax error to say:
SELECT * FROM table WHERE AND lastname = @lastname

we need a useless clause in there that wont affect the results but makes our concatentating life easy, otherwise we get into messy code, of "if the number of parameters is zero, dont put the AND otherwise do put the and"

Notice that i took your combo which apparently is 3 state (not specified, yes, no) for "is the person alive" and compared its value with "Yes" to give a boolean in the query assuming your table column is a boolean

At the end of this you will have a query that specifies only the parameters filled in.. 3 of 5 text boxes filled? your query will have 3 clauses (plus the useless 1=1) and 3 parameters

-

You also have the option of writing the whole query in advance:

SELECT * FROM tblPerson WHERE lastname LIKE @lastname AND firstname LIKE @firstname AND (isAlive = @isAlive OR @isAlive IS NULL)

Note that this form of query, because it is fixed (not dynamic), can be programmed into a tableadapter. The database might have more trouble optimizing it though, possibly leading to slower performance or wrong index selection.
Because it is fixed, put the values into variables then call the relevant fill method

If the user doesnt specify a last name, set it to a value of ;%':
VB.NET:
Dim last as String = lastnameTextbox.text

if String.IsNullOrEmpty(last) Then 
  last = "%"
end if
(Similar code for first)

If the user doesnt specify the isAlive, set it to null:
VB.NET:
Dim isalive as Nullable(Of Boolean) = nothing
If NotString.IsNullOrEmpty(isaliveCombo.SelectedText) Then
    isalive = (isaliveCombo.SelectedText = "Yes") 'perform comparison with "yes" and store resulting boolean
end if

Call the tableadapter search:
VB.NET:
myTA.FillByVarious(theDataTable, first, last, isalive)


-
remember that you can take the former style and use a dataadapter to fill your table..
 
good to see you're still about fixing my bad habits ;)

I didn't realise I could code it all into the dataTable then use "%" to return all rows for that parameter.

So the SP is OK on the SQL side of things? The only issue being the way I pass the parameters?
EDIT: Just looked, I don't actually need the SP any more do I?
 
Last edited:
good to see you're still about fixing my bad habits ;)

I didn't realise I could code it all into the dataTable then use "%" to return all rows for that parameter.

So the SP is OK on the SQL side of things? The only issue being the way I pass the parameters?

The SP is a manifestation of the second form; putting all parameters into the query and then wildcarding out the ones you don't want. This is perfectly possible in VB code as I've demonstrated

For strings, the % is just a wildcard that matches all strings. Parameter values can contain %. Tell your users that % is wildcard, or if they are more familiar with * as wildcard, write this code:

cmd.parameters.AddWithValue("paramname", sometextbox.Text.Replace("*", "%") )

myTA.FillByVarious(dt, text.Replace("*","%"))


For non strings, wildcard is achieved by assigning some value (that the user cannot enter directly) to be wildcard. In my case i used null:

WHERE (col = @val OR @val IS NULL)

If user enters nothing, you pass in NULL, and the @val IS NULL is always true,. hence wildcard!
 
Just edited last comment, realised that don't need that SP.

I'm going to try the first way you explained, just because if I need to be able to add more search options I can.
Although saying that, it may have to be the 2nd way, as I've just seen one of the search fields needs to come from a related table...

All fun, will let you know how I get on.
 
Now this has got a little more complicated. This might need to go into a new thread.

Both your ways work fine and I fully understand them. However I'm now trying to sort out a search on a related table.


A few tables I have
Project (ProjectID, RecipeID.......)
Recipe (RecipeID, RecipeName, LiveRecipe, RecipeTypeID)
Ingredient (IngredientID, IngredientName)
RecipeIngredients (RecipeID, IngredientName, IngredientWeight)

I am trying to link Project to RecipeIngredients on RecipeID

On my old search, it was simple... users wanted to search for all Projects containing XYZ Ingredient:
VB.NET:
SELECT     Project.*
FROM         Project INNER JOIN
                      RecipeIngredient ON Project.RecipeID = RecipeIngredient.RecipeID
WHERE     (RecipeIngredient.IngredientID = @IngredientID)
User selects an Ingredient... query returns all Projects. Easy.

However... now I'm trying to set up the dynamic search.

If I link Project to RecipeIngredient, no matter if I use INNER, LEFT OUTER, RIGHT OUTER ... I always end up with around 3 times as many records as there are Projects.

So currently, I do a search and select Ingredient and isLive = true. That returns the correct amount of rows.
If I leave Ingredient as blank and search only on the isLive = true, I end up with over 1000 rows instead of 205... obviously because it links each row in the RecipeIngredients table that matches the Projects table on RecipeID.

Have I done something silly or do I need to create a really complex query?
 
Although saying that, it may have to be the 2nd way, as I've just seen one of the search fields needs to come from a related table...

Related tables wont make much difference.. Allow me to alter your thinking about SQL:

SELECT (width altering subset) FROM (rectangular block of data) WHERE (height altering condition)


Thats an SQL, it takes in a rectangular block of data and applies some filters, and you select some, all or more columns, and you get out of it a rectangular block of data

This means you can stuff that new rectangular block into another query

No matter how many JOINS you write in (rectangular block of data) the JOINs still only achieve a.. wait for it... rectangular block of data
If you use union, it makes the block grow higher.. Join -> wider, but still it's ALL JUST RECTANGULAR BLOCKS OF DATA. A table is a rectangular block of data. A subquery is a rectangular block of data. Your mom is a rect.. ah, forget that one.


What we have discussed here goes exclusively in the (height altering) WHERE clause: how can adding more width to the rectangular block of data affect anything?

Think about it..
 
Have I done something silly or do I need to create a really complex query?

You've done something silly. You've specified your JOINs incompletely and ended up with a cartesian join somewhere

If I link Project to RecipeIngredient, no matter if I use INNER, LEFT OUTER, RIGHT OUTER ... I always end up with around 3 times as many records as there are Projects
The join type has nothing to do with the number of rows. If there is 1 project, with a recipeID of 7, and then 3 entries in RecipeIngredients and you join such that there is 1 project row and 3 recipeingredients rows that match the join criteria, the project row will be repeated 3 times

Even more confusing, you might not be selecting the column that is different:

ProjectID,Name
1,Teach Them Pancakes

RecipeID,ProjectID,RecipeIngredients
1,1,Water
1,1,Flour
1,1,Eggs

SELECT Name FROM projects INNER JOIN recipes USING(projectID)
->
Teach Them Pancakes
Teach Them Pancakes
Teach Them Pancakes

SELECT Name FROM projects INNER JOIN recipes USING(projectID)
->
1,Teach Them Pancakes,1,Water
1,Teach Them Pancakes,1,Flour
1,Teach Them Pancakes,1,Eggs


3 matching rows, but they look like duplicates until you include the column that differs

Last pointer: For the love of decent progrmming, do not just whack a DISTINCT in there to squish the duplicates. Work out which table youre joining on a condition that allows 1:M condition to persist
 
Back
Top