PHP多维数组添加计算列(points)及SQL优化
2025-03-20 00:52:10
PHP 多维数组中添加计算列
最近遇到一个需求, 需要给 PHP 从数据库查询出来的多维数组的每一行添加一个计算得来的新列 points
。 简单记录一下解决过程和思路。
问题
原本的 PHP 代码大概是这样:
$children = mysql_query("SELECT c.id, c.name, c.age, c.photoName, c.panelColor FROM children as c");
$temp = array();
while ($child = mysql_fetch_assoc($children)) {
// 获取孩子的评分
$ratings = mysql_query('
SELECT r.behaviourID, t.points, t.typeName
FROM behaviourRatings as r
JOIN behaviourTypes as t
ON r.behaviourID = t.typeID
WHERE r.childID = ' . $child['id']);
// 循环评分计算总分
$totalPoints = 0;
while ($childRatings = mysql_fetch_array($ratings)){
$totalPoints = ($totalPoints + $childRatings['points']);
}
// 分数处理逻辑
if(($totalPoints + $maxPoints) > $maxPoints) {
$total = $maxPoints;
} else if($totalPoints < 0){
$total = ($maxPoints + $totalPoints);
}else{
$total = ($maxPoints - $totalPoints);
}
// 设置 child 数组
$temp[] = $child;
}
$response = array();
$response['timestamp'] = $currentmodif;
$response['children'] = $temp;
echo json_encode($response, JSON_PRETTY_PRINT);
这段代码从 children
表查询数据, 然后通过循环和计算, 得到每个 child
的 totalPoints
,并经过处理,赋值到 $total
。目标是在 $temp
数组的每个元素(也就是每个 $child
)里加一个 points
键, 值就是 $total
。
之前试过 $temp['points'] = $total;
这种方式, 但这会把 points
放到 $temp
数组的 外面,而不是每个 $child
数组里面。
现在的输出是这样的:
{
"timestamp": 1482918104,
"children": [
{
"id": "1",
"name": "Maya",
"age": "5",
"photoName": "maya.png",
"panelColor": ""
},
{
"id": "2",
"name": "Brynlee",
"age": "3",
"photoName": "brynlee.png",
"panelColor": "green"
}
]
}
需要把每个 child 的 points
加进去。
问题原因
问题的关键在于对 PHP 数组结构的理解, 以及赋值操作的位置。 $temp[] = $child;
是将 $child
数组作为 $temp
的一个新元素 追加 进去。如果在这个操作 之后 再用 $temp['points'] = $total
, 那么 points
就成了 $temp
的一个键, 而不是各个 $child
的键。
解决方案
方案一: 直接在 $child
数组中添加
最直接的方法, 就是在把 $child
放入 $temp
之前, 直接给 $child
数组添加 points
键。
$children = mysql_query("SELECT c.id, c.name, c.age, c.photoName, c.panelColor FROM children as c");
$temp = array();
while ($child = mysql_fetch_assoc($children)) {
$ratings = mysql_query('
SELECT r.behaviourID, t.points, t.typeName
FROM behaviourRatings as r
JOIN behaviourTypes as t
ON r.behaviourID = t.typeID
WHERE r.childID = ' . $child['id']);
$totalPoints = 0;
while ($childRatings = mysql_fetch_array($ratings)){
$totalPoints = ($totalPoints + $childRatings['points']);
}
if(($totalPoints + $maxPoints) > $maxPoints) {
$total = $maxPoints;
} else if($totalPoints < 0){
$total = ($maxPoints + $totalPoints);
}else{
$total = ($maxPoints - $totalPoints);
}
// 直接添加到 $child 数组
$child['points'] = $total;
$temp[] = $child;
}
$response = array();
$response['timestamp'] = $currentmodif;
$response['children'] = $temp;
echo json_encode($response, JSON_PRETTY_PRINT);
原理:
$child
数组在循环中被创建, 我们在将它添加到 $temp
之前 , 用 $child['points'] = $total;
直接给 $child
添加了 points
。 这样, $temp
中的每个元素都包含 points
。
输出结果:
{
"timestamp": 1482918104,
"children": [
{
"id": "1",
"name": "Maya",
"age": "5",
"photoName": "maya.png",
"panelColor": "",
"points": 100
},
{
"id": "2",
"name": "Brynlee",
"age": "3",
"photoName": "brynlee.png",
"panelColor": "green",
"points": 85
}
]
}
方案二:使用 array_map
如果不想修改原有的循环逻辑, 也可以用 array_map
函数来处理。
$children = mysql_query("SELECT c.id, c.name, c.age, c.photoName, c.panelColor FROM children as c");
$temp = array();
while ($child = mysql_fetch_assoc($children)) {
$ratings = mysql_query('
SELECT r.behaviourID, t.points, t.typeName
FROM behaviourRatings as r
JOIN behaviourTypes as t
ON r.behaviourID = t.typeID
WHERE r.childID = ' . $child['id']);
$totalPoints = 0;
while ($childRatings = mysql_fetch_array($ratings)){
$totalPoints = ($totalPoints + $childRatings['points']);
}
if(($totalPoints + $maxPoints) > $maxPoints) {
$total = $maxPoints;
} else if($totalPoints < 0){
$total = ($maxPoints + $totalPoints);
}else{
$total = ($maxPoints - $totalPoints);
}
$child['total'] = $total; // 先临时存到 'total'里。
$temp[] = $child;
}
// 使用 array_map 添加 'points'
$temp = array_map(function($item) {
$item['points'] = $item['total'];
unset($item['total']); // 不需要的话删掉
return $item;
}, $temp);
$response = array();
$response['timestamp'] = $currentmodif;
$response['children'] = $temp;
echo json_encode($response, JSON_PRETTY_PRINT);
原理:
array_map
对 $temp
数组的每个元素执行一个回调函数。 在回调函数里, 我们把每个元素的 'total'
的值赋给 'points'
, 然后把 'total'
删掉 (如果不需要的话)。
安全建议:
- 弃用
mysql_*
函数:mysql_*
函数族已经在 PHP 5.5 中弃用,并在 PHP 7.0 中完全移除。 应该使用MySQLi
或PDO
扩展。 - SQL 注入防护: 代码中的 SQL 查询存在 SQL 注入的风险。务必使用预处理语句或者对输入进行转义。
使用 MySQLi 的改进示例:
$mysqli = new mysqli("localhost", "user", "password", "database");
$stmt = $mysqli->prepare("SELECT c.id, c.name, c.age, c.photoName, c.panelColor FROM children as c");
$stmt->execute();
$result = $stmt->get_result();
$temp = array();
while ($child = $result->fetch_assoc()) {
$stmt2 = $mysqli->prepare('
SELECT t.points
FROM behaviourRatings as r
JOIN behaviourTypes as t
ON r.behaviourID = t.typeID
WHERE r.childID = ?');
$stmt2->bind_param("i", $child['id']); // 'i' 表示 integer
$stmt2->execute();
$ratings = $stmt2->get_result();
$totalPoints = 0;
while ($childRatings = $ratings->fetch_assoc()){
$totalPoints = ($totalPoints + $childRatings['points']);
}
if(($totalPoints + $maxPoints) > $maxPoints) {
$total = $maxPoints;
} else if($totalPoints < 0){
$total = ($maxPoints + $totalPoints);
}else{
$total = ($maxPoints - $totalPoints);
}
$child['points'] = $total;
$temp[] = $child;
}
$response = array();
$response['timestamp'] = $currentmodif; // 假定 $currentmodif 已经设置
$response['children'] = $temp;
echo json_encode($response, JSON_PRETTY_PRINT);
$stmt->close();
$stmt2->close();
$mysqli->close();
代码优化及进阶使用技巧:
-
合并查询 : 可以考虑将获取
children
信息和计算points
的两个查询合并成一个,减少数据库查询次数。SELECT c.id, c.name, c.age, c.photoName, c.panelColor, COALESCE(SUM(t.points), 0) AS points -- 如果没有匹配的 ratings, 则 points 为 0 FROM children AS c LEFT JOIN behaviourRatings AS r ON c.id = r.childID LEFT JOIN behaviourTypes AS t ON r.behaviourID = t.typeID GROUP BY c.id;
PHP部分简化为:
$mysqli = new mysqli("localhost", "user", "password", "database");
$stmt = $mysqli->prepare('SELECT
c.id,
c.name,
c.age,
c.photoName,
c.panelColor,
COALESCE(SUM(t.points), 0) AS points
FROM children AS c
LEFT JOIN behaviourRatings AS r ON c.id = r.childID
LEFT JOIN behaviourTypes AS t ON r.behaviourID = t.typeID
GROUP BY c.id');
$stmt->execute();
$result = $stmt->get_result();
$temp = array();
while ($child = $result->fetch_assoc()){
// 简化分数逻辑
$total = min(max($maxPoints, $maxPoints + $child['points']), $maxPoints);
$child['points'] = $total;
$temp[] = $child;
}
$response = array();
$response['timestamp'] = time();
$response['children'] = $temp;
echo json_encode($response, JSON_PRETTY_PRINT);
$stmt->close();
$mysqli->close();
- 分数计算逻辑优化 :
$total
的计算逻辑可以用min
和max
函数简化:
$total = min(max($maxPoints, $maxPoints + $totalPoints), $maxPoints);
这样就不用 if...else if...else
了, 更简洁。
总结一下, 最好的解决方案是直接在获取 $child
数据后, 立刻计算并添加到 $child
数组里。 其次, 如果不想改动太大, 可以用 array_map
。 当然, 最重要的还是换用 MySQLi
或 PDO
, 并使用预处理语句来防止 SQL 注入, 这比单纯加个字段要重要得多! 合并 SQL 更是能大幅提升性能。