PHP搜索许多输入



我有一个搜索字段,用户可以在其中指定许多要搜索的值,如价格、表面、年份、花园、阳台等。

在我的搜索中,甚至不需要一个字段。每个字段都是可选的,所以用户可以提供0个已填充或全部输入。

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

目前,我有一个PHP文件,我从前面调用它,在这个文件中,我检查哪个字段被填充了,我正在执行从类中选择数据库并返回数据的方法。这对每个单独的输入都很好,但当我组合两个不同的字段(如price和surface)时,就不会执行任何方法。

我基本上是在问一个搜索体系结构的想法,用户可以填写许多不同的领域。我没有使用任何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(哪个输入)用户已满并发送。稍后,我需要在类中创建许多方法,这些方法具有不同的SELECT-to-db,基于我们的POST。。。我不认为这是最好的解决方案,因为我基本上会重复我的代码。

类似的东西

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

等等。

对于这样的事情,使用和数组并内爆几乎总是更可取的,而不是串联。通常,串联会留下一个挂起的"分隔符",在本例中为"AND"。例如,如果我们尝试级联:

//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('/sANDs$/', '', $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会容易得多。

我只是想展示这一点,以帮助通过串联将问题可视化。我们开始编写更多的代码,使用相同数量的变量,并将条件逻辑增加一倍。或者我们可以进入复杂的事情,比如Regex来修剪悬挂和关闭等

希望能有所帮助!

因为我在评论中提到了它。

如果你使用"OR",你当然可以用它做同样的事情,但通常"OR"会导致数据库的完全扫描。这就是OR的工作方式。当我们使用"AND"时,DB(基本上)获取返回集,并将下一个条件应用于该返回集,因为两者都必须通过。然而,对于失败的"OR"行,如果第二个条件通过,第一个条件仍然可以通过。因此,DB必须扫描每个或的完整记录集,并跟踪在以前的条件下通过的所有行。这只是逻辑为"OR"工作的方式。

现在,为了提高"OR"性能,我们可以使用作为并集的子查询。像这样:

$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"时,DB必须将这些结果存储在临时表中,并搜索整个数据集。因为我们正在提取表中的所有列,所以这个数据集将快速增长。一旦它达到一定的尺寸,它就会被换成光盘,我们的表演将因此受到巨大打击。

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

然后,我们将其连接回外部查询中的表,并使用它从我们需要的行中提取所有列。

基本上,我们接受了我们需要一个临时表的事实,并将其影响降至最低。

这看起来可能不会快得多,在某些情况下可能不会(当没有交换发生时)。但对我来说,使用像你描述的用户可以在多个字段上搜索的查询,我能够将耗时从大约15秒减少到1秒以下。我的查询中有几个联接,例如,如果用户处于某个状态,我必须先在participant上联接,然后在participants_addresses(连接表)上联接,再在addresses上联接,最后在states上联接。但如果他们打电话,我就必须加入participant > participants_phones > phone

我不能保证这在任何情况下都能起作用,在对查询进行基准测试时,您应该使用Explain和SQL_NO_CACHE。例如EXPLAIN SELECT SQL_NO_CACHE * FROM ...。Explain会告诉你索引是如何工作的,如果你多次运行查询,No Cache会阻止DB缓存查询。缓存会让它看起来很快,但实际上并不是。

排序时可以执行类似的操作,这也会影响性能。

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

这有一个类似的效果,只对临时表(而不是整个数据集)中的id进行排序,然后当我们加入它时,它实际上保持了id的顺序。我忘记了子查询与外部查询的顺序是否重要。

有趣的是,您甚至可以将两者与2个嵌套的子查询相结合,其中Union是最深的查询(类似于此)。

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

它可能会变得相当复杂。。。lol.

不管怎样,我很无聊,也许,只是也许,它会像对我一样对某人起作用

更新

如果您的参数有问题,您可以输出SQL,并通过以下操作填充值:

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

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

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

为此,您可以进行

//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 placeholdersn";
else if($num_placeholders < $num_params ) echo "to many paramsn";

当混合"AND"one_answers"OR"时,最后一件需要注意的事情是像这样的东西

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

这被称为"异或"。这将返回与arg1arg2ANDarg3匹配的所有行

希望能有所帮助。

您还可以创建一个需要的nessecary项列表,并检查每个项是否由PHP函数isset()设置。

最新更新