【问题标题】:Accurate Pagination with left Joins使用左连接的准确分页
【发布时间】:2012-03-06 04:09:23
【问题描述】:

我已经考虑这个问题有一段时间了,我认为最好四处打听,听听其他人的想法。

我正在构建一个在 Mysql 上存储位置的系统。每个位置都有一个类型,有些位置有多个地址。

表格看起来像这样

location
  - location_id (autoincrement)
  - location_name
  - location_type_id 

location_types
  - type_id
  - type_name (For example "Laundry")

location_information
  - location_id (Reference to the location table)
  - location_address
  - location_phone

因此,如果我想查询数据库中最近添加的 10 个,我会使用以下内容:

SELECT l.location_id, l.location_name,
       t.type_id, t.type_name,
       i.location_address, i.location_phone
FROM location AS l
LEFT JOIN location_information AS i ON (l.location_id = i.location_id)
LEFT JOIN location_types AS t ON (l.location_type_id = t.type_id)
ORDER BY l.location_id DESC
LIMIT 10

对吗?但问题是,如果一个位置有超过 1 个地址,则限制/分页将不准确,除非我“GROUP BY l.location_id”,但这将只显示每个地方的一个地址.. 会发生什么有多个地址的地方?

所以我认为解决这个问题的唯一方法是在循环中进行查询。像这样的东西(伪代码):

$db->query('SELECT l.location_id, l.location_name,
            t.type_id, t.type_name
            FROM location AS l
            LEFT JOIN location_types AS t ON (l.location_type_id = t.type_id)
            ORDER BY l.location_id DESC
            LIMIT 10');

 $locations = array();
 while ($row = $db->fetchRow())
 {
     $db->query('SELECT i.location_address, i.location_phone
                 FROM location_information AS i
                 WHERE i.location_id = ?', $row['location_id']);

     $locationInfo = $db->fetchAll();
     $locations[$row['location_id']] = array('location_name' => $row['location_name'],
                                             'location_type' => $row['location_type'],
                                             'location_info' => $locationInfo);

 }

现在我得到了最后 10 个位置,但这样做我最终得到了至少 10 个查询,而且我认为这对应用程序性能没有帮助。

有没有更好的方法来实现我正在寻找的东西? (准确的分页)。

【问题讨论】:

  • 您要返回哪个地址(local_information 记录)以获得位置?如果你能说出你想要哪一个,我们就可以告诉计算机你想要哪一个。

标签: php mysql performance


【解决方案1】:

这是您的原始查询

SELECT l.location_id, l.location_name, 
       t.type_id, t.type_name, 
       i.location_address, i.location_phone 
FROM location AS l 
LEFT JOIN location_information AS i ON (l.location_id = i.location_id) 
LEFT JOIN location_types AS t ON (l.location_type_id = t.type_id) 
ORDER BY l.location_id DESC 
LIMIT 10 

您最后执行分页。如果你重构这个查询,你可以更早地执行分页。

SELECT l.location_id, l.location_name, 
       t.type_id, t.type_name, 
       i.location_address, i.location_phone 
FROM
    (SELECT location_id,location_type_id FROM location
    ORDER BY location_id LIMIT 10) AS k
    LEFT JOIN location AS l ON (k.location_id = l.location_id)
    LEFT JOIN location_information AS i ON (k.location_id = i.location_id) 
    LEFT JOIN location_types AS t ON (l.location_type_id = t.type_id) 
;

请注意,我创建了一个名为 k 的子查询。 10 把钥匙被拿起并订购 FIRST !!!

然后 JOIN 可以从那里继续,希望只使用 10 个 location_id。

对子查询k有帮助的是一个带有location_id和location_type_id的索引

ALTER TABLE location ADD INDEX id_type_ndx (location_id,location_type_id);

您可能会喜欢这种方法的其他内容

你如何查询接下来的 10 个 id (ids 11 - 20) ?像这样:

SELECT l.location_id, l.location_name, 
       t.type_id, t.type_name, 
       i.location_address, i.location_phone 
FROM
    (SELECT location_id,location_type_id FROM location
    ORDER BY location_id LIMIT 10,10) AS k
    LEFT JOIN location AS l ON (k.location_id = l.location_id)
    LEFT JOIN location_information AS i ON (k.location_id = i.location_id) 
    LEFT JOIN location_types AS t ON (l.location_type_id = t.type_id) 
;

您所要做的就是在每个新页面中更改子查询k 中的LIMIT 子句。

  • LIMIT 20,10
  • LIMIT 30,10
  • 等等……

我可以通过删除位置表来改进重构,并让子查询 k 携带所需的字段,如下所示:

SELECT k.location_id, k.location_name, 
       t.type_id, t.type_name, 
       i.location_address, i.location_phone 
FROM
    (SELECT location_id,location_type_id,location_name
    FROM location ORDER BY location_id LIMIT 10,10) AS k
    LEFT JOIN location_information AS i ON (k.location_id = i.location_id) 
    LEFT JOIN location_types AS t ON (k.location_type_id = t.type_id) 
;

这个版本不需要额外的索引。

试试看!!!

【讨论】:

  • 考虑到在多页之后使用增加的 LIMIT 可能会很昂贵,所以主要的想法是好的。
  • 谢谢!这是我一直在寻找的那种查询,我从来没有想过要做这样的子查询!
【解决方案2】:

比循环和 10 个查询更好,您可以查询分页的 location.location_id 限制 10,将其连接成逗号分隔的字符串,然后完整查询以获取 WHERE location.location_id IN (1,2,3...{list of ids})

【讨论】:

    【解决方案3】:

    您可以按照原来的想法按 location_id 进行分组,然后使用 group_concat 函数将该位置的所有地址显示为 1 个字段。

    SELECT l.location_id, l.location_name,
       t.type_id, t.type_name,
       group_concat(concat("Address: ",i.location_address, " Phone: ", i.location_phone)) as addresses
    FROM location AS l
    LEFT JOIN location_information AS i ON (l.location_id = i.location_id)
    LEFT JOIN location_types AS t ON (l.location_type_id = t.type_id)
    GROUP BY l.location_id
    ORDER BY l.location_id DESC
    LIMIT 10
    

    【讨论】:

      【解决方案4】:

      有几种方法可以解决这个问题:

      • 您可以在location_information 表中添加一个IsPrimary 位列,并添加一个触发器以确保每个位置始终只有一个location_information 记录,并将此设置为1。
      • 如果没有DateCreatedDateModified 列,您可以使用location_id 列选择最旧或最新的location_information 记录(MIN/MAX)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-09-28
        • 2015-06-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-05-20
        • 2017-01-05
        相关资源
        最近更新 更多