这个问题在这里已有答案:
我有搜索字段,用户可以指定很多值来搜索价格,表面,年份,花园,阳台等。
在我的搜索中,甚至没有一个字段需要每个都是可选的,因此用户可以提供0输入或全部。
基本上所有这些信息都保存在我的数据库中,但我真的不知道如何构建我的代码。
目前我有PHP文件,我从前面调用,在这个文件中,我正在检查哪个字段已填充,我正在从类中执行选择db和返回数据的方法。这对于每个输入单独工作正常但是当我结合例如价格和表面这两个不同的字段时,则不会执行任何方法。
我基本上询问搜索架构的想法,用户可以填写许多不同的领域。我没有使用任何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(输入)用户fullfill和发送。后来我需要在类中使用不同的SELECT创建很多方法来基于我们拥有的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('/\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
会更容易。
我只是想表明这一点,以帮助可视化连接问题。我们使用相同数量的变量编写更多代码,并将条件逻辑加倍。或者我们可以进入像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必须将这些结果存储在临时表中以及搜索整个数据集。因为我们正在拉动表中的所有列,所以此数据集将快速增长。一旦它达到一个certian大小,它将被交换到Disc,我们的表现将受到重创。
使用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的顺序。我忘记了子查询的顺序与外部查询问题。
为了好玩,您甚至可以将两者与两个嵌套的子查询结合起来,将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
它会变得相当复杂但是......大声笑。
无论如何,我很无聊,也许,也许,它可能适用于像我这样的人。 (这是我不睡觉时会发生的事情):)
UPDATE
如果参数有问题,可以通过执行以下操作输出填充值的SQL:
echo str_replace(array_keys($params), $params, $sql)."\n";
但是,仅将此用于调试,而不是将数据放入查询中,因为这会破坏使用预准备语句并打开SQLInjection攻击的目的。也就是说,它可以让您更容易看到是否遗漏了任何内容或有任何拼写错误。当我只想在PHPMyAdmin中测试查询时,我也使用它,但我懒得将数据粘贴到它中。然后我只是将输出复制到PHPMyAdmin然后我可以排除PHP的任何问题或者如果需要调整查询。
如果数组中有许多元素,那么在查询中不包含AKA额外占位符,也会出现问题。
为此,你可以做到
//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";
混合“AND”和“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
这被称为“独家OR”。这将返回与arg1
或arg2
和arg3
相匹配的所有行
希望有所帮助。
您还可以创建所需项目的列表,并检查每个项目是否由PHP函数isset()设置。