How to implement a more efficient search?
The answer partly depends on what your definition of efficient is. I suspect you mean less code and fewer if blocks etc. But fundamentally, running a new SELECT *
query to apply a filter is inefficient because your base data set can be all the rows and you just fiddle with the users View of it.
I have a DB with random data in columns for Fish, Color (string), Bird, Group (int) and Active which should be similar enough for Name, Age and Gender in the question - or that other long thing at the bottom.
DataTable
Fill a datatable and bind it to a DGV:
' form level object
Private dtSample As DataTable
...
' elsewhere
Dim sql = "SELECT Id, Name, Descr, `Group`, Fish, Bird, Color, Active FROM Sample"
Using dbcon As MySqlConnection = New MySqlConnection(MySQLConnStr)
' create SELECT command with the Query and conn
Dim cmd As New MySqlCommand(sql, dbcon)
...
daSample.Fill(dtSample)
daSample.FillSchema(dtSimple, SchemaType.Source)
End Using
dgv2.DataSource = dtSample
Going forward, we can filter the user's view of that table without issuing a new query.
Filter Controls
If some of the fields are limited to certain selections, for instance Gender
, you can use a ComboBox
instead of a TextBox
. This is to help the user succeed and avoid typos (Make or Mael instead of Male; or here, correctly spelling Baracuda I mean Baraccuda, er Barracuda correctly.
For illustration purposes, Fish is something where the user can type in anything at all, but Bird is constrained to a set of choices. If there is a Bird
table, cboBird
can be bound or populated from it. But you may also be able to populate it from the master/base table:
Dim birds = dtSample.AsEnumerable.Where(Function(d) d.IsNull(5) = False).
Select(Function(d) d.Field(Of String)("Bird")).
Distinct.
ToArray()
cboBird.Items.AddRange(birds)
If "Finch" is a legal choice but there are none in the database, it wont show in the list. Depending on the app, this can be a Good Thing:
- If the user filters on
Finch
and there a no resulting records, you won't need a MessageBox
or StatusBar
message explaining the empty result set.
- If something is not in the list, you are signalling up front that there are none of those. It then becomes a matter of training why a known element isnt in the list.
- On the other hand, you'd have to repopulate those filter controls each time before they are used in case new records were added recently. If the controls are on a
Dialog
or different TabPage
, this is easy to do as needed.
- It isnt always applicable, but it can help the user avoid typos.
It depends on the app whether either method is of value.
DBNull / 'none'
I am not sure why you are adding 'none' to each clause. If someone want to see all the 'John` or all the 'Cod' records, it doesn't seem like they would also be interested in 'none'. Personally, Null/DBNull seems a better way to handle this, but it is easy to add or not add either form.
It would seem more valuable to filter to just those with DBNull/None. The code above for the Bird List filters out DBNull
and I would do so for none
as well. Then, before the result is added to the ComboBox
, add a `None' item first so it is at the top.
Again it depends on what the app does; Or = 'None'
, may make perfect sense in this case.
Filter
Using a TextBox
for Fish and Group, a ComboBox
for Bird and Color and a CheckBox
for Active, the code can form the filter thusly:
Dim filterTerms As New List(Of String)
Dim filterFmt = "{0} = '{1}' "
' OR:
' Dim filterFmt = "{0} = '{1}' OR {0} Is Null"
' OR:
' Dim filterFmt = "{0} = '{1}' OR {0} = 'none'"
If String.IsNullOrEmpty(tbSearchFish.Text) = False Then
Dim txt = tbSearchFish.Text.Replace("'", "''")
filterTerms.Add(String.Format(filterFmt, "Fish", txt))
End If
If cboBird.SelectedIndex > -1 Then
filterTerms.Add(String.Format(filterFmt, "Bird", cboBird.SelectedItem.ToString))
End If
If String.IsNullOrEmpty(tbGroup.Text) = False Then
Dim n As Int32
If Int32.TryParse(tbGroup.Text, n) Then
filterTerms.Add(String.Format(filterFmt, "[Group]", n))
End If
End If
If cboColor.SelectedIndex > -1 Then
filterTerms.Add(String.Format(filterFmt, "Color", cboColor.SelectedItem.ToString))
End If
If chkActive.Checked Then
' NOTE: I do not have TreatTinyAsBoolean turned on
' for some reason
filterTerms.Add(String.Format(filterFmt, "Active", "1"))
End If
If filterTerms.Count > 0 Then
Dim filter = String.Join(" AND ", filterTerms)
dtSample.DefaultView.RowFilter = filter
Dim rows = dtSample.DefaultView.Count
End If
- Use whichever
filterFmt
is appropriate for what the app needs to do
- A filter term is only added to the list if the related control has a value (as per above, this could include a 'None').
- For the
TextBox
, it escapes any embedded ticks such as might be found in names like O'Malley
or D'Artgnan
. It replaces one tick with two.
- Since Group is a numeric, a valid
Int32
input is tested
- If there are elements in the
filterTerms
list, a filter string is created
- The filter is applied to the
DefaultView.Filter
(you can use also use a DataView
or a BindingSource
) so that the code need not query the database to provide filter capabilities.
Rows
will tell you how many rows are in the current View.
The only halfway tricky one is a Boolean like Gender or Active because those actually resolve to three choices: {Any/Either, A, B}
. For that, I would use a ComboBox
and ignore it for SelectedIndex 0 as well. I didn't bother with this because the Combo
concept is amply covered. Result:

Is it More "Efficient"?
It still depends.
It doesn't re-query the database to get rows the app can already have.
No new DBConnection
, DBCommand
or other DBProvider objects are created, just a list.
No need to dynamically create a SQL statement with N parameters in a loop to avoid SQL injection/special words and chars.
It doesn't even query the database for the items for the filter terms. If there is a static list of them in the DB, they could be loaded once, the first time they use the filters.
It is easy to remove the filter, no need to query yet again without WHERE
clauses.
A ComboBox
where applicable helps the user find what they want and avoid typos.
Is the SQL "cleaner". more "efficient? The code doesn't really mess with new SQL, just some WHERE clauses.
Is there less code? I have no idea since we just see the result. It doesnt string me as a lot of code to do what it does.