postgresql 中的两个嵌套连接和数组连接

Posted

技术标签:

【中文标题】postgresql 中的两个嵌套连接和数组连接【英文标题】:postgresql two nested joins and arrays in join 【发布时间】:2014-05-31 22:12:02 【问题描述】:

我以前从来没有问过这样的问题,我最终总是通过搜索偶然发现我正在寻找的东西,但是这里发生了很多事情,我已经到了搜索能力的尽头。 . 感谢您的帮助/建议。

问题

我需要即时生成报告,说明我的组织中有多少“学生”参加了每个“评估”,以及与每个“评估”相关的标准。

提前计算学生总数并存储在评估报告中 报告行需要与“评估”连接才能显示信息 标准有子标准,但评估只知道子标准。 评估有多个子标准,并将它们存储在一个 id 数组中。

我正在尝试以最高效的方式来最小化 Postgres 的负载,但我不相信我已经实现了这个目标......

设置

服务器:运行 Ubuntu 的 Amazon EC2

应用(服务器): Node.js (0.10.26)

应用程序(客户端): Angular.js (1.2.13)

数据库(查询): PostgreSQL (9.3.1)

数据库(缓存): MongoDB (希望以后缓存报告)

桌子

SQL FIDDLE

评估

_id                int4
description        text
category           varchar(60)
sub_standards      int4 ARRAY
...

assesments_report(预先计算的总和)

assessment_id(FK)  int4
client_id(FK)      int4
students           int4
completed          int4
incomplete         int4
...

子标准

_id
standard_id(FK)    int4
description        varchar(255)
...

标准

_id                int4
name               varchar(60)
description        varchar(255)
...    

查询

//Stored as array for readability in Node-Postgres use
SELECT r.*, as.* FROM assessments_report r
INNER JOIN (
  SELECT a._id AS assessment_id, a.description, a.category, a.states,
  array_to_json(array_agg(ss.*)) AS standards
  FROM assessments a LEFT JOIN (
    SELECT ss.*, s.name AS parent_name, s.description AS parent_description
    FROM sub_standards ss
    INNER JOIN standards s ON ss.standard_id = s._id
  ) ss ON ss._id = ANY (a.sub_standards) GROUP BY a._id
) as
ON as.assessment_id = r.assessment_id
WHERE r.client_id = $1

每行所需的输出(显示为 JSON)

assessment_id: 2,
students: 2,
complete: 1
incomplete: 1,
description: "...",
category: "...",
states: ["AL","AZ",...],
standards: [
  
    _id: 1,
    standard_id: 3,
    description: "...",
    parent_name: "...",
    parent_description: "..."
  ,
  
    _id: 2,
    standard_id: 4,
    description: "...",
    parent_name: "...",
    parent_description: "..."
  ,
]

SQL FIDDLE

【问题讨论】:

为了有效地帮助优化查询,可用模式 + 示例数据(理想情况下是 sqlfiddle.com)将是非常可取的。您还应该为当前查询显示explain analyze 输出。 @CraigRinger 感谢您的来电!我创建了the fiddle 并将其添加到问题中。在其中,我更具体地了解了我的设置和未来的改进/更改,如果这有帮助,请告诉我。 【参考方案1】:

EXPLAIN 的查询产生的最大成本为 537。您可以通过创建索引将其降低到 59(提高 10 倍):

CREATE INDEX idx_sstandard_sid ON sub_standards (standard_id);

但这更快,成本为 21:

WITH r AS (SELECT * FROM assessments_report WHERE client_id = $1)
, ss AS (SELECT r.*, a.description, a.category, a.states , a.sub_standards
    FROM r INNER JOIN assessments a ON a._id = r.assessment_id)
, ss3 AS (SELECT ss2.*, s.name parent_name, s.description parent_description
            FROM sub_standards ss2
            INNER JOIN standards s
            ON ss2.standard_id = s._id)
, ss1 AS (SELECT ss.*, array_to_json(array_agg(ss3.*)) standards
    FROM ss
    LEFT JOIN ss3
    ON ss3._id = ANY (ss.sub_standards)
    GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14 ) 
SELECT _id, client_id, region_id, group_id, assessment_id, students, basic, 
    proficient, advanced, in_progress, description, category, states, standards
FROM ss1;

您可能会摆脱分组并比这更快。

基本思想是从最有限的约束 (client_id) 开始,通过下一个标准 (sub_standards) 对其进行扩展,之后不再扩展记录集。针对主键的查找比复杂的连接要快。 希望这会有所帮助。

最终查询计划:

CTE Scan on ss1  (cost=21.79..21.81 rows=1 width=434)
CTE r
->  Seq Scan on assessments_report  (cost=0.00..1.06 rows=1 width=40)
      Filter: (client_id = 4)
CTE ss
->  Nested Loop  (cost=0.14..8.19 rows=1 width=434)
      ->  CTE Scan on r  (cost=0.00..0.02 rows=1 width=40)
      ->  Index Scan using assesments_pkey on assessments a  (cost=0.14..8.16 rows=1 width=398)
            Index Cond: (_id = r.assessment_id)
CTE ss3
->  Hash Join  (cost=1.07..12.34 rows=3 width=1346)
      Hash Cond: (s._id = ss2.standard_id)
      ->  Seq Scan on standards s  (cost=0.00..10.90 rows=90 width=818)
      ->  Hash  (cost=1.03..1.03 rows=3 width=532)
            ->  Seq Scan on sub_standards ss2  (cost=0.00..1.03 rows=3 width=532)
CTE ss1
->  HashAggregate  (cost=0.19..0.20 rows=1 width=462)
      ->  Nested Loop Left Join  (cost=0.00..0.15 rows=1 width=462)
            Join Filter: (ss3._id = ANY (ss.sub_standards))
            ->  CTE Scan on ss  (cost=0.00..0.02 rows=1 width=434)
            ->  CTE Scan on ss3  (cost=0.00..0.06 rows=3 width=32)

【讨论】:

感谢您的帮助!自从我最初提出这个问题以来,查询已经发生了一些变化,但我会尽快评估您的解决方案,并在确认我的预期输出后接受答案。还有我没有在我的 SQL Fiddle 上创建索引的错误,我的本地数据库中确实有该索引。

以上是关于postgresql 中的两个嵌套连接和数组连接的主要内容,如果未能解决你的问题,请参考以下文章

如何创建一个结构连接行作为嵌套文档 PostgreSQL

如何从 postgresql 10.3 中的这个多重连接查询中删除嵌套循环

TransactionScope:具有不同数据库连接的嵌套事务(SQL Server 和 Postgresql)

为大型 Postgresql 表优化嵌套连接窗口函数

将嵌套的 JSON 从 PostgreSQL 的多对多连接表返回到 node.js

连接两个单维列表以获得一个多维列表