如何对带有联接的 SQL 查询的结果应用分页?

Posted

技术标签:

【中文标题】如何对带有联接的 SQL 查询的结果应用分页?【英文标题】:How to apply pagination to the result of a SQL query with Joins? 【发布时间】:2012-06-11 00:07:47 【问题描述】:

我有连接 3 个表的 SQL 查询,其中一个只是连接另外两个的多对多。我使用 Spring JDBC ResultSetExtractor 将 ResultSet 转换为我的对象,大致如下所示:

class Customer 
    private String id;
    private Set<AccountType> accountTypes;
    ...

ResultSetExtractor 实现如下所示:

public List<Client> extractData(ResultSet rs) throws SQLException,
    DataAccessException 
        Map<Integer, Client> clientsMap = new LinkedHashMap<Integer, Client>();
        while (rs.next()) 
            int id = rs.getInt("id");
            // add the client to the map only the first time
            if (!clientsMap.containsKey(id)) 
                Client client = new Client();
                client.setId(id);
                ...
                clientsMap.put(id, client);
            
            // always add the account type to the existing client
            Client client = clientsMap.get(id);
            client.addAccountType(extractAccountTypeFrom(rs, id));
        
        return new ArrayList<Client>(clientsMap.values());

不用分页也能正常工作。

但是,我需要对这些结果进行分页。我通常这样做的方式是将其添加到查询中,例如:

SELECT ... ORDER BY name ASC LIMIT 10 OFFSET 30;

但是,由于此查询具有联接,当我限制结果数量时,我实际上是在限制 JOINED 结果的数量(即,作为客户将出现的次数与其拥有的帐户类型的数量一样多,LIMIT应用的不是客户数量,而是客户*accountTypes的数量,这不是我想要的)。

我想出的唯一解决方案是从查询中删除 LIMIT(和 OFFSET,因为这也是错误的)并以编程方式应用它们:

List<Client> allClients = jdbcTemplate.query....
List<Client> result = allClients.subList(offset, offset+limit);

但这显然不是一个非常好的、有效的解决方案。有没有更好的办法?

【问题讨论】:

Renato,如何在三表连接上创建一个 VIEW 然后使用 ORDER BY name ASC LIMIT 10 OFFSET 30;从 VIEW_NAME 中选择 * 的技术?由您的 DBA 运行,看看他们是否愿意允许这样做。 @Rob Kielty - 感谢您的建议...我会尝试看看是否可能比我刚刚发布的解决方案更有效。 测试是要走的路。如果您有维护数据库的 DBA,他们应该很高兴您向他们询问每种方法的优缺点。 【参考方案1】:

有趣的是,写一个问题会让你思考,实际上对于为你自己的问题想出一个解决方案很有帮助。

我能够通过简单地将查询的分页部分添加到我的主查询的子查询而不是主查询本身来解决这个问题。

例如,而不是做:

SELECT client.id, client.name ...
FROM clients AS client
LEFT JOIN client_account_types AS cat ON client.id = cat.client_id
FULL JOIN account_types AS at ON cat.account_type_id = at.id
ORDER BY client.name ASC
LIMIT 10 OFFSET 30;

我正在这样做:

SELECT client.id, client.name ...
FROM (
    SELECT * FROM clients
    ORDER BY name ASC
    LIMIT 10 OFFSET 0
) AS client
LEFT JOIN client_account_types AS cat ON client.id = cat.client_id
FULL JOIN account_types AS at ON cat.account_type_id = at.id;

希望这对其他人也有帮助。

【讨论】:

我正在努力解决同样的问题,但无法弄清楚这个查询是如何解决问题的。如果客户端要求 pageSize 为 10,那么它期望它获得 10 个客户端,其中每个客户端有 N 个 account_types 等。因此,您最多可以实现 10 行或少于 10 行(如果任何客户有多个 account_type),您最终将返回少于 10 行。【参考方案2】:

如果您的 DBMS 支持,请使用窗口函数 DENSE_RANK。例如:

SELECT * FROM 
  (SELECT *, DENSE_RANK() OVER (ORDER BY name, id) count FROM 
     (SELECT a.id, a.name, b.title, DENSE_RANK() OVER (ORDER BY a.name, a.id) offset_ 
      FROM AUTHOR a, BOOK b 
      WHERE a.id = b.authorId) result_offset 
   WHERE offset_ > 30) result_offset_count 
WHERE count <= 10

【讨论】:

以上是关于如何对带有联接的 SQL 查询的结果应用分页?的主要内容,如果未能解决你的问题,请参考以下文章

如何编写带有联接和聚合的 SQLAlchemy 查询?

oracle数据库对查询后的结果分页如何实现和jsp分页

asp页面如何分页显示动态查询的结果?

如何将 Sqlalchemy ORM 查询结果转换为包含关系的单个联接表?

带有内部联接的 SQL 更新查询语法

如何在 SQL Server 的交叉应用联接中指定列名