[英]How to implement a more efficient search feature?
In my database there are 3 column which is Name, Age, Gender. 在我的数据库中有3列,分别是姓名,年龄,性别。 In the program, I only want to use 1 search button.
在程序中,我只想使用1个搜索按钮。 When the button is clicked, the program determine which 3 of the textbox has input and search for the right data.
单击该按钮时,程序将确定文本框中的哪3个输入并搜索正确的数据。
How do you work with the query? 你如何使用查询? For example if Name and Gender has text, the query :
例如,如果Name和Gender有文本,则查询:
" Select * from table Where (Name = @name) AND (Gender = @gender)
" “
Select * from table Where (Name = @name) AND (Gender = @gender)
”
And when only name is entered, I only query for the name. 当只输入名称时,我只查询名称。 Must I check textbox by textbox whether there is user input and then write multiple query for each of them?
我是否必须按文本框检查文本框是否有用户输入,然后为每个文本框写入多个查询? Or is there a better way to do this?
或者有更好的方法吗?
Edit (29/5/16) : I tried doing this another way like this 编辑(29/5/16):我尝试这样做另一种方式
myCommand = New MySqlCommand("Select * from project_record Where
(FloatNo = @floatNo OR FloatNo = 'None') AND
(DevCompanyName = @devCompanyName OR DevCompanyName = 'None') AND
(DevType = @devType OR DevType = 'None') AND
(LotPt = @lotPt OR LotPt = 'None') AND
(Mukim = @mukim OR Mukim = 'None') AND
(Daerah = @daerah OR Daerah = 'None') AND
(Negeri = @negeri OR Negeri = 'None') AND
(TempReference = @tempRef OR TempReference = 'None')", sqlConn)
But as you can guess already it will not work efficiently as well because if I only enter input for DevType
and leave other textboxes blank, the query will not pull up all the records for DevType
only. 但正如你可以猜到的那样它也不会有效工作,因为如果我只输入
DevType
输入并将其他文本框留空,则查询不会仅为DevType
提取所有记录。 It will just display as no records. 它只会显示为无记录。
Select * from table
Where (Name = @name OR @name is Null)
AND (Gender = @gender OR @gender is Null)
...
it should be one query 它应该是一个查询
Other answers have explained how to simplify the query. 其他答案已经解释了如何简化查询。 It is especially important to get rid of the
ORs
, since they inhibit any use of indexes. 摆脱
ORs
尤其重要,因为它们会抑制任何索引的使用。
Once you have the query build cleanly, you need to think about the dataset and decide which columns are usually used for filtering. 一旦干净地构建了查询,就需要考虑数据集并确定通常用于过滤的列。 Then make a few
INDEXes
for them. 然后为他们做几个
INDEXes
。 You won't be able to provide 'all' possible indexes, hence my admonition that you think about the dataset. 您将无法提供“所有”可能的索引,因此我告诫您要考虑数据集。
When building indexes, you can have single-column or multiple-column indexes. 构建索引时,可以使用单列索引或多列索引。 For your type of data, I would suggest starting with several 2-column indexes.
对于您的数据类型,我建议从几个2列索引开始。 Make sure each index starts with a different column.
确保每个索引都以不同的列开头。
For Where (Name = @name) AND (Gender = @gender)
, here are some notes: 对于
Where (Name = @name) AND (Gender = @gender)
,这里有一些注释:
INDEX(gender) is useless because of low 'cardinality';
INDEX(gender, name) might be useful, but the following would be better:
INDEX(name)
Things like name
and DevCompanyName
are virtually unique, so a 1-column index is probably good. 像
name
和DevCompanyName
这样的东西几乎是唯一的,因此1列索引可能很好。
If you had gender
and age
, then INDEX(age, gender)
might be useful. 如果您有
gender
和age
,那么INDEX(age, gender)
可能会有用。
MySQL will almost never use two indexes for a single SELECT
. MySQL几乎不会为单个
SELECT
使用两个索引。
By the way, the construction of the WHERE
could be done in a Stored Procedure. 顺便说一下,
WHERE
的构造可以在存储过程中完成。 You would need CONCAT
, PREPARE
, etc. 你需要
CONCAT
, PREPARE
等。
(scroll down to see update) (向下滚动以查看更新)
Can you try the following: 你能尝试以下方法吗?
The code looks like this: 代码如下所示:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim Predicate1 As String = Me.TextBox1.Text
Dim Predicate2 As String = Me.TextBox2.Text
Dim Predicate3 As String = Me.TextBox3.Text
Dim PredicateList As New List(Of String)
Dim WhereClause As String
Dim Query As String
If Predicate1 <> String.Empty Then
PredicateList.Add("Name=""" & Predicate1 & """")
End If
If Predicate2 <> String.Empty Then
PredicateList.Add("Age=""" & Predicate2 & """")
End If
If Predicate3 <> String.Empty Then
PredicateList.Add("Gender=""" & Predicate3 & """")
End If
WhereClause = String.Join(" AND ", PredicateList.ToArray)
Query = "SELECT * FROM TABLE WHERE " & WhereClause
MessageBox.Show(Query)
End Sub
Further to the comments re SQL injection, here is an updated sample. 继SQL注入的注释之后,这里有一个更新的示例。
Dim Command As SqlClient.SqlCommand
Dim Predicate1 As String = Me.TextBox1.Text
Dim Predicate2 As String = Me.TextBox2.Text
Dim Predicate3 As String = Me.TextBox2.Text
Dim ParameterList As New List(Of SqlClient.SqlParameter)
Dim PredicateList As New List(Of String)
Dim BaseQuery As String = "SELECT * FROM TABLE WHERE "
If Predicate1 <> String.Empty Then
PredicateList.Add("name = @name")
ParameterList.Add(New SqlClient.SqlParameter("@name", Predicate1))
End If
If Predicate2 <> String.Empty Then
PredicateList.Add("age = @age")
ParameterList.Add(New SqlClient.SqlParameter("@age", Predicate2))
End If
If Predicate3 <> String.Empty Then
PredicateList.Add("gender = @gender")
ParameterList.Add(New SqlClient.SqlParameter("@gender", Predicate3))
End If
Command = New SqlClient.SqlCommand(BaseQuery & String.Join(" AND ", PredicateList.ToArray))
Command.Parameters.AddRange(ParameterList.ToArray)
COALESCE is your friend here. COALESCE是你的朋友。 You can use it to make the where clause ignore comparisons where the parameter is NULL.
您可以使用它来使where子句忽略参数为NULL的比较。
Select * from table Where (Name = COALESCE(@name,table.Name))
AND (Gender = COALESCE(@gender,table.Gender))
So, if the @name
parameter is NULL, COALESCE(@name,table.Name)
will return the value of the 'Name' column of the current row and (Name = COALESCE(@name,table.Name))
will always be true. 因此,如果
@name
参数为NULL,则COALESCE(@name,table.Name)
将返回当前行的“Name”列的值,并且(Name = COALESCE(@name,table.Name))
将始终为真正。
This assumes that if no value is entered in a textbox the corresponding parameter will be NULL. 这假设如果在文本框中没有输入值,则相应的参数将为NULL。 If instead it is a value such as 'None', you can use the
NULLIF
function to map 'None' to NULL 如果它是一个诸如'None'之类的值,
NULLIF
可以使用NULLIF
函数将'None'映射为NULL
Select * from table Where
(Name = COALESCE( NULLIF( @name, 'None'), table.Name))
AND (Gender = COALESCE( NULLIF( @gender, 'None'), table.Gender))
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. 我怀疑你意味着更少的代码和更少的块等等。但从根本上说,运行一个新的
SELECT *
查询来应用过滤器是低效的,因为你的基础数据集可以是所有行,你只是摆弄用户的视图。
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. 我有一个数据库,其中包含Fish , Color (字符串), Bird , Group (int)和Active的列中的随机数据,这些数据应该与问题中的姓名 , 年龄和性别相似 - 或者底部的其他长项。
Fill a datatable and bind it to a DGV: 填充数据表并将其绑定到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. 展望未来,我们可以过滤用户对该表的视图 ,而无需发出新查询。
If some of the fields are limited to certain selections, for instance Gender
, you can use a ComboBox
instead of a TextBox
. 如果某些字段仅限于某些选择,例如
Gender
,则可以使用ComboBox
而不是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. 这是为了帮助用户成功并避免拼写错误( Make或Mael而不是Male ;或者在这里,正确拼写
Baracuda
我的意思是
Baraccuda
,正确的Barracuda 。
For illustration purposes, Fish is something where the user can type in anything at all, but Bird is constrained to a set of choices. 为了便于说明, Fish是用户可以输入任何内容的东西,但Bird被限制为一组选择。 If there is a
Bird
table, cboBird
can be bound or populated from it. 如果有一个
Bird
表,可以从中绑定或填充cboBird
。 But you may also be able to populate it from the master/base table: 但您也可以从master / base表中填充它:
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. 如果“Finch”是合法的选择,但数据库中没有,它将不会显示在列表中。 Depending on the app, this can be a Good Thing:
取决于应用程序,这可能是一件好事:
Finch
and there a no resulting records, you won't need a MessageBox
or StatusBar
message explaining the empty result set. Finch
过滤并且没有结果记录,则不需要MessageBox
或StatusBar
消息来解释空结果集。 Dialog
or different TabPage
, this is easy to do as needed. Dialog
或不同的TabPage
,则可以根据需要轻松完成。 It depends on the app whether either method is of value. 取决于应用程序,这两种方法是否有价值。
I am not sure why you are adding 'none' to each clause. 我不确定你为什么要在每个条款中添加'none'。 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'.
如果有人想要查看所有'John`或所有'Cod'记录,那么他们似乎也不会对'none'感兴趣。 Personally, Null/DBNull seems a better way to handle this, but it is easy to add or not add either form.
就个人而言,Null / DBNull似乎是一种更好的处理方式,但很容易添加或不添加任何一种形式。
It would seem more valuable to filter to just those with DBNull/None. 过滤到只有 DBNull / None的那些似乎更有价值。 The code above for the Bird List filters out
DBNull
and I would do so for none
as well. 上面的鸟列表的代码过滤掉
DBNull
,我会为这样做none
为好。 Then, before the result is added to the ComboBox
, add a `None' item first so it is at the top. 然后,在将结果添加到
ComboBox
,首先添加一个“无”项,使其位于顶部。
Again it depends on what the app does; 这又取决于应用程序的功能;
Or = 'None'
, may make perfect sense in this case. Or = 'None'
,在这种情况下可能非常有意义。
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: 使用
TextBox
鱼和集团 ,一个ComboBox
的鸟和颜色和CheckBox
为主动 ,代码可正是如此形成的过滤器:
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
filterFmt
is appropriate for what the app needs to do filterFmt
TextBox
, it escapes any embedded ticks such as might be found in names like O'Malley
or D'Artgnan
. TextBox
,它可以逃避任何嵌入式刻度,例如可能在O'Malley
或D'Artgnan
等名称中找到的。 It replaces one tick with two. Int32
input is tested Int32
输入 filterTerms
list, a filter string is created filterTerms
列表中有元素,则会创建一个过滤字符串 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. DefaultView.Filter
(您也可以使用DataView
或BindingSource
),以便代码无需查询数据库即可提供过滤功能。 Rows
will tell you how many rows are in the current View. Rows
会告诉你有多少行是在当前视图。 The only halfway tricky one is a Boolean like Gender or Active because those actually resolve to three choices: {Any/Either, A, B}
. 唯一有道理的一个是类似于性别或主动的布尔值,因为那些实际上解决了三个选择:
{Any/Either, A, B}
。 For that, I would use a ComboBox
and ignore it for SelectedIndex 0 as well. 为此,我将使用
ComboBox
并忽略它为SelectedIndex 0。 I didn't bother with this because the Combo
concept is amply covered. 我没有为此烦恼,因为
Combo
概念已经充分涵盖。 Result: 结果:
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. 没有创建新的
DBConnection
, DBCommand
或其他DBProvider对象,只是一个列表。
No need to dynamically create a SQL statement with N parameters in a loop to avoid SQL injection/special words and chars.
无需在循环中动态创建带有N个参数的SQL语句,以避免SQL注入/特殊字和字符。
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. 删除过滤器很容易,没有
WHERE
子句就不需要再次查询。
A
ComboBox
where applicable helps the user find what they want and avoid typos. 适用的
ComboBox
可帮助用户找到他们想要的内容并避免拼写错误。
Is the SQL "cleaner".
SQL“更干净”。 more "efficient? The code doesn't really mess with new SQL, just some WHERE clauses.
更高效?代码并没有真正搞乱新SQL,只是一些WHERE子句。
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.
它并没有把我作为很多代码来做它所做的事情。
In my database there are 3 column which is Name, Age, Gender.
在我的数据库中有3列,分别是姓名,年龄,性别。 In the program, I only want to use 1 search button.
在程序中,我只想使用1个搜索按钮。 When the button is clicked, the program determine which 3 of the textbox has input and search for the right data.
单击该按钮时,程序将确定文本框中的哪3个输入并搜索正确的数据。
And when only name is entered, I only query for the name.
当只输入名称时,我只查询名称。 Must I check textbox by textbox whether there is user input and then write multiple query for each of them?
我是否必须按文本框检查文本框是否有用户输入,然后为每个文本框写入多个查询? Or is there a better way to do this?
或者有更好的方法吗?
SELECT * FROM `table`
WHERE (`name` = @name AND `name` IS NOT NULL)
OR (`age` = @age AND (`age`>0 OR `age` IS NOT NULL))
OR (`gender` = @gender AND `gender` IS NOT NULL);
With the above query if all text boxes have value, the result will not be one record (as if you where using logical AND
between fields). 使用上述查询,如果所有文本框都有值,则结果将不是一条记录(就像在字段之间使用逻辑
AND
)。 If you want only that record you will filter it server-side with php from the rest of the results. 如果你只想要那条记录,你将使用php从其他结果中过滤服务器端。
You can check the results on your own in this Fiddle 您可以在此小提琴中自行查看结果
EDIT 编辑
In order to solve the above inconvenience (not bringing easily single results when needed) i got a little help from this answer and re-wrote the above query as: 为了解决上述不便(在需要时不能轻易带来单个结果),我从这个答案中获得了一点帮助,并重新编写了上述查询:
SELECT *, IF(`name`=@name, 10, 0) + IF(`age`=@age, 10, 0) + IF(`gender`=@gender, 10, 0) AS `weight`
FROM `table`
WHERE (`name` = @name AND `name` IS NOT NULL)
OR (`age` = @age AND (`age`>0 OR `age` IS NOT NULL))
OR (`gender` = @gender AND `gender` IS NOT NULL)
HAVING `weight`=30;
OR to still get all records with a
weight
on result 或仍然获得结果
weight
所有记录
SELECT *, IF(`name`=@name, 10, 0) + IF(`age`=@age, 10, 0) + IF(`gender`=@gender, 10, 0) AS `weight`
FROM `table` WHERE (`name` = @name AND `name` IS NOT NULL)
OR (`age` = @age AND (`age`>0 OR `age` IS NOT NULL))
OR (`gender` = @gender AND `gender` IS NOT NULL)
ORDER BY `weight` DESC;
You were pretty close. 你非常接近。 Let's look at
我们来看看吧
(FloatNo = @floatNo OR FloatNo = 'None')
So you want the field either to be the given input or 'None'? 所以你希望字段是给定的输入还是'None'? But there are (supposedly) no records in your table with FloatNo 'None'.
但是(表示)你的表中没有FloatNo'无'的记录。 What you really want to do is find out whether the input is none (ie empty):
你真正想做的是找出输入是否为空(即空):
(FloatNo = @floatNo OR @floatNo = '')
And for the case the user types in a blank by mistake, you can ignore this, too: 对于用户错误输入空白的情况,您也可以忽略这一点:
(FloatNo = @floatNo OR TRIM(@floatNo) = '')
The whole thing: 整个东西:
myCommand = New MySqlCommand(
"Select * from project_record Where
(FloatNo = @floatNo OR TRIM(@floatNo) = '') AND
(DevCompanyName = @devCompanyName OR TRIM(@devCompanyName) = '') AND
(DevType = @devType OR TRIM(@devType) = '') AND
(LotPt = @lotPt OR TRIM(@lotPt) = '') AND
(Mukim = @mukim OR TRIM(@mukim) = '') AND
(Daerah = @daerah OR TRIM(@daerah) = '') AND
(Negeri = @negeri OR TRIM(@negeri) = '') AND
(TempReference = @tempRef OR TRIM(@tempRef) = '')", sqlConn)
What is wrong with your approach? 你的方法有什么问题?
Just change (FloatNo = @floatNo OR FloatNo = 'None') 只需更改(FloatNo = @floatNo OR FloatNo ='None')
to 至
(FloatNo = @floatNo OR FloatNo = '' or FloatNo IS NULL) (FloatNo = @floatNo OR FloatNo =''或FloatNo IS NULL)
And do that for every criteria. 并为每个标准做到这一点。
Your query will respect empty values and NULL values after that. 您的查询将在此之后考虑空值和NULL值。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.