假设我有这三个表:
Person table
id | name
1 | Sam
Dress table
id | person_id |name
1 | 1 |shorts
2 | 1 |tshirt
Interest table
id | person_id | interest
1 | 1 | football
2 | 1 | basketball
Run Code Online (Sandbox Code Playgroud)
(上面只是一个简单的例子,实际上我有很多表要加入)
我需要在页面上显示所有这些细节,因此将所有这些细节合并为一个左连接查询主要是为了提高性能.现在我们得到的结果应该是针对一个人的服装和兴趣的组合的重复结果而混乱.要解决这个问题,我需要手动循环以安排我想要使用的数组.我的查询看起来像这样(我做得对吗?):
select p.id, d.name, i.interest
from person as p
left join dress as d on p.id = d.person_id
left join interest as i on p.id = i.person_id
where p.id = 1;
Run Code Online (Sandbox Code Playgroud)
有什么更好的方法呢?我知道我也可以使用GROUP_CONCAT来避免重复.
更新输出
我希望我的最终结果看起来像这样(我知道我需要循环才能获得这种格式),查询表格的最佳方法是什么?
[
[
'id' => 1,
'dresses' => [
[
'id' => 1,
'name' => 'shorts',
...more columns
],
[
'id' => 2,
'name' => 'tshirt',
..more columns
]
],
'interests' => [
'football',
'basketball'
]
]
]
Run Code Online (Sandbox Code Playgroud)
小智 9
数据量与灵活性:
就个人而言,对于你的任务 - 让我们假设它比它呈现的更复杂,好吗?- 我不建议你使用任何sql函数(比如group_concat等).当然,您可以通过使用它们获得较少量的数据.但是你肯定会失去阅读和处理获取结果所需的灵活性.
考虑使用(可能很多)更多列运行查询.如果其中一些突然要求你应用其他sql函数或条件 - 你是否仍然想要"美化"查询 - 比如另一个简单但棘手的GROUP BY条款?结果读取算法会发生什么?它必须(可能很难) - 再次考虑.
资源者:
另外,请记住,所有这些group_concat功能/选择也正在吃MySQL资源.
索引和EXPLAIN优化:
我只是在考虑一种情况,你可能希望将索引应用到某些字段 - 例如,用于搜索目的.并且你想要用EXPLAIN命令检查它们的有效性/速度.我真诚地不知道是否group_concat会让这个变得容易和透明.
显示目的与后处理?
通常,类似group_concat的函数用于显示目的,例如在数据网格/表格中.但是您的任务需要对获取的数据进行后处理.
已经分类:
也就是说,在您的原始问题中,您已经提出了一个SQL解决方案.恕我直言,你的版本是正确的和灵活的.你的sql语句已经正确了.您可以应用某些ORDER BY条件,以便从获取的数据直接构建排序数组.
获取数据和/或后处理......替代方案?
您正在尝试一次获取大量的数据和进行后期处理它.这是一个标志,数据库和 PHP引擎都必须工作很多.也许以另一种方式投射任务会更好.例如,在没有后处理的情况下获取大量数据.或者获取较少量的数据并允许PHP对其进行后期处理.看看我今天在PDOStatement::fetchAll网页上发现了什么
使用此方法获取大型结果集将导致对系统和可能的网络资源的大量需求.不是检索所有数据并在PHP中操作它,而是考虑使用数据库服务器来操作结果集.例如,在使用PHP检索和处理结果之前,使用SQL中的WHERE和ORDER BY子句来限制结果.
统一阵列结构:
是否有特殊原因要构建结果数组以使其具有不均匀的结构(关于interests)?统一阵列结构不是更好吗?在后处理后查看我在PHP中的结果,以了解我的意思与您请求的结构.
代码版本:
我已经准备了一个php版本 - 而不是针对这个问题的OOP - 数据获取和数组构建步骤.我已经对它进行了评论,并且还显示了我正在测试的数据源.最后,我还将介绍结果.构建最终数组($personDetails)的步骤非常简单:循环获取的数据并仅传输它(!)(如果尚未传输).
来自不同表的相同列的强制别名:
我试图获取所有dress和interest(使用通配符)这样的一次数据:
SELECT d.*, i.* FROM ...
Run Code Online (Sandbox Code Playgroud)
我在PHP中运行了一些测试并尝试了一些编码选项,但最后,我得出结论:以这样的方式处理feched数据是不可能的:
$fetchedData = $statement->fetchAll(PDO::FETCH_ASSOC);
foreach ($fetchedData as $key => $record) {
$dressId = $record['d.id'];
$interestId = $record['i.id'];
//...
}
Run Code Online (Sandbox Code Playgroud)
无论我尝试过什么,PHP都没有在$record数组中为这两id列分配不同的项目.唯一指定的项目始终对应id于列列表中的最后一列.因此,对于正确的输出,跳过使用通配符并对具有相同名称并驻留在不同表中的所有列进行别名是一项强制性任务.像这样:
SELECT d.id AS dress_id, i.id AS interest_id FROM ...
Run Code Online (Sandbox Code Playgroud)
...和PHP代码:
$fetchedData = $statement->fetchAll(PDO::FETCH_ASSOC);
foreach ($fetchedData as $key => $record) {
$dressId = $record['dress_id'];
$interestId = $record['interest_id'];
//...
}
Run Code Online (Sandbox Code Playgroud)
我会说实话:即使这种情况在某种程度上是直接的,我从来没有测试过它.我总是对具有相同名称的列使用别名,但现在我也通过代码测试给出了确定性.
按键地址数组项目与搜索数组项目键:
结果array($personDetails)保存获取的数据,如下所示:每个人id都是相应详细信息项的KEY.为什么我这样做(并推荐)?因为您可能希望通过传递所需的ID 直接从数组中读取一个人.最好通过其唯一键来处理数组项,而不是在整个数组中搜索它.
哦,差点忘了:我在两个人身上运行了这个例子,有不同的数据库条目/记录号.
祝好运.
<?php
// Db configs.
define('HOST', 'localhost');
define('PORT', 3306);
define('DATABASE', 'db');
define('USERNAME', 'user');
define('PASSWORD', 'pass');
define('CHARSET', 'utf8');
/*
* Error reporting.
* To do: define an error handler, an exception handler and a shutdown
* handler function to handle the raised errors and exceptions.
*
* @link http://php.net/manual/en/function.error-reporting.php
*/
error_reporting(E_ALL);
ini_set('display_errors', 1); // SET IT TO 0 ON A LIVE SERVER!
/*
* Create a PDO instance as db connection to db.
*
* @link http://php.net/manual/en/class.pdo.php
* @link http://php.net/manual/en/pdo.constants.php
* @link http://php.net/manual/en/pdo.error-handling.php
* @link http://php.net/manual/en/pdo.connections.php
*/
$connection = new PDO(
sprintf('mysql:host=%s;port=%s;dbname=%s;charset=%s', HOST, PORT, DATABASE, CHARSET)
, USERNAME
, PASSWORD
, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => FALSE,
PDO::ATTR_PERSISTENT => TRUE,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]
);
// Person ID's to fetch.
$personId1 = 1;
$personId2 = 2;
/*
* The SQL statement to be prepared. Notice the so-called named markers.
* They will be replaced later with the corresponding values from the
* bindings array when using PDOStatement::bindValue.
*
* When using named markers, the bindings array will be an associative
* array, with the key names corresponding to the named markers from
* the sql statement.
*
* You can also use question mark markers. In this case, the bindings
* array will be an indexed array, with keys beginning from 1 (not 0).
* Each array key corresponds to the position of the marker in the sql
* statement.
*
* @link http://php.net/manual/en/mysqli.prepare.php
*/
$sql = 'SELECT
p.id AS person_id,
d.id AS dress_id,
d.name AS dress_name,
d.produced_in AS dress_produced_in,
i.id AS interest_id,
i.interest,
i.priority AS interest_priority
FROM person AS p
LEFT JOIN dress AS d ON d.person_id = p.id
LEFT JOIN interest AS i ON i.person_id = p.id
WHERE
p.id = :personId1 OR
p.id = :personId2
ORDER BY
person_id ASC,
dress_name ASC,
interest ASC';
/*
* The bindings array, mapping the named markers from the sql
* statement to the corresponding values. It will be directly
* passed as argument to the PDOStatement::execute method.
*
* @link http://php.net/manual/en/pdostatement.execute.php
*/
$bindings = [
':personId1' => $personId1,
':personId2' => $personId2,
];
/*
* Prepare the sql statement for execution and return a statement object.
*
* @link http://php.net/manual/en/pdo.prepare.php
*/
$statement = $connection->prepare($sql);
/*
* Execute the prepared statement. Because the bindings array
* is directly passed as argument, there is no need to use any
* binding method for each sql statement's marker (like
* PDOStatement::bindParam or PDOStatement::bindValue).
*
* @link http://php.net/manual/en/pdostatement.execute.php
*/
$executed = $statement->execute($bindings);
/*
* Fetch data (all at once) and save it into $fetchedData array.
*
* @link http://php.net/manual/en/pdostatement.fetchall.php
*/
$fetchedData = $statement->fetchAll(PDO::FETCH_ASSOC);
// Just for testing. Display fetched data.
echo '<pre>' . print_r($fetchedData, TRUE) . '</pre>';
/*
* Close the prepared statement.
*
* @link http://php.net/manual/en/pdo.connections.php Example #3 Closing a connection.
*/
$statement = NULL;
/*
* Close the previously opened database connection.
*
* @link http://php.net/manual/en/pdo.connections.php Example #3 Closing a connection.
*/
$connection = NULL;
// Filter the fetched data.
$personDetails = [];
foreach ($fetchedData as $key => $record) {
$personId = $record['person_id'];
$dressId = $record['dress_id'];
$dressName = $record['dress_name'];
$dressProducedIn = $record['dress_produced_in'];
$interestId = $record['interest_id'];
$interest = $record['interest'];
$interestPriority = $record['interest_priority'];
// Check and add person id as key.
if (!array_key_exists($personId, $personDetails)) {
$personDetails[$personId] = [
'dresses' => [],
'interests' => [],
];
}
// Check and add dress details.
if (!array_key_exists($dressId, $personDetails[$personId]['dresses'])) {
$personDetails[$personId]['dresses'][$dressId] = [
'name' => $dressName,
'producedIn' => $dressProducedIn,
// ... (other fetched dress details)
];
}
// Check and add interest details.
if (!array_key_exists($interestId, $personDetails[$personId]['interests'])) {
$personDetails[$personId]['interests'][$interestId] = [
'interest' => $interest,
'interestPriority' => $interestPriority,
// ... (other fetched interest details)
];
}
}
// Just for testing. Display person details list.
echo '<pre>' . print_r($personDetails, TRUE) . '</pre>';
Run Code Online (Sandbox Code Playgroud)
获取$fetchedData两个人的数据():
Array
(
[0] => Array
(
[person_id] => 1
[dress_id] => 1
[dress_name] => shorts
[dress_produced_in] => Taiwan
[interest_id] => 2
[interest] => basketball
[interest_priority] => 2
)
[1] => Array
(
[person_id] => 1
[dress_id] => 1
[dress_name] => shorts
[dress_produced_in] => Taiwan
[interest_id] => 1
[interest] => football
[interest_priority] => 1
)
[2] => Array
(
[person_id] => 1
[dress_id] => 2
[dress_name] => tshirt
[dress_produced_in] => USA
[interest_id] => 2
[interest] => basketball
[interest_priority] => 2
)
[3] => Array
(
[person_id] => 1
[dress_id] => 2
[dress_name] => tshirt
[dress_produced_in] => USA
[interest_id] => 1
[interest] => football
[interest_priority] => 1
)
[4] => Array
(
[person_id] => 2
[dress_id] => 3
[dress_name] => yellow hat
[dress_produced_in] => England
[interest_id] => 4
[interest] => films
[interest_priority] => 1
)
[5] => Array
(
[person_id] => 2
[dress_id] => 3
[dress_name] => yellow hat
[dress_produced_in] => England
[interest_id] => 5
[interest] => programming
[interest_priority] => 1
)
[6] => Array
(
[person_id] => 2
[dress_id] => 3
[dress_name] => yellow hat
[dress_produced_in] => England
[interest_id] => 3
[interest] => voleyball
[interest_priority] => 3
)
)
Run Code Online (Sandbox Code Playgroud)
PHP中的过滤数据,例如最终数组($personDetails)包含两个人的信息:
Array
(
[1] => Array
(
[dresses] => Array
(
[1] => Array
(
[name] => shorts
[producedIn] => Taiwan
)
[2] => Array
(
[name] => tshirt
[producedIn] => USA
)
)
[interests] => Array
(
[2] => Array
(
[interest] => basketball
[interestPriority] => 2
)
[1] => Array
(
[interest] => football
[interestPriority] => 1
)
)
)
[2] => Array
(
[dresses] => Array
(
[3] => Array
(
[name] => yellow hat
[producedIn] => England
)
)
[interests] => Array
(
[4] => Array
(
[interest] => films
[interestPriority] => 1
)
[5] => Array
(
[interest] => programming
[interestPriority] => 1
)
[3] => Array
(
[interest] => voleyball
[interestPriority] => 3
)
)
)
)
Run Code Online (Sandbox Code Playgroud)