清除mysql中多个左连接的结果

时间:2017-06-02 03:29:23

标签: php mysql

我们说我有这三张桌子:

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

(上面只是一个简单的例子,实际上我有很多表要加入)

我需要在页面上显示所有这些细节,因此将所有细节合并为1个左连接查询主要是为了提高性能。现在我们得到的结果应该是针对一个人的服装和兴趣的组合的重复结果而混乱。要解决这个问题,我需要手动循环以安排我想要使用的数组。我的查询看起来像这样(我做得对吗?):

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; 

有什么更好的方法可以做到这一点?我知道我也可以使用GROUP_CONCAT来避免重复。

使用OUTPUT更新

我希望我的最终结果看起来像这样(我知道我需要循环才能获得这种格式),查询表格的最佳方法是什么?

[
    [
        'id' => 1,
        'dresses' => [
            [
                'id' => 1,
                'name' => 'shorts',
                ...more columns
            ],
            [
                'id' => 2,
                'name' => 'tshirt',
                ..more columns
            ]
        ],
        'interests' => [
            'football',
            'basketball'
        ]
    ]
]

5 个答案:

答案 0 :(得分:8)

数据量与灵活性:

就个人而言,对于你的任务 - 让我们假设它比它呈现的更复杂,好吗? - 我不建议你使用任何sql函数(比如group_concat等)。当然,您可以通过使用它们获得较少量的数据。但是你肯定会失去阅读和处理获取结果所需的灵活性。

考虑使用(可能很多)更多列运行查询。你还想要美化"查询是否其中一些突然要求您应用其他sql函数或条件 - 比如另一个简单但棘手的GROUP BY子句?结果读取算法会发生什么?它必须(可能很难) - 再次考虑。

资源消费者:

另外,请记住,所有这些group_concat函数/选择也都在使用MySQL资源。

索引和EXPLAIN进行优化:

我还在考虑一种情况,例如,您可能希望将索引应用于某些字段 - 用于搜索目的。并且您希望使用EXPLAIN命令检查其有效性/快速性。我真诚地不知道让group_concat成为一个简单而透明的任务。

显示目的与后期处理?

通常,group_concat等函数用于显示目的,例如在数据网格/表格中。但是您的任务需要对获取的数据进行后处理。

已经排序:

那就是说,在你原来的问题中,你已经提出了一个sql解决方案。恕我直言,你的版本是正确的和灵活的。你的sql语句已经正确了。您可以应用一些ORDER BY条件,以便从获取的数据中直接构建排序数组。

获取数据和/或后期处理......替代方案?

您正试图一次性获取大量数据 AND 以对其进行后期处理。这是一个标志,数据库 AND PHP引擎都必须工作很多。也许以另一种方式投射任务会更好。例如。无需后期处理即可获取大量数据。或者获取较少量的数据并允许PHP对其进行后期处理。查看我今天在PDOStatement::fetchAll网页上找到的内容

  • PDOStatement::fetchAll - Return Values
      

    使用此方法获取大型结果集将导致繁重   对系统和可能的网络资源的需求。而不是   检索所有数据并在PHP中操作它,考虑使用   数据库服务器来操作结果集。例如,使用   SQL中的WHERE和ORDER BY子句用于限制之前的结果   使用PHP检索和处理它们。

统一数组结构:

是否有特殊原因要构建结果数组以使其结构不均匀(关于interests)?统一阵列结构会不会更好?在后处理后查看我在PHP中的结果,以了解我的意思与您请求的结构。

代码版本:

我已经为数据提取和数组构建步骤准备了一个php版本 - 而不是针对此问题的OOP。我对它进行了评论,并显示了我正在测试的数据源。最后,我还将介绍结果。构建最终数组($personDetails)的步骤非常简单:循环获取的数据并仅传输(!)(如果尚未传输)。

来自不同表格的相同列的强制别名:

我尝试一次性获取所有dressinterest数据(使用外卡):

SELECT d.*, i.* FROM ...

我在PHP中运行了一些测试并尝试了一些编码选项,但最后,我得出结论:以这样的方式处理feched数据是不可能的:

$fetchedData = $statement->fetchAll(PDO::FETCH_ASSOC);
foreach ($fetchedData as $key => $record) {
    $dressId = $record['d.id'];
    $interestId = $record['i.id'];
    //...
}

对于两个$record列,PHP没有在id数组中分配不同的项目,无论我尝试过什么。唯一指定的项目始终对应于列列表中的最后一个id列。因此,要获得正确的输出,必须跳过使用通配符和别名所有具有相同名称且位于不同表中的列。像这样:

SELECT d.id AS dress_id, i.id AS interest_id FROM ...

...和php代码:

$fetchedData = $statement->fetchAll(PDO::FETCH_ASSOC);
foreach ($fetchedData as $key => $record) {
    $dressId = $record['dress_id'];
    $interestId = $record['interest_id'];
    //...
}

我说实话:即使这种情况在某种程度上是直接的,我从未测试过。我总是对具有相同名称的列使用别名,但现在我也有代码测试给出的确认。

按键解决数组项目与搜索数组项目键:

结果数组($personDetails)按如下方式保存提取的数据:每个人id是相应详细信息项的KEY。为什么我这样做(并推荐)?因为您可能希望直接通过传递所需的ID来从数组中读取某个人。通过其唯一键来处理数组项比在整个数组中搜索它更好。

哦,差点忘了:我在两个人身上运行了这个例子,不同的数据库条目/记录号。

祝你好运。

代码:

测试了下表:

enter image description here

在db编辑器中运行查询的结果:

enter image description here

在PHP中获取并处理数据库数据(read_person_details.php):

<?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>';

PHP代码中的结果:

获取两个人的数据($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
        )

)

在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
                        )

                )

        )

)

答案 1 :(得分:3)

MySQL(或任何其他SQL数据库)不会以您描述的嵌套数组格式返回结果。因此,您必须编写应用程序代码以便以某种方式处理查询结果。

像你一样编写多个连接必然会在连接的表之间创建一个Cartesian product,如果它们中的任何一个匹配多行,这将乘以结果集的大小。

我建议您为每种类型的相关信息运行单独的查询,并将它们组合在应用程序代码中。这是一个例子:

function get_details($pdo, $person_id) {
    $sql = "
        select p.id, d.name
        from person as p
        left join dress as d on p.id = d.person_id
        where p.id = ?";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$person_id]);
    $rows = $stmt->fetchAll();
    foreach ($rows as $row) {
        if (!isset($data[$row['id']])) {
            $data[$row['id']] = [
                'id' => $row['id'],
                'dress' => []
            ];
        }
        $data[$row['id']]['dress'][] = $row['name'];
    }

    $sql = "
        select p.id, i.interest
        from person as p
        left join interest as i on p.id = i.person_id
        where p.id = ?";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$person_id]);
    $rows = $stmt->fetchAll();
    foreach ($rows as $row) {
        if (!isset($data[$row['id']])) {
            $data[$row['id']] = [
                'id' => $row['id'],
                'interest' => []
            ];
        }
        $data[$row['id']]['interest'][] = $row['interest'];
    }
    return $data;
}

我通过以下方式调用它来测试它:

$pdo = new PDO("mysql:host=127.0.0.1;dbname=test", "xxxx", "xxxxxxxx");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);

$result = get_details($pdo, 1);

print_r($result);

输出:

Array
(
    [1] => Array
        (
            [id] => 1
            [dress] => Array
                (
                    [0] => shorts
                    [1] => tshirt
                )

            [interest] => Array
                (
                    [0] => football
                    [1] => basketball
                )

        )

)

重新评论:

我无法保证哪种方法会有更好的效果。这取决于其他几个因素,例如您需要查询的行数,创建GROUP_CONCAT()解决方案所需的临时表的速度,传输包含重复项的大型结果集的网络速度等等。

与所有与性能相关的问题一样,最终的答案是您需要在服务器上测试数据。

答案 2 :(得分:1)

使用UNION

怎么样?
(
SELECT p.id,  d.id AS type_id, d.name, 'dress' AS `type`
FROM person AS p
    LEFT JOIN dress AS d ON p.id = person_id 
        WHERE p.id = 1
)
UNION
(
SELECT  p.id, i.id AS type_id , i.interest AS NAME, 'interest' AS `type`
FROM person  AS p
    LEFT JOIN interest AS i ON p.id = person_id 
    WHERE p.id = 1
)

答案 3 :(得分:0)

您只需在group by上使用group_concat人ID和distinct以及添加dress and interest,否则您将获得duplicate着装和兴趣的结果。

<强>查询:

select p.id, p.name, group_concat(distinct i.interest) as interests,group_concat(distinct d.name) as dresses 
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 group by p.id;

因此您将以逗号分隔interest and dress

<强>输出:

+----+------+---------------------+---------------+
| id | name | interests           | dresses       |
+----+------+---------------------+---------------+
|  1 | Sam  | football,basketball | shorts,tshirt |
+----+------+---------------------+---------------+

答案 4 :(得分:0)

完成此操作的几种基本方法:

收集所有信息

根据@aendeerei的建议,扩展您的查询:

   SELECT p.id AS p_id,
          p.name AS p_name,
          d.id AS d_id,
          d.name AS d_name,
          i.id AS i_id,
          i.name AS i_name
     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;

然后在应用程序代码中:

$person = [];

foreach ($rows as $row) {

  $person['id'] = $row['p_id'];
  $person['name'] = $row['p_name'];

  if($row['d_id']){
    $person['dresses'][$row['d_id']] = [
       'id' => $row['d_id'],
       'name' => $row['d_name'],
    ]
  }

  if($row['i_id']){
    $person['interests'][$row['i_id']] = [
       'id' => $row['i_id'],
       'name' => $row['i_name'],
    ]
  }
}

当您通过各自的ID索引连衣裙和兴趣数组时,任何重复数据都会覆盖相同的索引。使用一些if(array_key_exists(...))条件也可以避免覆盖。

这个想法可以扩展到$persons数组中的多个人,通过他们自己的id索引每个人。

这里的缺点是,当人们拥有大量的衣服和兴趣时,你会返回大量的冗余数据..(5件礼服和5个人的利益将会返回他们的名字25次)。

单独收集相关数据

或者根据@BillKarwin的建议,您可以为每个表运行单独的查询。我想我甚至想进一步分开人员表。

 SELECT * FROM person WHERE id = 1;

从单行返回构建人员数组

 SELECT * FROM dress WHERE person_id = 1;

根据返回的行构建人物的衣服阵列。

 SELECT * FROM interest WHERE person_id = 1;

根据返回的行构建person的兴趣数组。

使用WHERE person_id IN (...)对依赖查询使用第一个中的人员ID进行扩展,可以将其扩展为多个人。

这样做的缺点是你正在运行3个不同的查询,这可能需要更长的时间并增加复杂性。如果有人在中间删除了某个人,你可能会担心一些小的并发问题。看来已删除的人仍然存在,但没有衣服/兴趣。