以下是我在工作表单提交后开始执行的工作查询。我的查询只有在填写了所有文本框时才有效,因此目前一切都是必需的。
工作查询
SELECT behaviour.hash,
Sum(behaviour.timespent) AS timeSpent,
new_table.percentile_rank,
Count(*) AS total
FROM behaviour,
audience,
new_table
WHERE ( $url )
AND behaviour.timestamp >= Date_sub(Curdate(), INTERVAL $last_visit day) AND behaviour.timestamp < Date_add(Curdate(), INTERVAL 1 day)
AND behaviour.hash = audience.hash
AND behaviour.hash = new_table.hash
AND audience.country = '$from_country'
GROUP BY behaviour.hash
HAVING Count(*) >= $more_than
AND timespent >= $time_spent
AND new_table.percentile_rank >= $lead_scoring
我想要实现的是不要求用户填写所有文本框以便提交,但仅限于他喜欢的那些文本框。所以我构建了以下内容,但它有一些错误。
我的问题是我的查询有一个having
子句,所以不是每个condition
都与我现在的AND
相关联(请查看下面的代码)。因此,提交的$more_than
或$time_spent
或$lead_scoring
个文本框中的第一个,必须有HAVING
而不是AND
。
如何编辑我的代码以实现此目的&#34;特殊条件&#34; ?
我的代码
$url= 'url="'.implode('" OR url="', $vals).'"';
$conditions = array();
if (!empty($last_visit)) $conditions[] = "behaviour.TIMESTAMP >= DATE_SUB( CURDATE( ) , INTERVAL '".$last_visit."' DAY) AND behaviour.TIMESTAMP < DATE_ADD( CURDATE( ) , INTERVAL 1 DAY ) ";
if (!empty($from_country)) $conditions[] = "audience.country = '".$from_country."'";
if (!empty($more_than)) $conditions[] = "COUNT( * ) >= '".$more_than."'";
if (!empty($time_spent)) $conditions[] = "timeSpent >= '".$time_spent."'";
if (!empty($lead_scoring)) $conditions[] = "new_table.percentile_rank >= '".$lead_scoring."'";
$conditionString = implode(' AND ', $conditions);
$sql = "SELECT behaviour.hash,
Sum(behaviour.timespent) AS timeSpent,
new_table.percentile_rank,
Count( * ) AS total
FROM behaviour,
audience,
new_table
WHERE ($url) AND ".$conditionString;
当前输出
在下面的示例中,已填写除more_than
以外的所有文本框。问题是AND timespent >= '20'
应该是HAVING timespent >= '20'
SELECT behaviour.hash,
SUM(behaviour.timespent) AS timeSpent,
new_table.percentile_rank,
Count(*) AS total
FROM behaviour,
audience,
new_table
WHERE ( url = "/10369" )
AND behaviour.timestamp >= Date_sub(Curdate(), interval '3' day)
AND behaviour.timestamp < Date_add(Curdate(), interval 1 day)
[missing] AND behaviour.hash = audience.hash
[missing] AND behaviour.hash = new_table.hash
AND audience.country = 'it'
[missing] GROUP BY behaviour.hash
[wrong] AND timespent >= '20' ////// it should be HAVING /////
AND new_table.percentile_rank >= '30'
答案 0 :(得分:10)
首先,必须确保无法进行SQL注入。为此,我们使用PDO。
接下来,要解决您的实际问题,您只需创建两个条件列表即可。一个包含您希望在查询的WHERE
部分中包含的条件,另一个包含需要在查询的HAVING
部分中输入的条件。
$pdo = new PDO(/* See http://php.net/manual/en/pdo.construct.php */);
$whereConditions = [];
$havingConditions = [];
$parameters = [];
if (!empty($last_visit)) {
$whereConditions[] = "behaviour.TIMESTAMP >= DATE_SUB( CURDATE( ) , INTERVAL :last_visit DAY) AND behaviour.TIMESTAMP < DATE_ADD( CURDATE( ) , INTERVAL 1 DAY ) ";
$parameters['last_visit'] = $last_visit;
}
if (!empty($from_country)) {
$whereConditions[] = "audience.country = :from_country";
$parameters['from_country'] = $from_country;
}
if (!empty($more_than)) {
$havingConditions[] = "COUNT( * ) >= :more_than";
$parameters['more_than'] = $more_than;
}
if (!empty($time_spent)) {
$havingConditions[] = "timeSpent >= :time_spent";
$parameters['time_spent'] = $time_spent;
}
if (!empty($lead_scoring)) {
$havingConditions[] = "new_table.percentile_rank >= :lead_scoring";
$parameters['lead_scoring'] = $lead_scoring;
}
if (count($vals)) {
$escapedUrlList = implode(', ', array_map(function ($url) use ($pdo) {
return $pdo->quote($url);
}, $vals));
$whereConditions[] = "url IN($escapedUrlList)";
}
$whereClause = count($whereConditions) ? ' AND ' . implode(' AND ', $whereConditions) : '';
$havingClause = count($havingConditions) ? ' HAVING ' . implode(' AND ', $havingConditions) : '';
$statement = $pdo->prepare("
SELECT behaviour.hash,
Sum(behaviour.timespent) AS timeSpent,
new_table.percentile_rank,
Count(*) AS total
FROM behaviour,
audience,
new_table
WHERE behaviour.hash = audience.hash
AND behaviour.hash = new_table.hash
{$whereClause}
GROUP BY behaviour.hash
{$havingClause}
");
$result = $statement->execute($parameters);
答案 1 :(得分:4)
这是一个使用预处理语句的“棘手”方法(看起来很干净)。我在未来的变化中添加了一些通用的“功能”。 阅读带有解释的评论(我认为这样会更方便):
//assume established PDO connection - example:
try {
$pdo = new PDO("mysql:dbname={$database_name};host=localhost", $user, $password);
} catch (PDOException $e) {
echo 'Connection failed: ' . $e->getMessage();
}
//static: conditional strings without parameters check (no keys required)
//conditional: assoc - keys should match both query placeholders and variable names
$static_where = [];
$optional_where = [
'last_visit' => 'behaviour.TIMESTAMP >= DATE_SUB(CURDATE(), INTERVAL :last_visit DAY) AND behaviour.TIMESTAMP < DATE_ADD(CURDATE(), INTERVAL 1 DAY)',
'from_country' => 'audience.country = :from_country'
];
$static_having = [];
$optional_having = [
'more_than' => 'COUNT(*) >= :more_than',
'time_spent' => 'timeSpent >= :time_spent',
'lead_scoring' => 'new_table.percentile_rank >= :lead_scoring'
];
//params: query parameters array - assigned manually + resolved from optional variables
$params = [];
//resolve condition from $urls array
if (count($urls) == 1) {
$static_where[] = 'url = :url';
$params['url'] = reset($urls);
} else if (!empty($urls)) {
foreach ($urls as $idx => $url) {
$params['url' . $idx] = $url;
}
$static_where[] = 'url IN(:' . implode(', :', array_keys($params)) . ')';
}
//filtering existing params used in query
//empty() is not a good idea for general purpose though,
//because some valid values might be recognised as empty (int 0, string '0')
$params += array_filter(
compact(array_keys($optional_where), array_keys($optional_having)),
function ($value) { return !empty($value); }
);
//concatenating conditional strings
//with corresponding params that weren't filtered out
//or these without params (static)
$where_clause = implode(' AND ', $static_where + array_intersect_key($optional_where, $params));
$having_clause = implode(' AND ', $static_having + array_intersect_key($optional_having, $params));
//don't need clauses without conditions - same as if (!empty($where)) {...}
empty($where_clause) or $where_clause = 'WHERE ' . $where_clause;
empty($having_clause) or $having_clause = 'HAVING ' . $having_clause;
$sql = "SELECT
behaviour.hash,
Sum(behaviour.timespent) AS timeSpent,
new_table.percentile_rank,
Count( * ) AS total
FROM behaviour,
INNER JOIN audience ON behaviour.hash = audience.hash,
INNER JOIN new_table ON behaviour.hash = new_table.hash
{$where_clause}
GROUP BY behaviour.hash
{$having_clause}";
//PDO part
$query = $pdo->prepare($sql);
$result = $query->execute($params);
//...
答案 2 :(得分:4)
这是一种使用字符串连接而不是implode
的简单方法。 &#34;技巧&#34;是用1=1
开始条件。因此,以下每个条件都可以AND
开头。
$andWhere = '';
$andHaving = '';
$params = [];
if (!empty($last_visit)) {
$andWhere .= " AND behaviour.TIMESTAMP >= CURDATE() - INTERVAL :last_visit DAY AND behaviour.TIMESTAMP < CURDATE() + INTERVAL 1 DAY";
$params['last_visit'] = $last_visit;
}
if (!empty($from_country)) {
$andWhere .= " AND audience.country = :from_country";
$params['from_country'] = $from_country;
}
if (!empty($more_than)) {
$andHaving .= " AND COUNT( * ) >= :more_than";
$params['more_than'] = $more_than;
}
if (!empty($time_spent)) {
$andHaving .= " AND timeSpent >= :time_spent";
$params['time_spent'] = $time_spent;
}
if (!empty($lead_scoring)) {
$andHaving .= " AND new_table.percentile_rank >= :lead_scoring";
$params['lead_scoring'] = $lead_scoring;
}
$urlPlaceholders = [];
foreach ($vals as $key => $val) {
$urlPlaceholders[] = ":url_$key";
$params["url_$key"] = $val;
}
if (count($vals) > 0) {
$inUrl = implode(',', $urlPlaceholders);
$andWhere .= " AND url IN ($inUrl)";
}
$sql = "
SELECT behaviour.hash,
Sum(behaviour.timespent) AS timeSpent,
new_table.percentile_rank,
Count(*) AS total
FROM behaviour
JOIN audience ON behaviour.hash = audience.hash
JOIN new_table ON behaviour.hash = new_table.hash
WHERE 1=1 {$andWhere}
GROUP BY behaviour.hash
HAVING 1=1 {$andHaving}
";
#var_export($sql);
#var_export($params);
$sth = $dbh->prepare($sql);
$sth->execute($params);
$data = $sth->fetchAll(PDO::FETCH_ASSOC);
#var_export($data);
拥有像
这样的样本数据$last_visit = '';
$from_country = 'UK';
$more_than = '5';
$time_spent = '3';
$lead_scoring = '';
$vals = ['u1', 'u2'];
您将获得以下查询:
SELECT behaviour.hash,
Sum(behaviour.timespent) AS timeSpent,
new_table.percentile_rank,
Count(*) AS total
FROM behaviour
JOIN audience ON behaviour.hash = audience.hash
JOIN new_table ON behaviour.hash = new_table.hash
WHERE 1=1 AND audience.country = :from_country AND url IN (:url_0,:url_1)
GROUP BY behaviour.hash
HAVING 1=1 AND COUNT(*) >= :more_than AND timeSpent >= :time_spent
使用这些绑定:
array (
'from_country' => 'UK',
'more_than' => '5',
'time_spent' => '3',
'url_0' => 'u1',
'url_1' => 'u2',
)
答案 3 :(得分:2)
如果有唯一的问题,为什么不将它分成不同的块,如下所示:
$conditions = array();
if (!empty($last_visit)) $conditions[] = "behaviour.TIMESTAMP >= DATE_SUB( CURDATE( ) , INTERVAL '".$last_visit."' DAY) AND behaviour.TIMESTAMP < DATE_ADD( CURDATE( ) , INTERVAL 1 DAY ) ";
if (!empty($from_country)) $conditions[] = "audience.country = '".$from_country."'";
$conditionString = implode(' AND ', $conditions);
$conditions_having = array();
if (!empty($more_than)) $conditions_having[] = "COUNT( * ) >= '".$more_than."'";
if (!empty($time_spent)) $conditions_having[] = "timeSpent >= '".$time_spent."'";
if (!empty($lead_scoring)) $conditions_having[] = "new_table.percentile_rank >= '".$lead_scoring."'";
$conditionString .= " GROUP BY behaviour.hash"
if(count($conditions_having))
$conditionString .= " HAVING ".implode(' AND ', $conditions_having);
$sql = "SELECT behaviour.hash,
Sum(behaviour.timespent) AS timeSpent,
new_table.percentile_rank,
Count( * ) AS total
FROM behaviour,
audience,
new_table
WHERE ($url) AND ".$conditionString;
答案 4 :(得分:2)
您只能将您的内爆函数更改为此代码
$conditionString = implode(' ', array_map(function($item) {
if ((strpos($item, 'timeSpent') !== false))
return 'HAVING '.$item;
return 'AND '.$item;
}, $conditions));
请注意您的代码容易受到攻击。
有关详细信息,请参阅:SQL Injection In CAPEC
答案 5 :(得分:2)
$url= 'url="'.implode('" OR url="', $vals).'"';
$conditions = array();
$havings = array();
if (!empty($last_visit)) $conditions[] = "behaviour.TIMESTAMP >= DATE_SUB( CURDATE( ) , INTERVAL '".$last_visit."' DAY) AND behaviour.TIMESTAMP < DATE_ADD( CURDATE( ) , INTERVAL 1 DAY ) ";
if (!empty($from_country)) $conditions[] = "audience.country = '".$from_country."'";
if (!empty($more_than)) $havings[] = "COUNT( * ) >= '".$more_than."'";
if (!empty($time_spent)) $havings[] = "timeSpent >= '".$time_spent."'";
if (!empty($lead_scoring)) $havings[] = "new_table.percentile_rank >= '".$lead_scoring."'";
$conditionString = implode(' AND ', $conditions);
$havingString = '';
if(count($havings)>0) {
$havingString = ' having '.implode(', ', $havings);
}
$sql = "SELECT behaviour.hash,
Sum(behaviour.timespent) AS timeSpent,
new_table.percentile_rank,
Count( * ) AS total
FROM behaviour,
audience,
new_table
WHERE ($url) AND ".$conditionString.$havingString;