【问题标题】:How to filter SQL results in a has-many-through relation如何过滤 SQL 结果在 has-many-through 关系中
【发布时间】:2011-11-13 23:09:10
【问题描述】:

假设我有表 studentclubstudent_club

student {
    id
    name
}
club {
    id
    name
}
student_club {
    student_id
    club_id
}

我想知道如何查找足球 (30) 和棒球 (50) 俱乐部的所有学生。
虽然这个查询不起作用,但它是我迄今为止最接近的东西:

SELECT student.*
FROM   student
INNER  JOIN student_club sc ON student.id = sc.student_id
LEFT   JOIN club c ON c.id = sc.club_id
WHERE  c.id = 30 AND c.id = 50

【问题讨论】:

    标签: mysql sql postgresql sql-match-all relational-division


    【解决方案1】:
    select *
    from student
    where id in (select student_id from student_club where club_id = 30)
    and id in (select student_id from student_club where club_id = 50)
    

    【讨论】:

    • 这个查询工作正常,但是让我不得不让 RDBMS 检查这么多索引 * 俱乐部数量让我感到困扰。
    • 我最喜欢这个查询,因为它像一个干净的样式,它就像 sql 中的 python。我很乐意用 0.44 毫秒(与肖恩的查询不同)来换取这种代码。
    【解决方案2】:
    SELECT *
    FROM   student
    WHERE  id IN (SELECT student_id
                  FROM   student_club
                  WHERE  club_id = 30
                  INTERSECT
                  SELECT student_id
                  FROM   student_club
                  WHERE  club_id = 50)  
    

    或者更通用的解决方案更容易扩展到n 俱乐部,并且避免INTERSECT(在MySQL 中不可用)和IN(如performance of this sucks in MySQL

    SELECT s.id,
           s.name
    FROM   student s
           join student_club sc
             ON s.id = sc.student_id
    WHERE  sc.club_id IN ( 30, 50 )
    GROUP  BY s.id,
              s.name
    HAVING COUNT(DISTINCT sc.club_id) = 2  
    

    【讨论】:

    • 毫无疑问,您的第二个答案最适合由代码生成的查询。我真的要写 10 个连接或子查询来找到 10 个标准的关系划分吗?哎呀,不,我将使用这个出色的解决方案。感谢您教我 HAVING 在 MySQL 中的作用。
    【解决方案3】:
    SELECT s.*
    FROM student s
    INNER JOIN student_club sc_soccer ON s.id = sc_soccer.student_id
    INNER JOIN student_club sc_baseball ON s.id = sc_baseball.student_id
    WHERE 
     sc_baseball.club_id = 50 AND 
     sc_soccer.club_id = 30
    

    【讨论】:

      【解决方案4】:

      因此,给猫剥皮的方法不止一种
      我将添加另外两个以使其更完整。

      1) 先分组,后加入

      假设一个健全的数据模型,其中(student_id, club_id)student_club 中是唯一。 Martin Smith 的第二个版本有点类似,但他先加入,然后再分组。这应该更快:

      SELECT s.id, s.name
        FROM student s
        JOIN (
         SELECT student_id
           FROM student_club
          WHERE club_id IN (30, 50)
          GROUP BY 1
         HAVING COUNT(*) > 1
             ) sc USING (student_id);
      

      2) 存在

      当然,还有经典的EXISTS。类似于 Derek 的变体 IN。简单快速。 (在 MySQL 中,这应该比 IN 的变体快很多):

      SELECT s.id, s.name
        FROM student s
       WHERE EXISTS (SELECT 1 FROM student_club
                     WHERE  student_id = s.student_id AND club_id = 30)
         AND EXISTS (SELECT 1 FROM student_club
                     WHERE  student_id = s.student_id AND club_id = 50);
      

      【讨论】:

        【解决方案5】:

        我很好奇。众所周知,好奇心以杀死猫而闻名。

        那么,给猫剥皮最快的方法是什么?

        本次测试的猫皮环境:

        • PostgreSQL 9.0 在 Debian Squeeze 上,具有不错的 RAM 和设置。
        • 6000 名学生,24000 名俱乐部会员(数据从具有现实生活数据的类似数据库复制而来。)
        • 与问题中的命名模式略有不同:student.idstudent.stud_idclub.idclub.club_id
        • 我在此线程中以作者的名字命名了查询。
        • 我对所有查询运行了几次以填充缓存,然后我用EXPLAIN ANALYZE 选择了 5 个中最好的一个。
        • 相关索引(应该是最优的——只要我们不知道哪些俱乐部会被查询):
        ALTER TABLE student ADD CONSTRAINT student_pkey PRIMARY KEY(stud_id );
        ALTER TABLE student_club ADD CONSTRAINT sc_pkey PRIMARY KEY(stud_id, club_id);
        ALTER TABLE club       ADD CONSTRAINT club_pkey PRIMARY KEY(club_id );
        CREATE INDEX sc_club_id_idx ON student_club (club_id);
        

        这里的大多数查询都不需要club_pkey
        主键在 PostgreSQL 中自动实现唯一索引。
        最后一个索引是为了弥补PostgreSQL上multi-column indexes这个已知的缺点:

        多列 B 树索引可以与以下查询条件一起使用 涉及索引列的任何子集,但索引是最 当前导(最左侧)列有约束时,效率很高。

        结果

        EXPLAIN ANALYZE 的总运行时间。

        1) 马丁 2:44.594 毫秒

        SELECT s.stud_id, s.name
        FROM   student s
        JOIN   student_club sc USING (stud_id)
        WHERE  sc.club_id IN (30, 50)
        GROUP  BY 1,2
        HAVING COUNT(*) > 1;
        

        2) 欧文 1:33.217 毫秒

        SELECT s.stud_id, s.name
        FROM   student s
        JOIN   (
           SELECT stud_id
           FROM   student_club
           WHERE  club_id IN (30, 50)
           GROUP  BY 1
           HAVING COUNT(*) > 1
           ) sc USING (stud_id);
        

        3) 马丁 1:31.735 毫秒

        SELECT s.stud_id, s.name
        FROM   student s
        WHERE  student_id IN (
           SELECT student_id
           FROM   student_club
           WHERE  club_id = 30
        
           INTERSECT
           SELECT stud_id
           FROM   student_club
           WHERE  club_id = 50
           );
        

        4) 德里克:2.287 毫秒

        SELECT s.stud_id,  s.name
        FROM   student s
        WHERE  s.stud_id IN (SELECT stud_id FROM student_club WHERE club_id = 30)
        AND    s.stud_id IN (SELECT stud_id FROM student_club WHERE club_id = 50);
        

        5) 欧文 2:2.181 毫秒

        SELECT s.stud_id,  s.name
        FROM   student s
        WHERE  EXISTS (SELECT * FROM student_club
                       WHERE  stud_id = s.stud_id AND club_id = 30)
        AND    EXISTS (SELECT * FROM student_club
                       WHERE  stud_id = s.stud_id AND club_id = 50);
        

        6) 肖恩:2.043 毫秒

        SELECT s.stud_id, s.name
        FROM   student s
        JOIN   student_club x ON s.stud_id = x.stud_id
        JOIN   student_club y ON s.stud_id = y.stud_id
        WHERE  x.club_id = 30
        AND    y.club_id = 50;
        

        最后三个的表现几乎相同。 4) 和 5) 产生相同的查询计划。

        后期添加

        花哨的 SQL,但性能跟不上:

        7) 超立方体 1:148.649 毫秒

        SELECT s.stud_id,  s.name
        FROM   student AS s
        WHERE  NOT EXISTS (
           SELECT *
           FROM   club AS c 
           WHERE  c.club_id IN (30, 50)
           AND    NOT EXISTS (
              SELECT *
              FROM   student_club AS sc 
              WHERE  sc.stud_id = s.stud_id
              AND    sc.club_id = c.club_id  
              )
           );
        

        8) 超立方体 2:147.497 毫秒

        SELECT s.stud_id,  s.name
        FROM   student AS s
        WHERE  NOT EXISTS (
           SELECT *
           FROM  (
              SELECT 30 AS club_id  
              UNION  ALL
              SELECT 50
              ) AS c
           WHERE NOT EXISTS (
              SELECT *
              FROM   student_club AS sc 
              WHERE  sc.stud_id = s.stud_id
              AND    sc.club_id = c.club_id  
              )
           );
        

        正如预期的那样,这两者的表现几乎相同。查询计划导致表扫描,计划程序在这里找不到使用索引的方法。

        9) Wildplasser 1:49.849 毫秒

        WITH RECURSIVE two AS (
           SELECT 1::int AS level
                , stud_id
           FROM   student_club sc1
           WHERE  sc1.club_id = 30
           UNION
           SELECT two.level + 1 AS level
                , sc2.stud_id
           FROM   student_club sc2
           JOIN   two USING (stud_id)
           WHERE  sc2.club_id = 50
           AND    two.level = 1
           )
        SELECT s.stud_id, s.student
        FROM   student s
        JOIN   two USING (studid)
        WHERE  two.level > 1;
        

        花哨的 SQL,CTE 的出色性能。非常奇特的查询计划。

        10) Wildplasser 2:36.986 毫秒

        WITH sc AS (
           SELECT stud_id
           FROM   student_club
           WHERE  club_id IN (30,50)
           GROUP  BY stud_id
           HAVING COUNT(*) > 1
           )
        SELECT s.*
        FROM   student s
        JOIN   sc USING (stud_id);
        

        查询 2) 的 CTE 变体。令人惊讶的是,它可能会导致使用完全相同的数据的查询计划略有不同。我在student 上发现了顺序扫描,其中子查询变体使用了索引。

        11) 超立方体 3:101.482 毫秒

        另一个后期添加的超立方体。真是太神奇了,有多少种方法。

        SELECT s.stud_id, s.student
        FROM   student s
        JOIN   student_club sc USING (stud_id)
        WHERE  sc.club_id = 10                 -- member in 1st club ...
        AND    NOT EXISTS (
           SELECT *
           FROM  (SELECT 14 AS club_id) AS c  -- can't be excluded for missing the 2nd
           WHERE  NOT EXISTS (
              SELECT *
              FROM   student_club AS d
              WHERE  d.stud_id = sc.stud_id
              AND    d.club_id = c.club_id
              )
           );
        

        12) 欧文 3:2.377 毫秒

        ypercube 的 11) 实际上只是这个更简单的变体的令人费解的反向方法,它仍然缺失。执行速度几乎与顶级猫科动物一样快。

        SELECT s.*
        FROM   student s
        JOIN   student_club x USING (stud_id)
        WHERE  sc.club_id = 10                 -- member in 1st club ...
        AND    EXISTS (                        -- ... and membership in 2nd exists
           SELECT *
           FROM   student_club AS y
           WHERE  y.stud_id = s.stud_id
           AND    y.club_id = 14
           );
        

        13) 欧文 4:2.375 毫秒

        难以置信,但这是另一个真正的新变体。我看到了超过两个成员的潜力,但它也跻身于只有两个成员的顶级猫之列。

        SELECT s.*
        FROM   student AS s
        WHERE  EXISTS (
           SELECT *
           FROM   student_club AS x
           JOIN   student_club AS y USING (stud_id)
           WHERE  x.stud_id = s.stud_id
           AND    x.club_id = 14
           AND    y.club_id = 10
           );
        

        俱乐部会员的动态数量

        换句话说:不同数量的过滤器。这个问题要求恰好 两个 俱乐部会员资格。但是许多用例必须为不同的数量做准备。见:

        【讨论】:

        • Brandstetter,非常好的工作。我在这个问题上开始赏金给你额外的功劳(但我必须等待 24 小时)。无论如何,我想知道当您开始添加多个 club_id 而不是两个时,这些查询是如何进行的......
        • @Xeoncross:感谢您的慷慨举动。 :) 随着更多的 club_ids 我怀疑 1) 和 2) 的速度会更接近,但它必须是一个更大的数字才能推翻排名。
        • 如果您有多个俱乐部,则创建另一个包含这些俱乐部的表格。然后在您的选择中加入该表。
        • @Erwin:Thnx(用于基准测试)。不是吹毛求疵,但也许您可以使用 (student_id, club_id)(或反向)索引尝试这些查询(我的意思是全部,而不仅仅是我的)。
        • 考虑到相关领域和样本量,我认为任何低于 200 毫秒的性能都是可以接受的,我错了吗?出于个人兴趣,我在 SQL Server 2008 R2 上进行了自己的测试,使用相同的结构索引和(我认为)数据传播,但扩展到一百万学生(我觉得对于给定域来说是相当大的集合)并且仍然没有IMO,将不同的方法分开并不多。当然,基于关系划分的可以针对基表,从而具有“可扩展性”的优势。
        【解决方案6】:

        如果你只想要 student_id 那么:

            Select student_id
              from student_club
             where club_id in ( 30, 50 )
          group by student_id
            having count( student_id ) = 2
        

        如果您还需要学生姓名,那么:

        Select student_id, name
          from student s
         where exists( select *
                         from student_club sc
                        where s.student_id = sc.student_id
                          and club_id in ( 30, 50 )
                     group by sc.student_id
                       having count( sc.student_id ) = 2 )
        

        如果您在一个 club_selection 表中有两个以上的俱乐部,那么:

        Select student_id, name
          from student s
         where exists( select *
                         from student_club sc
                        where s.student_id = sc.student_id
                          and exists( select * 
                                        from club_selection cs
                                       where sc.club_id = cs.club_id )
                     group by sc.student_id
                       having count( sc.student_id ) = ( select count( * )
                                                           from club_selection ) )
        

        【讨论】:

        • 前两个包含在 / 中,与我的查询 1 相同。但第三个解决了 @Xeoncross 在上面的 cmets 中添加的问题。我会在没有受骗的情况下投票支持那部分。
        • 感谢您的评论,但我也在演示一些格式。我会保持原样。
        【解决方案7】:

        由于没有人添加这个(经典)版本:

        SELECT s.*
        FROM student AS s
        WHERE NOT EXISTS
              ( SELECT *
                FROM club AS c 
                WHERE c.id IN (30, 50)
                  AND NOT EXISTS
                      ( SELECT *
                        FROM student_club AS sc 
                        WHERE sc.student_id = s.id
                          AND sc.club_id = c.id  
                      )
              )
        

        或类似的:

        SELECT s.*
        FROM student AS s
        WHERE NOT EXISTS
              ( SELECT *
                FROM
                  ( SELECT 30 AS club_id  
                  UNION ALL
                    SELECT 50
                  ) AS c
                WHERE NOT EXISTS
                      ( SELECT *
                        FROM student_club AS sc 
                        WHERE sc.student_id = s.id
                          AND sc.club_id = c.club_id  
                      )
              )
        

        再尝试一种稍微不同的方法。灵感来自Explain Extended: Multiple attributes in a EAV table: GROUP BY vs. NOT EXISTS中的一篇文章:

        SELECT s.*
        FROM student_club AS sc
          JOIN student AS s
            ON s.student_id = sc.student_id
        WHERE sc.club_id = 50                      --- one option here
          AND NOT EXISTS
              ( SELECT *
                FROM
                  ( SELECT 30 AS club_id           --- all the rest in here
                                                   --- as in previous query
                  ) AS c
                WHERE NOT EXISTS
                      ( SELECT *
                        FROM student_club AS scc 
                        WHERE scc.student_id = sc.id
                          AND scc.club_id = c.club_id  
                      )
              )
        

        另一种方法:

        SELECT s.stud_id
        FROM   student s
        
        EXCEPT
        
        SELECT stud_id
        FROM 
          ( SELECT s.stud_id, c.club_id
            FROM student s 
              CROSS JOIN (VALUES (30),(50)) c (club_id)
          EXCEPT
            SELECT stud_id, club_id
            FROM student_club
            WHERE club_id IN (30, 50)   -- optional. Not needed but may affect performance
          ) x ;   
        

        【讨论】:

        • +1 .. 对不太完整的猫皮系列进行了很好的补充! :) 我将它们添加到基准测试中。
        • 这不是一场公平的斗争 :) 像这样的关系除法的最大优势是除数可以是基表,因此更改除数非常便宜,即对比更新目标基表中的行通过相同的查询,每次更改 SQL 查询。
        • @ErwinBrandstetter:是否可以在您的测试中添加第 3 个变体?
        • @ypercube:你明白了。相当扭曲的版本。 :)
        • @Erwin:当您设法在这方面浪费一些时间时,您是否也可以尝试在(stud_id, club_id)(club_id, stud_id)(或主要和唯一)上使用两个唯一键?我仍然认为对于其中一些查询,2 到 140 毫秒的差异太大,无法用执行计划的差异来解释。
        【解决方案8】:
        WITH RECURSIVE two AS
            ( SELECT 1::integer AS level
            , student_id
            FROM tmp.student_club sc0
            WHERE sc0.club_id = 30
            UNION
            SELECT 1+two.level AS level
            , sc1.student_id
            FROM tmp.student_club sc1
            JOIN two ON (two.student_id = sc1.student_id)
            WHERE sc1.club_id = 50
            AND two.level=1
            )
        SELECT st.* FROM tmp.student st
        JOIN two ON (two.student_id=st.id)
        WHERE two.level> 1
        
            ;
        

        这似乎表现得相当不错,因为 CTE 扫描避免了对两个单独的子查询的需要。

        滥用递归查询总是有原因的!

        (顺便说一句:mysql似乎没有递归查询)

        【讨论】:

        • +1 用于找到另一种体面的方法!我将您的查询添加到基准测试中。希望你没问题。 :)
        • 没关系。但这当然是为了开玩笑。如果添加更多“流浪”学生*俱乐部记录,CTE 实际上表现良好。 (为了测试,我使用了 1000 个学生 * 100 个俱乐部,并随机删除了 80%)
        【解决方案9】:

        另一个 CTE。它看起来很干净,但它可能会生成与普通子查询中的 groupby 相同的计划。

        WITH two AS (
            SELECT student_id FROM tmp.student_club
            WHERE club_id IN (30,50)
            GROUP BY student_id
            HAVING COUNT(*) > 1
            )
        SELECT st.* FROM tmp.student st
        JOIN two ON (two.student_id=st.id)
            ;
        

        对于那些想要测试的人,我的 generate testdata 东西的副本:

        DROP SCHEMA tmp CASCADE;
        CREATE SCHEMA tmp;
        
        CREATE TABLE tmp.student
            ( id INTEGER NOT NULL PRIMARY KEY
            , sname VARCHAR
            );
        
        CREATE TABLE tmp.club
            ( id INTEGER NOT NULL PRIMARY KEY
            , cname VARCHAR
            );
        
        CREATE TABLE tmp.student_club
            ( student_id INTEGER NOT NULL  REFERENCES tmp.student(id)
            , club_id INTEGER NOT NULL  REFERENCES tmp.club(id)
            );
        
        INSERT INTO tmp.student(id)
            SELECT generate_series(1,1000)
            ;
        
        INSERT INTO tmp.club(id)
            SELECT generate_series(1,100)
            ;
        
        INSERT INTO tmp.student_club(student_id,club_id)
            SELECT st.id  , cl.id
            FROM tmp.student st, tmp.club cl
            ;
        
        DELETE FROM tmp.student_club
        WHERE random() < 0.8
            ;
        
        UPDATE tmp.student SET sname = 'Student#' || id::text ;
        UPDATE tmp.club SET cname = 'Soccer' WHERE id = 30;
        UPDATE tmp.club SET cname = 'Baseball' WHERE id = 50;
        
        ALTER TABLE tmp.student_club
            ADD PRIMARY KEY (student_id,club_id)
            ;
        

        【讨论】:

        • 是的,这实际上只是一个带有 group by 的子查询,就像我的第一个版本一样。相同的查询计划 + CTE 开销导致相同的性能 + CTE 的一点。不过,不错的测试设置。
        • 我不知道是否存在 CTE 开销。测试数据的分布非常重要。统计数据的可用性也是如此:在 VACUUM ANALYZE 之后,运行时间从 67.4 毫秒变为 1.56 毫秒。 QP 中仅涉及哈希和位图。
        • 这对你来说很特别,在删除了 80% 的大表并更新了很多内容之后,你的死元组比其他任何东西都多。难怪,真空分析有很大帮助。我运行了有和没有 CTE 的两种变体,令人惊讶的是查询计划并不相同。或者更好的是,我会为此打开一个聊天室。
        • 别担心,我知道 80% 的死行...我认为统计数据也很重要。但是直方图相当“平坦”,随机删除。也许只是对所需页面的估计变化足以让规划者决定切换计划。
        【解决方案10】:

        查询 2) 和 10) 中的不同查询计划

        我在现实生活中的数据库中进行了测试,因此名称与猫皮列表不同。这是一个备份副本,因此在所有测试运行期间都没有任何变化(除了对目录的微小更改)。

        查询 2)

        SELECT a.*
        FROM   ef.adr a
        JOIN (
            SELECT adr_id
            FROM   ef.adratt
            WHERE  att_id IN (10,14)
            GROUP  BY adr_id
            HAVING COUNT(*) > 1) t using (adr_id);
        
        Merge Join  (cost=630.10..1248.78 rows=627 width=295) (actual time=13.025..34.726 rows=67 loops=1)
          Merge Cond: (a.adr_id = adratt.adr_id)
          ->  Index Scan using adr_pkey on adr a  (cost=0.00..523.39 rows=5767 width=295) (actual time=0.023..11.308 rows=5356 loops=1)
          ->  Sort  (cost=630.10..636.37 rows=627 width=4) (actual time=12.891..13.004 rows=67 loops=1)
                Sort Key: adratt.adr_id
                Sort Method:  quicksort  Memory: 28kB
                ->  HashAggregate  (cost=450.87..488.49 rows=627 width=4) (actual time=12.386..12.710 rows=67 loops=1)
                      Filter: (count(*) > 1)
                      ->  Bitmap Heap Scan on adratt  (cost=97.66..394.81 rows=2803 width=4) (actual time=0.245..5.958 rows=2811 loops=1)
                            Recheck Cond: (att_id = ANY ('{10,14}'::integer[]))
                            ->  Bitmap Index Scan on adratt_att_id_idx  (cost=0.00..94.86 rows=2803 width=0) (actual time=0.217..0.217 rows=2811 loops=1)
                                  Index Cond: (att_id = ANY ('{10,14}'::integer[]))
        Total runtime: 34.928 ms
        

        查询 10)

        WITH two AS (
            SELECT adr_id
            FROM   ef.adratt
            WHERE  att_id IN (10,14)
            GROUP  BY adr_id
            HAVING COUNT(*) > 1
            )
        SELECT a.*
        FROM   ef.adr a
        JOIN   two using (adr_id);
        
        Hash Join  (cost=1161.52..1261.84 rows=627 width=295) (actual time=36.188..37.269 rows=67 loops=1)
          Hash Cond: (two.adr_id = a.adr_id)
          CTE two
            ->  HashAggregate  (cost=450.87..488.49 rows=627 width=4) (actual time=13.059..13.447 rows=67 loops=1)
                  Filter: (count(*) > 1)
                  ->  Bitmap Heap Scan on adratt  (cost=97.66..394.81 rows=2803 width=4) (actual time=0.252..6.252 rows=2811 loops=1)
                        Recheck Cond: (att_id = ANY ('{10,14}'::integer[]))
                        ->  Bitmap Index Scan on adratt_att_id_idx  (cost=0.00..94.86 rows=2803 width=0) (actual time=0.226..0.226 rows=2811 loops=1)
                              Index Cond: (att_id = ANY ('{10,14}'::integer[]))
          ->  CTE Scan on two  (cost=0.00..50.16 rows=627 width=4) (actual time=13.065..13.677 rows=67 loops=1)
          ->  Hash  (cost=384.68..384.68 rows=5767 width=295) (actual time=23.097..23.097 rows=5767 loops=1)
                Buckets: 1024  Batches: 1  Memory Usage: 1153kB
                ->  Seq Scan on adr a  (cost=0.00..384.68 rows=5767 width=295) (actual time=0.005..10.955 rows=5767 loops=1)
        Total runtime: 37.482 ms
        

        【讨论】:

        • @wildplasser:查看不同的查询计划!出乎我的意料。第 9.0 页。聊天室很笨拙,所以我在这里滥用答案。
        • 奇怪的场景。 CTE 的 QP 基本相同(9.0.1-beta-something):seq scan+bitmap 而不是 index scan+merge。也许优化器的成本启发式存在缺陷?我要制造另一个 CTE 滥用...
        【解决方案11】:
        -- EXPLAIN ANALYZE
        WITH two AS (
            SELECT c0.student_id
            FROM tmp.student_club c0
            , tmp.student_club c1
            WHERE c0.student_id = c1.student_id
            AND c0.club_id = 30
            AND c1.club_id = 50
            )
        SELECT st.* FROM tmp.student st
        JOIN two ON (two.student_id=st.id)
            ;
        

        查询计划:

         Hash Join  (cost=1904.76..1919.09 rows=337 width=15) (actual time=6.937..8.771 rows=324 loops=1)
           Hash Cond: (two.student_id = st.id)
           CTE two
             ->  Hash Join  (cost=849.97..1645.76 rows=337 width=4) (actual time=4.932..6.488 rows=324 loops=1)
                   Hash Cond: (c1.student_id = c0.student_id)
                   ->  Bitmap Heap Scan on student_club c1  (cost=32.76..796.94 rows=1614 width=4) (actual time=0.667..1.835 rows=1646 loops=1)
                         Recheck Cond: (club_id = 50)
                         ->  Bitmap Index Scan on sc_club_id_idx  (cost=0.00..32.36 rows=1614 width=0) (actual time=0.473..0.473 rows=1646 loops=1)                     
                               Index Cond: (club_id = 50)
                   ->  Hash  (cost=797.00..797.00 rows=1617 width=4) (actual time=4.203..4.203 rows=1620 loops=1)
                         Buckets: 1024  Batches: 1  Memory Usage: 57kB
                         ->  Bitmap Heap Scan on student_club c0  (cost=32.79..797.00 rows=1617 width=4) (actual time=0.663..3.596 rows=1620 loops=1)                   
                               Recheck Cond: (club_id = 30)
                               ->  Bitmap Index Scan on sc_club_id_idx  (cost=0.00..32.38 rows=1617 width=0) (actual time=0.469..0.469 rows=1620 loops=1)
                                     Index Cond: (club_id = 30)
           ->  CTE Scan on two  (cost=0.00..6.74 rows=337 width=4) (actual time=4.935..6.591 rows=324 loops=1)
           ->  Hash  (cost=159.00..159.00 rows=8000 width=15) (actual time=1.979..1.979 rows=8000 loops=1)
                 Buckets: 1024  Batches: 1  Memory Usage: 374kB
                 ->  Seq Scan on student st  (cost=0.00..159.00 rows=8000 width=15) (actual time=0.093..0.759 rows=8000 loops=1)
         Total runtime: 8.989 ms
        (20 rows)
        

        所以它似乎仍然想要对学生进行 seq 扫描。

        【讨论】:

        • 迫不及待想看看 9.1 中是否已修复。
        【解决方案12】:

        @erwin-brandstetter 请对此进行基准测试:

        SELECT s.stud_id, s.name
        FROM   student s, student_club x, student_club y
        WHERE  x.club_id = 30
        AND    s.stud_id = x.stud_id
        AND    y.club_id = 50
        AND    s.stud_id = y.stud_id;
        

        这就像 @sean 的第 6 号),我猜是更干净了。

        【讨论】:

        • 您必须知道@-notifying 仅适用于 cmets,不适用于答案。我偶然发现了这篇文章。您的查询的查询计划和性能与 Sean 的查询相同。它实际上是相同的,但 Sean 使用显式 JOIN 语法的查询是通常首选的形式,因为它更清晰。不过,+1 是另一个有效的答案!
        【解决方案13】:
        SELECT s.stud_id, s.name
        FROM   student s,
        (
        select x.stud_id from 
        student_club x 
        JOIN   student_club y ON x.stud_id = y.stud_id
        WHERE  x.club_id = 30
        AND    y.club_id = 50
        ) tmp_tbl
        where tmp_tbl.stud_id = s.stud_id
        ;
        

        使用最快的变体(Brandstetter 先生图表中的 Sean 先生)。可能只有一个加入到只有 student_club 矩阵有生存权的变体。因此,最长的查询将只有两列要计算,想法是使查询变细。

        【讨论】:

        • 虽然此代码 sn-p 可以解决问题,包括解释 really helps 以提高您的帖子质量。请记住,您正在为将来的读者回答问题,而不仅仅是现在提问的人!请edit您的答案添加解释,并说明适用的限制和假设。
        猜你喜欢
        • 1970-01-01
        • 2017-07-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-11-03
        • 2018-01-03
        相关资源
        最近更新 更多