简体   繁体   English

如何实现更高效的搜索功能?

[英]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. nameDevCompanyName这样的东西几乎是唯一的,因此1列索引可能很好。

If you had gender and age , then INDEX(age, gender) might be useful. 如果您有genderage ,那么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. 你需要CONCATPREPARE等。

Original answer 原始答案

(scroll down to see update) (向下滚动以查看更新)

Can you try the following: 你能尝试以下方法吗?

  • build a list only including values of the textboxes that have an input 构建一个列表,仅包含具有输入的文本框的值
  • set a string of the join the items of that list together with the " AND " string 设置该列表的项目的连接字符串以及“AND”字符串
  • append that string to your standard SELECT statement 将该字符串附加到标准SELECT语句

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

Update 更新

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. 我有一个数据库,其中包含FishColor (字符串), BirdGroup (int)和Active的列中的随机数据,这些数据应该与问题中的姓名年龄性别相似 - 或者底部的其他长项。

DataTable 数据表

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. 展望未来,我们可以过滤用户对该表的视图 ,而无需发出新查询。

Filter Controls 过滤控制

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. 这是为了帮助用户成功并避免拼写错误( MakeMael而不是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: 取决于应用程序,这可能是一件好事:

  • 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. 如果用户在Finch过滤并且没有结果记录,则不需要MessageBoxStatusBar消息来解释空结果集。
  • 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. 如果控件位于Dialog或不同的TabPage ,则可以根据需要轻松完成。
  • 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' DBNull /'none'

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' ,在这种情况下可能非常有意义。

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: 使用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
  • Use whichever filterFmt is appropriate for what the app needs to do 使用适合应用程序需要执行的filterFmt
  • 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 . 对于TextBox ,它可以逃避任何嵌入式刻度,例如可能在O'MalleyD'Artgnan等名称中找到的。 It replaces one tick with two. 它用两个替换一个tick。
  • Since Group is a numeric, a valid Int32 input is tested 由于Group是数字,因此测试了有效的Int32输入
  • If there are elements in the filterTerms list, a filter string is created 如果filterTerms列表中有元素,则会创建一个过滤字符串
  • 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. 过滤器应用于DefaultView.Filter (您也可以使用DataViewBindingSource ),以便代码无需查询数据库即可提供过滤功能。
  • 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: 结果:

在此输入图像描述

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. 没有创建新的DBConnectionDBCommand或其他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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM