PHP搜索许多输入

时间:2018-08-30 22:02:04

标签: php database search

我有一个搜索字段,用户可以在其中指定很多值来进行搜索,例如价格,地表,年份,花园,阳台等。

在我的搜索中,甚至没有一个字段是必填字段,因此用户可以提供0个输入或全部输入。

基本上所有这些信息都保存在我的数据库中,但是我真的不知道如何构造代码。

目前,我有从前面调用的PHP文件,在此文件中,我正在检查填充了哪个字段,并且正在执行类中的方法,这些方法确实选择了db并返回数据。分别对每个输入都可以正常工作,但是当我组合例如价格和表面等2个不同的字段时,将不会执行任何方法。

我基本上是在问一个关于搜索体系结构的想法,用户可以在其中填充许多不同的字段。我没有使用任何PHP框架。


我可以做类似的事情:

if(a & b & c & d & e & f) then execute method a
if(a & b & c & d & e) then execute method b
if(a & b & c & d) then execute method c

,依此类推。..这些字母(a,b,c等...)是$_POST['something'],但我会有很多if来检查哪个POST(哪个输入)用户已满并发送。稍后,我将需要在类中使用基于POST的db创建很多带有不同SELECT的类的方法...我认为这不是最好的解决方案,因为我基本上会重复我的代码。

2 个答案:

答案 0 :(得分:3)

类似这样的东西

 $sql = 'SELECT * FROM sometable';
 $where = [];
 $params = [];

 if($a){
    $where[] = 'a = :a';
    $params[':a'] = $a;
 }

 if($b){
    $where[] = 'b = :b';
    $params[':b'] = $b;
 }


 if(!empty($where)){
    $sql .= ' WHERE '.implode(' AND ', $where);
 }

$stmt = $PDO->prepare($sql);
$res = $stmt->execute($params);

以此类推。

对于这样的事情,使用并数组内爆几乎总是可取的,而不是使用串联。在这种情况下,“串联”通常会使串联带有一个悬挂的“分隔符”。例如,如果我们尝试使用串联:

//if we put WHERE here and then nothing passes our conditions we wind up with:
//"SELECT * FROM sometable WHERE" which wont work
$sql = 'SELECT * FROM sometable ';

//we still need something like an array if we want to prepare our query.
//which is something we should always do
$params = []; 

 if($a){
    //if we put WHERE here, then what if this condition doesn't pass
    //do we put it in the next condition? How do we tell.     .
    $sql .= 'WHERE a = :a AND ';
    $params[':a'] = $a;
 }

 if($b){
     //again if the first condition didn't pass how do we know to put "WHERE" here.
    //"SELECT * FROM sometable b = :b AND" which wont work
    $sql .= 'b = :b AND ';
    $params[':b'] = $b;
 }

 if($c){
    //lets say the first 2 conditions passes but this last one failed
    //"SELECT * FROM sometable WHERE a = :a AND b = :b AND" which wont work
    $sql .= 'c = :c';
    $params[':c'] = $c;
 }

 //we would need to do something like this to trim the last "AND" off
 $sql = preg_replace('/\sAND\s$/', '', $sql);

 //--------------------
 //now if we were prepending "AND" instead of appending it, we're no better off.
 //--------------------
  //we can fix the where issue by using a string variable (and testing it latter)
  $where = '';

  if($a){
    $where .= 'a = :a';
    $params[':a'] = $a;
  }

  if($b){
    //However lets say the first condition failed, we get this:
    //"SELECT * FROM sometable WHERE AND b = :b" which wont work
    $where .= ' AND b = :b';
    $params[':b'] = $b;
    //--------------------------
    //so in every condition following we would have to test $where
    //and if its not empty then we can prepend "AND"
    if(!empty($where)) $where .= ' AND ';
    $where .= 'b = :b';
    $params[':b'] = $b;
 }

 if($c){
    if(!empty($where)) $where .= ' AND ';
    $where .= 'c = :c';
    $params[':c'] = $c;
  }


 //finally to fix the "WHERE" issue we need to do something like this:  
  if(empty($where)) $sql .= ' WHERE '.$where;

 //we could also try something like this in every condition:
  if($d){
    if(empty($where)) $where .= ' WHERE ';
    //However, this breaks our fix for prepending "AND", because 
    //$where will never be empty when we test it.
    //if(!empty($where)) $where .= ' AND ';
    $where .= 'd = :d';
    $params[':d'] = $d;
  }

希望一切都有意义。使用数组并稍后implode更加容易。

我只是想表明这一点,以帮助可视化级联问题。我们希望使用相同数量的变量并使条件逻辑加倍来编写更多代码。或者我们可以进入正则表达式之类的复杂事物来修剪悬挂和关闭等。

希望有帮助!

由于,我在评论中提到了它。

如果您使用“ OR”,则当然可以执行相同的操作,但是通常“ OR”将导致对数据库的完整扫描。这只是OR工作的方式。当我们使用“ AND”时,DB(基本上)采用返回集并对其应用下一个条件,因为两者都必须通过。但是,对于失败的“或”行,如果第二个条件通过,则第一个条件仍然可以通过。因此,数据库必须扫描每个记录的完整记录集,并跟踪在先前条件下传递的所有行。这就是逻辑“或”的工作方式。

现在,为了提高“或”性能,我们可以使用作为联合的子查询。像这样:

$sql = 'SELECT * FROM sometable AS t';
$union = [];
$params = [];


if($a){
   $union[] = 'SELECT id FROM sometable WHERE a = a:';
   $params[':a'] = $a;
}

if($b){
  $union[] = 'SELECT id FROM sometable WHERE b = b:';
  $params[':b'] = $b;
}

if(!empty($union)){
   $sql .= '
   JOIN( '.
        implode(' UNION ', $union).
   ' ) AS u ON t.id = u.id
}

我们想到的是这样的查询:

SELECT
    *
FROM
   sometable AS t
JOIN (
     SELECT id FROM sometable WHERE a = a:
  UNION
     SELECT id FROM sometable WHERE b = b:
) AS u ON t.id = u.id

当我们使用“ OR”作为数据集增长时,数据库必须将这些结果存储在临时表中并搜索整个数据集。由于我们要提取表中的所有列,因此该数据集将快速增长。一旦达到certian大小,它将被替换为Disc,而我们的性能将因此受到很大的冲击。

通过联合查询,我们还创建了一个临时表。但是,因为我们只关心提取ids,所以此临时表将非常小。与Union ALL不同,Union还将自动删除重复记录,从而进一步减少了我们的数据集。因此,我们要使用Union而不是Union ALL。

然后,我们将其重新加入外部查询的表中,并使用它从我们需要的行中拉出所有列。

基本上,我们接受这样一个事实,我们需要一个临时表并将其影响最小化。

这看起来好像不会快得多,在某些情况下可能不是(当没有交换发生时)。但是对我来说,使用像您这样描述的查询来描述用户可以在多个字段中进行搜索的地方,我将时间从大约15秒减少到不到1秒。我的查询中有多个联接,例如,如果用户处于某种状态,则必须先联接participant,然后联接participants_addresses(连接表),然后联接addresses,最后联接{{ 1}}。但是,如果他们接了电话,我必须加入states等。

我不能保证这在每种情况下都能奏效,并且在对查询进行基准测试时应使用Explain和SQL_NO_CACHE。例如participant > participants_phones > phone。 Explain会告诉您索引如何工作,如果多次运行,No Cache会阻止DB缓存查询。缓存会使它看起来真的很快,而实际上它却很快。

您可以在排序时执行类似的操作,这也会降低性能。

EXPLAIN SELECT SQL_NO_CACHE * FROM ...

这具有类似的效果,即仅对临时表中的ID(而不是整个数据集)进行排序,然后在我们加入它时,它实际上保持了ID的顺序。我忘记了子查询的顺序与外部查询有关。

为了有趣,您甚至可以将两个带有两个嵌套子查询的查询组合在一起,并以Union作为最深层的查询(类似这样)。

 SELECT
    *
 FROM
    sometable AS t
 JOIN (
        SELECT id FROM sometable WHERE a = a: ORDER BY date DESC
 ) AS u ON t.id = u.id

它可能会变得相当复杂……大声笑。

无论如何,我很无聊,也许,也许它将像对我一样对某人有用。 (这是我不睡觉的时候发生的事情):)

更新

如果您对参数有疑问,可以执行以下操作以输出带有已填充值的SQL:

 SELECT
    *
 FROM
    sometable AS t
 JOIN (
        SELECT id FROM sometable AS t0 JOIN (
             SELECT id FROM sometable WHERE a = a:
           UNION
             SELECT id FROM sometable WHERE b = b:
        ) AS u ON t0.id = u.id
        ORDER BY t0.date DESC
 ) AS t1 ON t.id = t1.id

但是仅将其用于调试,而不是将数据放入查询中,因为这将破坏使用准备好的语句的目的,并使您容易受到SQLInjection攻击。也就是说,它可以使您更轻松地查看是否丢失了任何内容或有任何拼写错误。当我只想在PHPMyAdmin中测试查询时,我也会使用此方法,但是懒于将数据粘贴到其中。然后,我只将输出复制到PHPMyAdmin中,然后就可以排除PHP的任何问题,或者根据需要调整查询。

如果数组中有很多元素,也就是查询中没有的额外占位符,那么也会出现问题。

为此,您可以

   echo str_replace(array_keys($params), $params, $sql)."\n";

混合“ AND”和“ OR”时要注意的最后一件事是这样的东西

  //count the number of : in the query
  $num_placeholders = substr_count(':', $sql);
  //count the elements in the array
  $num_params = count($params);

  if($num_placeholders > $num_params ) echo "to many placeholders\n";

  else if($num_placeholders < $num_params ) echo "to many params\n";

它的执行方式就像这样

 SELECT * FROM foo WHERE arg1 = :arg1 OR arg2 = :arg2  AND arg3 = :arg3

这将返回与SELECT * FROM foo WHERE arg1 = :arg1 OR (arg2 = :arg2 AND arg3 = :arg3) 匹配的所有行,而不管查询的其余部分如何。 大多数情况下,这不是您想要的。您实际上希望它这样做:

arg1

这称为“异或”。这将返回与SELECT * FROM foo WHERE (arg1 = :arg1 OR arg2 = :arg2) AND arg3 = :arg3 arg1arg2

匹配的所有行

希望有帮助。

答案 1 :(得分:0)

您还可以创建通缉的必需品列表,并检查是否通过PHP函数isset()设置了每个条目。