【问题标题】:How to implement a more efficient search feature?如何实现更高效的搜索功能?
【发布时间】:2016-04-27 06:26:24
【问题描述】:

在我的数据库中有 3 列,分别是姓名、年龄、性别。 在程序中,我只想使用 1 个搜索按钮。当点击按钮时,程序判断文本框的哪3个有输入并搜索正确的数据。

您如何处理查询?例如,如果姓名和性别有文本,则查询:

Select * from table Where (Name = @name) AND (Gender = @gender)

当只输入名字时,我只查询名字。我必须逐个检查文本框是否有用户输入,然后为每个输入写多个查询吗?或者有更好的方法吗?

编辑(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)

但正如您已经猜到的那样,它也不会有效地工作,因为如果我只输入DevType 的输入并将其他文本框留空,查询将不会只提取DevType 的所有记录。它只会显示为没有记录。

【问题讨论】:

  • 直接在 MySQL 数据库中过滤结果会更好。如果这是通过一个简单的查询完成的,那就更可取了!我没有看到任何数据库模式,但我认为您必须设置索引。 @RickJames 查看如何在您的查询中使用 OR,但如果需要,仍然会得到单个结果。
  • 我真的无法理解...您在查询中使用了多个 AND。为什么?您可以使用OR 查询获取单个和多个结果。我不能理解的另一件事是(我希望我不会迷失在翻译中),这是来自您的问题In my database there are 3 column which is Name, Age, Gender. In the program, I only want to use 1 search button. When the button is clicked, the program determine which 3 of the textbox has input and search for the right data. 从上面我了解到,如果输入值为 null 或 0 则不参与查询结果,对吗?
  • @PeterDarmis 是的,正确。这就是我放置 AND 子句的原因,因为我试图让查询确定哪些文本框具有价值,然后只在数据库中搜索。上面的列也只是一个例子。不是我程序中的实际列名。我没有早点澄清的错误
  • 我打算检查我的答案,但正如我所见,您已经获得了正确答案的投票。无论如何,请尝试答案,我认为它可以更好地覆盖您。
  • 会的!也非常感谢!每个答案都很重要!

标签: mysql sql database vb.net


【解决方案1】:
Select * from table 
Where (Name = @name OR @name is Null) 
  AND (Gender = @gender OR @gender is Null)
 ...

应该是一个查询

【讨论】:

  • 不知道为什么,但 datagridview 没有显示记录。看看这里link
  • 您也可以尝试或@gender = '' 只需检查在文本字段为空的情况下发送到数据库的内容
  • 它不起作用。假设我的名称文本框输入是“约翰”。查询将是Name = John OR John is Null
  • 是的。这有什么问题?如果您使用 paratrized 查询。 'John' 为 null - false 并且 name='John' 为 true。所以结果找到了所有 'John' 记录
  • 将查询更改为 Name = John OR Name is Null
【解决方案2】:

其他答案已经解释了如何简化查询。删除ORs 尤其重要,因为它们禁止使用任何索引。

一旦您干净地构建了查询,您需要考虑数据集并决定通常使用哪些列进行过滤。然后为他们制作几个INDEXes。您将无法提供“所有”可能的索引,因此我告诫您要考虑数据集。

在构建索引时,您可以使用单列或多列索引。对于您的数据类型,我建议从几个 2 列索引开始。确保每个索引都以不同的列开头。

对于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)

nameDevCompanyName 之类的内容实际上是独一无二的,因此 1 列索引可能比较好。

如果你有genderage,那么INDEX(age, gender) 可能有用。

MySQL 几乎不会为单个 SELECT 使用两个索引。

顺便说一句,WHERE 的构造可以在存储过程中完成。您需要CONCATPREPARE 等。

【讨论】:

  • 什么是 INDEX,它是如何工作的?还有你所说的 Where 子句的存储过程是什么意思?
  • 停止数据库的所有进程,直到你读到“keys”和“indexes”!它们对于 SQL 查询的性能至关重要。
  • @RickJames 说得再好不过了!
  • @RickJames 是的,使用正确的键、索引、外键等设置数据库是最佳实践,初始数据库模式应该是这样的。但是除了简化查询以获得我们需要的结果之外,即使我们无法重新设计数据库,有时也需要。另外我不明白为什么有人不能在字段之间使用OR 得到结果???
  • iLikeMySql 的答案应该可以工作,但比构建“最小”查询要慢,正如 Robin 所建议的那样。
【解决方案3】:

原答案

(向下滚动查看更新)

您可以尝试以下方法吗:

  • 建立一个列表,只包含有输入的文本框的值
  • 设置一个字符串,将该列表的项目与“AND”字符串连接起来
  • 将该字符串附加到您的标准 SELECT 语句中

代码如下所示:

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

更新

关于 cmets re SQL injection,这里是一个更新的示例。

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)

【讨论】:

  • 请试一试 - 只要有一些 TextBox 是空的,它就不会被包含在列表中。因此列表中的 join 方法只包含非空输入。
  • 列表是您可以从中添加和删除项目的集合。根据您的要求,我们仅将非空项目添加到列表中。一旦我们拥有所有非空项目,我们将它们与字符串“AND”连接起来并附加到 SELECT。如果要排除用户意外输入空格的项目,请使用 If Trim(Predicate1) = String.Empty Then ...
  • 是的。此外,尝试过滤/查找像O'BrianD'Artagnan 这样的名称,它会崩溃。
  • 我不敢相信这是公认的答案!您应该绝不将查询文本与用户输入连接!这是一个严重的安全漏洞,因为它为 SQL 注入打开了大门。 总是用参数给命令传值!醒醒,2016年!!!
  • @Student,我理解你的理由,这很好,你只是从中提取了好的部分,但是周围有很多人不这样做。它们是盲目的复制粘贴解决方案,并在投入生产的代码中使用。像这样的答案使得 SQL 注入之类的事情仍然存在。这是一个非常微不足道的错误,今天应该已经消失了。我会鼓励答案所有者去编辑这个答案并添加参数。
【解决方案4】:

COALESCE 是你的朋友。您可以使用它使 where 子句忽略参数为 NULL 的比较。

Select * from table Where (Name = COALESCE(@name,table.Name))
AND (Gender = COALESCE(@gender,table.Gender))

因此,如果@name 参数为NULL,COALESCE(@name,table.Name) 将返回当前行的“名称”列的值,(Name = COALESCE(@name,table.Name)) 将始终为真。

这假定如果在文本框中没有输入值,则相应的参数将为 NULL。如果它是一个值,例如“None”,您可以使用NULLIF 函数将“None”映射到 NULL

Select * from table Where 
(Name = COALESCE( NULLIF( @name, 'None'), table.Name)) 
AND (Gender = COALESCE( NULLIF( @gender, 'None'), table.Gender))

【讨论】:

  • 输入的值是空格怎么办?
  • 剪掉进来的前导和尾随的数据。否则"GIGO" -- Garbage In, Garbage Out。
【解决方案5】:

How to implement a more efficient search?

答案部分取决于您对高效的定义。我怀疑你的意思是更少的代码和更少的 if 块等。但从根本上说,运行一个新的SELECT * 查询来应用过滤器是低效的,因为你的基础数据集可以是所有的行,你只是摆弄用户的视图。

我有一个数据库,其中 FishColor(字符串)、BirdGroup的列中有随机数据> (int) 和 Active 这对于问题中的 NameAgeGender 应该足够相似 - 或底部的另一个长的东西。

数据表

填写数据表并将其绑定到 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

接下来,我们可以过滤用户对该表的视图而不发出新的查询。

过滤器控件

如果某些字段仅限于某些选择,例如Gender,您可以使用ComboBox 而不是TextBox。这是为了帮助用户成功并避免拼写错误(MakeMael 而不是 Male;或者在这里,正确拼写 Baracuda 我的意思是 Barracuda,呃 Barracuda 没错。

出于说明目的,Fish 是用户可以输入任何内容的东西,但 Bird 仅限于一组选择。如果有Bird 表,则可以从它绑定或填充cboBird。但您也可以从主/基表中填充它:

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)

如果“Finch”是合法选择,但数据库中没有,它不会显示在列表中。根据应用程序,这可能是一件好事:

  • 如果用户在Finch 上进行过滤并且没有结果记录,则不需要MessageBoxStatusBar 消息来解释空结果集。
  • 如果列表中没有某些内容,则表示您预先表明没有这些内容。然后就变成了训练为什么已知元素不在列表中的问题。
  • 另一方面,您必须在每次使用之前重新填充这些过滤器控件,以防最近添加了新记录。如果控件位于 Dialog 或不同的 TabPage 上,则可以根据需要轻松执行此操作。
  • 它并不总是适用,但它可以帮助用户避免拼写错误。

这两种方法是否有价值取决于应用程序。

DBNull / '无'

我不确定您为什么要在每个子句中添加“无”。如果有人想查看所有“John”或所有“Cod”记录,他们似乎也不会对“none”感兴趣。就个人而言,Null/DBNull 似乎是一种更好的处理方式,但添加或不添加任何一种形式都很容易。

使用 DBNull/None 过滤到只是那些似乎有价值。上面的鸟列表代码过滤掉了DBNull,我也会过滤掉none。然后,在将结果添加到 ComboBox 之前,先添加一个 `None' 项,使其位于顶部。

同样,这取决于应用程序的功能; Or = 'None',在这种情况下可能非常有意义。

过滤器

FishGroup 使用 TextBox,对 BirdColor 使用 ComboBoxCheckBox 表示Active,代码可以这样形成过滤器:

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
  • 仅当相关控件具有值时才会将过滤器项添加到列表中(如上所述,这可能包括 'None')。
  • 对于TextBox,它会转义任何嵌入的记号,例如可能在O'MalleyD'Artgnan 等名称中找到的记号。它将一个刻度替换为两个。
  • 由于 Group 是数字,因此测试了有效的 Int32 输入
  • 如果filterTerms 列表中有元素,则创建过滤字符串
  • 过滤器应用于DefaultView.Filter(您也可以使用DataViewBindingSource),以便代码无需查询数据库即可提供过滤功能。
  • Rows 会告诉你当前视图中有多少行。

唯一有点棘手的是布尔值,如 GenderActive,因为它们实际上解析为三个选择:{Any/Either, A, B}。为此,我会使用 ComboBox 并且对于 SelectedIndex 0 也忽略它。我没有为此烦恼,因为Combo 概念已被充分涵盖。结果:

是否更“高效”?

这仍然取决于。
它不会重新查询数据库来获取应用程序已经拥有的行。
没有新的DBConnectionDBCommand 或其他 DBProvider 对象被创建,只是一个列表。
无需在循环中动态创建带有N个参数的SQL语句,以避免SQL注入/特殊单词和字符。
它甚至不查询用于过滤条件的项目的数据库。如果数据库中有静态列表,它们可以加载一次,第一次使用过滤器。
删除过滤器很容易,没有WHERE无需再次查询子句。
ComboBox 在适用的情况下帮助用户找到他们想要的内容并避免拼写错误。
是 SQL 的“清洁剂”。更“高效?代码并没有真正与新 SQL 混淆,只是一些 WHERE 子句。

代码少了吗?我不知道,因为我们只是看到结果。它不会把我当作很多代码来完成它的工作。

【讨论】:

  • 非常感谢专家的回答。但老实说,我并没有得到大部分内容,因为我还是新手,还在学习。此外,我正在做的应用程序将被至少 3 个不同的人使用,我希望它获得最新更新的记录,因为一个人可能会不断输入记录,而另一个人正在搜索记录..
  • 多用户并不意味着您需要重新查询数据库。如果您有完全配置的 DataAdapter,myDA.Fill(myDT)刷新 数据表,获取其他人添加的任何新行,并删除其他人删除的行 构建一个全新的查询。我怀疑会有一些知识空白,这就是为什么那里有很多解释。过滤器代码很简单,添加到某个测试项目中看看效果如何。
  • .RowFilter 想象为神奇地将所需的WHERE 子句添加到现有查询中——这就是它的作用。如果数据表以所有行开头,则只需添加所需的过滤器。对于多用户,唯一的更改是在开始时执行daSample.Fill(dtSample) 以获取自上次以来提交的所有更改。请参阅this answer 将 DataAdapter 设置为智能
  • 这通常不适用于大型表,具体取决于您想要返回的数据。例如,我们有一些存储地理空间图像的系统表,这些表加起来非常快(1 个表大约 12 gig),因此预先进行全选将非常昂贵且无用。虽然解决方案很好,但老实说要视情况而定。
  • 如果一个人可以一次性将所有内容加载到内存中,那么他就不会关心优化数据库查询,因为无论如何它都会非常快。
【解决方案6】:

在我的数据库中有 3 列,分别是姓名、年龄、性别。在程序中,我只想使用 1 个搜索按钮。当点击按钮时,程序判断文本框的哪3个有输入并搜索正确的数据。

当只输入名字时,我只查询名字。我必须逐个检查文本框是否有用户输入,然后为每个输入写多个查询吗?或者有更好的方法吗?

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);

如果所有文本框都有值,则通过上述查询,结果将不是一条记录(就像您在字段之间使用逻辑AND 一样)。如果您只想要该记录,您将使用 php 从其余结果中过滤它的服务器端。

您可以在此Fiddle 中自行查看结果

编辑

为了解决上述不便(在需要时不容易带来单一结果),我从answer 得到了一点帮助,并将上述查询重写为:

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;

或者仍然得到所有结果带有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;

【讨论】:

    【解决方案7】:

    你已经很接近了。来看看

    (FloatNo = @floatNo OR FloatNo = 'None')
    

    所以您希望该字段是给定的输入还是“无”?但是(假设)您的表中没有 FloatNo 'None' 的记录。您真正想要做的是找出 input 是否为空(即为空):

    (FloatNo = @floatNo OR @floatNo = '')
    

    对于用户误输入空白的情况,你也可以忽略:

    (FloatNo = @floatNo OR TRIM(@floatNo) = '')
    

    整件事:

    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)
    

    【讨论】:

      【解决方案8】:

      你的方法有什么问题?

      只是改变 (FloatNo = @floatNo 或 FloatNo = '无')

      (FloatNo = @floatNo OR FloatNo = '' 或 FloatNo 为空)

      并针对每个标准执行此操作。
      之后,您的查询将尊重空值和 NULL 值。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-01-08
        • 2022-07-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-21
        相关资源
        最近更新 更多