【问题标题】:Optimize Database Search Query优化数据库搜索查询
【发布时间】:2015-03-25 16:44:59
【问题描述】:

我的表格包含以下列
-> col1, col2, col3

我正在尝试使用这些列进行搜索。所以我从用户那里获取了 3 个输入。

简单的搜索规则:

1) 如果用户没有输入任何col,那么它应该只使用其他 2 列进行搜索。

select * from myTable where col1="abc" and col2="def"; // something like this. Any combination like col1-col2, col1-col3 or col2-col3

2) 如果输入了所有cols,则:

select * from myTable where col1="abc" and col2="def" and col3="ghi"; // something like this

3) 如果用户输入了col 中的任何一个,则:

select * from myTable where col1="abc"; // something like this. It can be col1, col2 or col3.

我知道这可以通过对数据库使用不同的 select 语句并在 Java 代码中使用 if-else 来完成。

我想要一个针对这种情况的最优化解决方案(几乎没有代码/解释)。

编辑

注意:所有 3 列都是 NULL !我正在使用 Microsoft-SQL Server (MSSQL),但我想要 MySQL 和 MSSQL 的解决方案

【问题讨论】:

  • 您使用的是什么 ORM,顺便说一句,查询将始终在您的 if else 语句之后创建,因此将进行优化。
  • @ankur-singhal 只想要一个简单的 sql 查询。最优化的解决方案。
  • 您可以使用包含 where 子句的循环来执行此操作,然后将其用于单选。
  • 你用mysql mssql (Microsoft SQL Server) 标记了这个。你真正使用的是什么?
  • @a_horse_with_no_name mssql ...但我想要两个数据库的解决方案。

标签: java mysql sql sql-server jdbc


【解决方案1】:

假设您分别绑定了名为:col1:col2:col3 的变量,这可以通过使用几个or 条件在单个语句中完成。这里的想法是让数据库为 eahc 列执行短路逻辑 - 如果用户传递 null,则该部分条件仅评估为 true,而不访问表。如果传递的是实数值,则将其与表中的列进行比较。

SELECT *
FROM   myTable 
WHERE  (:col1 IS NULL OR :col1 = '' OR :col1 = col1) AND
       (:col2 IS NULL OR :col2 = '' OR :col2 = col2) AND
       (:col3 IS NULL OR :col3 = '' OR :col3 = col3)

【讨论】:

  • 嗯,我的回答中有 3 个问题。 1) 有没有办法在 SQL 查询中检查我传递的值是否为NULL,请提供参考? 2) 如果我有:col1 = "" 并且我不想在我的搜索语句中考虑它怎么办。 3)这意味着:col1 IS NULL OR col1=:col1(用文字)是什么意思?
  • @Junaid 1) 这正是我的查询所做的 :-) 2) 我没有注意到您在询问 MySQL,它对空字符串的处理方式与 nulls 不同 - 我相应地编辑了问题. 3)我编辑了答案以试图澄清。
  • 您的查询看起来非常有趣且合乎逻辑。在测试您的查询时,我遇到了这个问题(stackoverflow.com/questions/28177447/…)。你能帮我么。谢谢
  • 有趣的是我使用了一个稍微不同的版本
【解决方案2】:

确定这就是你所需要的吗?

Select * 
    from myTable 
where (col1 like @col1 +'%' or @col1 is null) 
  and (col2 like @col2 +'%' or @col2 is null) 
  and (col3 like @col3 +'%' or @col3 is null)

【讨论】:

  • 具有以下通配符模式%someterm% 的LIKE 语句不能使用索引,因此总是很慢。 LIKE 语句可以使用索引的唯一方法是使用以下通配符模式someterm%
  • 你能解释一下为什么不写( @col1 is null or col1 like @col1 +'%') ...应该先检查null吗??
  • 据我所知,它并没有什么不同,因为它无论如何都会评估这两个表达式。话虽如此,我知道某些 DBMS 可以设置为在找到返回 true 后停止评估 or 。如果是这种情况,您最好在语句中首先设置最有可能返回 true 的那个。
【解决方案3】:

您还可以在查询中使用嵌套的CASE。那么当某些变量为null时,查询条件会更简单。

第一个命题:

SELECT *
FROM myTable 
WHERE
  CASE
    WHEN @col1 is NULL OR @col1 = '' THEN 
      CASE 
        WHEN @col2 is NULL OR @col2 = '' THEN
          CASE 
            WHEN @col3 is NULL OR @col3 = '' THEN 1=1
            ELSE @col3 = col3
          END
        ELSE
          CASE 
            WHEN @col3 is NULL OR @col3 = '' THEN @col2 = col2
            ELSE @col2 = col2 AND @col3 = col3
          END
      END
    ELSE
      CASE 
        WHEN @col2 is NULL OR @col2 = '' THEN
          CASE 
            WHEN @col3 is NULL OR @col3 = '' THEN @col1 = col1
            ELSE @col1 = col1 AND @col3 = col3
          END
        ELSE
          CASE 
            WHEN @col3 is NULL OR @col3 = '' THEN @col1 = col1 AND @col2 = col2
            ELSE @col1 = col1 AND @col2 = col2 AND @col3 = col3
          END
      END
  END;

第二个命题:

SELECT *
FROM myTable 
WHERE
  col1 = 
  CASE
    WHEN @col1 IS NULL OR @col1 = '' THEN col1
    ELSE @col1
  END
AND
  col2 = 
  CASE
    WHEN @col2 IS NULL OR @col2= '' THEN col2
    ELSE @col2
  END
AND
  col3 = 
  CASE
    WHEN @col3 IS NULL OR @col3= '' THEN col3
    ELSE @col3
  END;

你可以在SQLFiddle看到结果

编辑:

所以有三个不同的查询。一个是 Mureinik 提出的,上面两个是我提出的。要确定其中哪一个是最佳的,我们必须了解 MySQL(和其他 DBMS)在执行前如何优化查询。我们可以查看详情here

对我们来说最重要的短语是

去除常数条件

这意味着我的一个查询中的条件(1=1) 将被删除。这也意味着当:col1:col2 都是空值并且:col3 = 'aaa' 然后Mureinik 的查询:

WHERE  (NULL  IS NULL OR NULL  = '' OR NULL  = col1) AND
   (NULL  IS NULL OR NULL  = '' OR NULL  = col2) AND
   ('aaa' IS NULL OR 'aaa' = '' OR 'aaa' = col3)

将简化为:

WHERE 'aaa' = col3

如果我们以这种方式分析所有 3 个提议的查询,我们将看到对于每组变量 col1col2col3,所有这些查询都将被 DBMS 优化为完全相同的查询。因此,它们三个都具有相同的性能。所以你可以选择任何你想要的(Mureinik 的那个似乎最清晰)

【讨论】:

  • 您在 SQLFiddle 中有 3 个搜索查询,对吧?哪一个最优化,为什么?这些查询也可以在 MSSQL 上运行?
【解决方案4】:

您可以使用 PHP 执行以下操作,使用 Java 执行相同的方式:

$mapColVal = array( 1 => $first_post_value, 2 => $second_post_value, 3 => $third_post_value);
$whereCond = '';
for($i = 1; $i <= 3; $i++){
   $whereCond .= "col".$i. "=". $mapColValue[$i]." AND ";
}
$whereCond = subStr($whereCond,0,-5);

然后如下操作:

SELECT * FROM my_table WHERE $whereCond;

【讨论】:

    【解决方案5】:

    使用 where 子句中的 case 可以很容易地做到这一点:

    SET @col1='someterm1';
    SET @col2='someterm2';
    SET @col3=NULL;
    
    SELECT  *
    FROM table tbl1
    WHERE 
        CASE WHEN @col1 IS NULL THEN 1=1 ELSE tbl1.col1=@col1 END
        AND CASE WHEN @col2 IS NULL THEN 1=1 ELSE tbl1.col2=@col2 END
        AND CASE WHEN @col3 IS NULL THEN 1=1 ELSE tbl1.col3=@col3 END;
    

    如果你传递一个非空值,where 子句只会搜索一个值。因此,当变量/参数被替换时,上面的语句将如下所示:

    SELECT  *
    FROM table tbl1
    WHERE 
        CASE WHEN @col1 IS NULL THEN 1=1 ELSE tbl1.col1='someterm1' END
        AND CASE WHEN @col2 IS NULL THEN 1=1 ELSE tbl1.col2='someterm2' END
        /* THIS LINE AND CASE WHEN @col3 IS NULL THEN 1=1 ELSE tbl1.col3=@col3 END; changes because of the NULL*/
       AND 1=1;
    

    因此,您可以传递您拥有的任何字段组合,并且只会搜索这些字段。对于您没有发送 NULL 值的字段,CASE 语句将其变为 1=1,并且不应用条件。

    该技术应该适用于任何数据库引擎。

    【讨论】:

      【解决方案6】:

      我会假设您的列名称不完全是 col1、col2、col3,并且列的数量将来可能会增加,因此您需要在发生这种情况时不需要完全重做的东西。因此,您将需要一个包含列名的数组。用户输入同样应该来自与列名数组大小相同的字符串列表。

      我还将假设您正在使用某种准备好的语句,但如果没有,请遵循基本大纲。

      此外,我的假设是,如果所有输入均为空,我们将返回整个表。

      private final String[] COLUMNS = new String[]{"col1", "col2", "col3"};
      
      public static PreparedStatement getStatement(String queryString){
         //you do this
      }
      
      public static PreparedStatement generateOptimizedStatement(List<String> input) {
        String whereOrAnd = " where ";
        StringBuilder sb = new StringBuilder("select * from myTable ");
        int i = 0;
        for(int i = 0; i < COLUMNS.length; i++){
          if(input.get(i) != null){
            sb.append(whereOrAnd).append(COLUMNS[i]).append(" = ? ");
            whereOrAnd = " and ";  
          }
        }
        PreparedStatement ps = getStatement(sb);
        for(int j = 0; j < COLUMNS.length; j++){
          String s = input.get(j);
          if(s != null){
            ps.setString(j+ 1, s); //prepared statement starts with index 1
          }
        }
        return ps;
      }
      

      【讨论】:

        【解决方案7】:

        您应该注意的一点是,在 WHERE 子句中,添加“OR”通常会给查询增加很多开销。 “AND”通常更快,并且需要编译器更少的计算。所以我会尽可能地尝试一些不会使用它的东西。

        这是我对如何最好地优化它的想法:

        1) 在所有 3 列(col1、col2、col3)上放置索引。 2) 理想情况下,确定要使用的列应该在 Java 中计算,并在此基础上触发查询。这是我的想法(在PHP中,但可以扩展到Java......对不起,不够熟悉!):

        <?php
        
        if (isset($_GET['options'])) {
        $options = explode(",",$_GET['options']); // assuming you feed the columns separated with columns
        }
        
        if (isset($_GET['col1Value'])) {
        $col1Value = $_GET['col1Value']; 
        }
        
        if (isset($_GET['col2Value'])) {
        $col2Value = $_GET['col2Value']; 
        }
        
        if (isset($_GET['col3Value'])) {
        $col3Value = $_GET['col3Value']; 
        }
        
        if (in_array("col1",$options)) { // check to see if 'col1' exists in array
        $clause = ' and coalesce(col1,'') = $col1Value';
        }
        
        if (in_array("col2",$options)) { // check to see if 'col2' exists in array
        $clause = $clause.' and coalesce(col2,'') = $col2Value';
        }
        
        if (in_array("col3",$options)) { // check to see if 'col3' exists in array
        $clause = $clause.' and coalesce(col3,'') = $col3Value';
        }
        
        
        $sql = "
        
        select *
        from table
        where 1=1
             $clause
        ";
        
        pg_execute($databaseConnection,$sql);
        
        ?>
        

        这可能不是 PHP 中最好的例子,但希望能给你一些想法......

        干杯!

        【讨论】:

          猜你喜欢
          • 2018-02-23
          • 1970-01-01
          • 2013-03-16
          • 2016-01-06
          • 2021-08-23
          • 1970-01-01
          • 2011-02-27
          相关资源
          最近更新 更多