SQL查询|处理多个具有潜在空值的条件

时间:2018-05-01 03:23:50

标签: mysql sql

假设下表:

╔═══╦══════════════╦═════════════╗
║   ║Property A    ║Property B   ║
╠═══╬══════════════╬═════════════╣
║ 1 ║ slow         ║low          ║
║ 2 ║ fast         ║high         ║
╚═══╩══════════════╩═════════════╝

用户可以选择使用两个属性或仅使用一个属性来过滤结果。输入存储在变量$p1, $p2

如果两个变量都从用户获得值,我可以像这样轻松查询:

select * from table where (propertyA=$p1 and propertyB=$p2)

但是,如果其中一个没有得到用户的价值,我该如何查询? 由于上述查询propertyA=$p1中的行会变为false,因为$p1可能为null,但查询结果应根据$p2的值返回。

例如对于输入(null,low),查询应该返回第一条记录,但是对于我的查询,它不会返回。

这个例子只适用于两个属性,我的表中有多个属性,因此实现多个if条件会很麻烦。

我想知道是否有任何简洁的方法可以解决这个问题。

谢谢!

3 个答案:

答案 0 :(得分:2)

这个问题有两种基本方法:1)动态生成SQL语句,2)在静态SQL语句中使用表达式处理NULL

动态方法,如果正在从应用程序执行查询...

我们从一个静态字符串开始,每次查询都会是什么样子:

 $sql = 'SELECT t.id
           FROM mytable t
         WHERE 1=1';

然后决定我们是否要将另一个条件附加到WHERE子句

 if( $p1 !== '' ) {
    $sql .= ' AND t.propertyA = :p1';
 }
 if( $p2 !== '' ) {
    $sql .= ' AND t.propertyB = :p2';
 }

 // prepare the SQL statement
 $sth=$pdo->prepare($sql);

 // conditionally bind values to the placeholders
 if( $p1 !== '' ) {
    $sth->bindValue(':p1',$p1);
 }
 if( $p2 !== '' ) {
    $sth->bindValue(':p2',$p2);
 }

动态SQL的缺点是,对于更复杂的问题,正确构造SQL语句可能会变得非常棘手。只有WHERE条款中的条件,它才是可行的方法。

另一个缺点是我们最终会产生各种各样的SQL语句,并确保每个变体都有一个合适的执行计划变得复杂。 WHERE子句中只有两个可选条件,我们总共可以管理4个变量...

哦,在1=1条款中包含条件WHERE的原因并不会影响该陈述;优化器足够聪明,可以确定每个可能的行都是真的,这样条件就会被抛出。我们购买的是当我们附加到WHERE子句时,我们不需要检查"这是WHERE子句中的第一个条件吗?"所以我们知道是否要将WHEREAND附加到声明中。

第二种方法是使用静态SQL,使用一些表达式。

例如,假设propertyA和propertyB是字符类型列:

$sql = "SELECT t.id 
          FROM mytable t
         WHERE t.propertyA <=> IFNULL(NULLIF(:p1,''),t.propertyA) 
           AND t.propertyB <=> IFNULL(NULLIF(:p2,''),t.propertyB)"; 

$sth = $pdo->prepare($sql);
$sth->bindValue(':p1',$p1);
$sth->bindValue(':p2',$p2);

如果我们为:p1提供非零长度字符串,则NULLIF函数返回:p1,而IFNULL函数返回:p1。就好像我们刚写的那样:

 t.propertyA <=> :p1  

如果我们为$p1(占位符:p1)提供零长度字符串,则SQL NULLIF函数将返回NULL。反过来,IFNULL函数将返回t.propertyA,因此语句将propertyA与自身进行比较,因此最终结果将与我们编写的一致

AND t.propertyA <=> t.propertyA  

或只是

AND 1=1

(不同之处在于优化器不会放弃我们的条件,因为优化器不知道我们将要提供什么价值:p1在准备执行计划时。

注意:<=>太空飞船运营商是一个NULL安全的比较。它保证返回TRUE或FALSE(并且不返回NULL),这与标准的相等比较(=)不同,后者在被比较的值中的任何一个(或两个)为NULL时返回NULL。

此:

foo <=> bar

基本上是等效的缩写:

foo = bar OR ( foo IS NULL AND bar IS NULL ) 

如果我们保证propertyA永远不会为NULL(例如,通过表定义中的显式NOT NULL约束),我们可以放弃太空船操作符并使用普通的相等比较。

这种方法的缺点是不太直观的SQL语句;不熟悉的人可能会摸不着头脑。所以我们要在代码中留下评论,解释匹配条件是条件,如果:p1是空字符串,那么就不能与:p1进行比较。

我们可以使用不同的语法来实现相同的结果,例如,使用更加可移植的ANSI标准兼容COALESCE函数代替IFNULL,并使用CASE表达式NULLIF。 (这需要我们将$p1提供给额外的占位符,像我们一样写,我们只需要提供$p1一次。)

但这些是两种基本模式。选择你的毒药。

答案 1 :(得分:1)

如果两个属性都是字符串,则可以使用通配符'%'

select * from table where (propertyA like $p1 and propertyB like $p2)

答案 2 :(得分:0)

井查询是一个字符串,你需要先根据条件格式化字符串,然后在最终字符串准备就绪时,执行查询。

function chechForProperty($variable, $colName)
{
      if(isset($variable) || $variable != '')
      {
           return $colName."=".$variable." and ";
      } 
      return "";
}


$query = "select * from table where (";
$query .= checkForProperty($p1,'propertyA');
$query .= checkForProperty($p2,'propertyB');
$query .= checkForProperty($p3,'propertyC');
$query = substr($query,0,-5);
$query .= ");";

// now execute the query, I am just printing
print_r($query); 

希望这有帮助,谢谢