วิธีการกรองผลลัพธ์ SQL ในความสัมพันธ์แบบหลายผ่าน


101

สมมติว่าผมมีตารางstudent, clubและstudent_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

คำตอบ:


146

ฉันอยากรู้อยากเห็น และอย่างที่เราทราบกันดีว่าความอยากรู้อยากเห็นมีชื่อเสียงในการฆ่าแมว

ดังนั้นวิธีใดเป็นวิธีที่เร็วที่สุดในการถลกหนังแมว?

สภาพแวดล้อมที่แม่นยำสำหรับการทดสอบนี้:

  • PostgreSQL 9.0บน Debian Squeeze พร้อม RAM และการตั้งค่าที่เหมาะสม
  • นักเรียน 6.000 คนสมาชิกชมรม 24.000 คน (ข้อมูลคัดลอกจากฐานข้อมูลที่คล้ายกันกับข้อมูลชีวิตจริง)
  • ความแตกต่างเล็กน้อยจากสคีมาการตั้งชื่อในคำถาม: student.idis student.stud_idand club.idis club.club_idhere
  • ฉันตั้งชื่อการสืบค้นตามผู้เขียนในชุดข้อความนี้โดยมีดัชนีที่มีสองรายการ
  • ฉันเรียกใช้แบบสอบถามทั้งหมดสองสามครั้งเพื่อเติมข้อมูลแคชจากนั้นฉันเลือกสิ่งที่ดีที่สุดจาก 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:

ดัชนี B-tree แบบหลายคอลัมน์สามารถใช้กับเงื่อนไขการสืบค้นที่เกี่ยวข้องกับส่วนย่อยของคอลัมน์ของดัชนี แต่ดัชนีจะมีประสิทธิภาพสูงสุดเมื่อมีข้อ จำกัด ในคอลัมน์นำหน้า (ซ้ายสุด)

ผล:

เวลาทำงานทั้งหมดจาก EXPLAIN ANALYZE

1) Martin 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) Martin 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) Derek: 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) ypercube 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) ypercube 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 แผนการสืบค้นที่แปลกใหม่มาก
อีกครั้งจะน่าสนใจว่า 9.1 จัดการสิ่งนี้อย่างไร ฉันจะอัพเกรดคลัสเตอร์ db ที่ใช้ที่นี่เป็น 9.1 เร็ว ๆ นี้ บางทีฉันอาจจะเรียกใช้ Shebang อีกครั้ง ...


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

แบบสอบถามตัวแปร CTE 2) น่าแปลกที่อาจส่งผลให้แผนการสืบค้นข้อมูลแตกต่างกันเล็กน้อยโดยมีข้อมูลเดียวกัน ฉันพบการสแกนตามลำดับstudentโดยที่ตัวแปรย่อยใช้ดัชนี


11) ypercube 3: 101.482 มิลลิวินาที

อีกอย่างตอนปลาย @ypercube เป็นเรื่องที่น่าอัศจรรย์มากว่ามีกี่วิธี

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's 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
   )

จำนวนสมาชิกแบบไดนามิกของสโมสร

กล่าวอีกนัยหนึ่ง: จำนวนตัวกรองที่แตกต่างกัน คำถามนี้ถามสำหรับสมาชิกคลับสองคน แต่กรณีการใช้งานจำนวนมากต้องเตรียมสำหรับจำนวนที่แตกต่างกัน

การอภิปรายโดยละเอียดในคำตอบในภายหลังที่เกี่ยวข้อง:


1
Brandstetter ทำงานได้ดีมาก ฉันเริ่มให้ความสำคัญกับคำถามนี้เพื่อให้เครดิตเพิ่มเติมแก่คุณ (แต่ฉันต้องรอ 24 ชั่วโมง) อย่างไรก็ตามฉันสงสัยว่าข้อความค้นหาเหล่านี้เกิดขึ้นได้อย่างไรเมื่อคุณเริ่มเพิ่ม club_id หลายรายการแทนที่จะเป็นเพียงสอง ...
Xeoncross

@Xeoncross: ขอชื่นชมกับท่าทางใจกว้างของคุณ :) ด้วย club_ids ที่มากขึ้นฉันสงสัยว่า 1) และ 2) จะเข้าใกล้ความเร็วมากขึ้น แต่มันจะต้องมีจำนวนมากกว่านี้ในการโค่นอันดับ
Erwin Brandstetter

หากคุณมีไม้กอล์ฟมากกว่าสองอันให้สร้างโต๊ะอื่นที่มีไม้กอล์ฟเหล่านั้น จากนั้นเข้าร่วมในตารางนั้นในการเลือกของคุณ
Paul Morgan

@ Erwin: Thnx (สำหรับเกณฑ์มาตรฐาน) ไม่ใช่ nitpicking แต่บางทีคุณอาจลองใช้คำค้นหาเหล่านั้น (ฉันหมายถึงทั้งหมดไม่ใช่แค่ของฉัน) ด้วย(student_id, club_id)ดัชนี (หรือย้อนกลับ)
ypercubeᵀᴹ

3
ฉันคิดผิดหรือเปล่าที่คิดว่าสิ่งใดก็ตามที่ต่ำกว่า 200 มิลลิวินาทีเป็นประสิทธิภาพที่ยอมรับได้เนื่องจากโดเมนที่เป็นปัญหาและขนาดตัวอย่าง เพื่อความสนใจส่วนตัวฉันทำการทดสอบของตัวเองบน SQL Server 2008 R2 โดยใช้ดัชนีโครงสร้างเดียวกันและ (ฉันคิดว่า) การแพร่กระจายของข้อมูล แต่ปรับขนาดไปยังนักเรียนหนึ่งล้านคน (ฉันรู้สึกว่าชุดใหญ่พอสมควรสำหรับโดเมนที่กำหนด) และยังไม่มี ไม่มากที่จะแยกวิธีการต่างๆ IMO แน่นอนว่าสิ่งที่อยู่บนพื้นฐานของการหารเชิงสัมพันธ์สามารถกำหนดเป้าหมายไปที่ตารางฐานทำให้พวกเขาได้รับประโยชน์จาก 'ความสามารถในการขยายตัว'
onedaywhen

18
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

10
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 ตรวจสอบดัชนีจำนวนมาก * จำนวนคลับ
Xeoncross

6
ฉันชอบคำค้นหานี้มากที่สุดเพราะคล้ายกับสไตล์ที่สะอาดตาเหมือน python ใน sql ฉันจะแลกเปลี่ยน 0.44ms อย่างมีความสุข (แตกต่างกับข้อความค้นหาของฌอน) สำหรับรหัสประเภทนี้
MGP

5

หากคุณต้องการเพียง 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 ในความคิดเห็นด้านบน ฉันจะลงคะแนนให้กับส่วนนั้นโดยไม่ต้องมีสิ่งเจือปน
Erwin Brandstetter

ขอบคุณสำหรับความคิดเห็น แต่ฉันกำลังสาธิตการจัดรูปแบบด้วย ฉันจะปล่อยให้มัน 'ตามที่เป็นอยู่'
Paul Morgan

4
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(เนื่องจากประสิทธิภาพของสิ่งนี้แย่ใน 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
Eric L.

4

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

สำหรับผู้ที่ต้องการทดสอบสำเนาของการสร้างข้อมูลทดสอบของฉัน:

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 like ในเวอร์ชันแรกของฉัน แผนการสืบค้นเดียวกัน + ค่าโสหุ้ย CTE ส่งผลให้ประสิทธิภาพเดียวกัน + บิตสำหรับ CTE การตั้งค่าการทดสอบที่ดีแม้ว่า
Erwin Brandstetter

ฉันไม่รู้ว่ามีค่าใช้จ่าย CTE การกระจายข้อมูลทดสอบมีความสำคัญมาก ความพร้อมใช้งานของสถิติก็เช่นกัน: หลังจาก VACUUM ANALYZE เวลาทำงานก็เปลี่ยนจาก 67.4 เป็น 1.56 ms เฉพาะแฮชและบิตแมปที่เกี่ยวข้องใน QP
wildplasser

นี่เป็นกรณีพิเศษของคุณหลังจากลบ 80% ของตารางขนาดใหญ่และอัปเดตจำนวนมากคุณมีสิ่งที่ไม่พึงประสงค์มากกว่าสิ่งอื่นใด ไม่น่าแปลกใจที่การวิเคราะห์สูญญากาศช่วยได้มาก ฉันใช้ทั้งสองตัวแปรที่มีและไม่มี CTE และน่าแปลกใจที่แผนการสืบค้นไม่เหมือนกัน หรือดีกว่านั้นฉันจะเปิดห้องสนทนาสำหรับสิ่งนั้น
Erwin Brandstetter

ไม่ต้องกังวลฉันรู้เกี่ยวกับแถวตาย 80% แล้ว ... ฉันคิดว่าสถิติก็สำคัญเช่นกัน แต่ฮิสโตแกรมค่อนข้าง 'แบน' ซึ่งได้รับการลบแบบสุ่ม อาจเป็นเพียงค่าประมาณของหน้าที่จำเป็นที่เปลี่ยนแปลงเพียงพอที่จะทำให้ผู้วางแผนตัดสินใจเปลี่ยนแผน
wildplasser

3

ดังนั้นจึงมีมากกว่าหนึ่งวิธีเพื่อผิว cat
ฉันจะเพิ่มอีกสองอย่างเพื่อให้ดีและสมบูรณ์ยิ่งขึ้น

1) GROUP ก่อนเข้าร่วมในภายหลัง

สมมติว่ารูปแบบข้อมูลที่มีสติ(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);

3

เนื่องจากไม่มีใครเพิ่มเวอร์ชัน (คลาสสิก) นี้:

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: แอตทริบิวต์หลายรายการในตาราง EAV: GROUP BY เทียบกับ 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 .. สิ่งที่ดีเพิ่มเติมสำหรับคอลเลกชันหนังแมวที่ยังไม่สมบูรณ์! :) ฉันเพิ่มไว้ในเกณฑ์มาตรฐาน
Erwin Brandstetter

มันไม่ใช่การต่อสู้ที่ยุติธรรม :) ข้อได้เปรียบที่สำคัญของการหารเชิงสัมพันธ์เช่นนี้คือตัวหารสามารถเป็นตารางฐานเพื่อให้การเปลี่ยนตัวหารมีราคาถูกมากคือความคมชัดในการอัปเดตแถวในตารางฐานที่กำหนดเป้าหมายโดยแบบสอบถามเดียวกันกับการเปลี่ยน SQL สอบถามทุกครั้ง
onedaywhen

@ErwinBrandstetter: เป็นไปได้ไหมที่จะเพิ่มรูปแบบที่ 3 ในการทดสอบของคุณ
ypercubeᵀᴹ

@ypercube: คุณเข้าใจแล้ว รุ่นบิดสวย :)
Erwin Brandstetter

1
@ เออร์วิน: เมื่อคุณเสียเวลาไปกับเรื่องนี้คุณสามารถลองใช้คีย์ที่ไม่ซ้ำกันสองอันบนทั้งสอง(stud_id, club_id)และ(club_id, stud_id)(หรือหลักและไม่ซ้ำกัน) ได้หรือไม่? ฉันยังคงคิดว่าสำหรับข้อความค้นหาเหล่านั้นความแตกต่างระหว่าง 2 ถึง 140 มิลลิวินาทีนั้นสูงเกินกว่าที่จะอธิบายได้จากความแตกต่างในแผนการดำเนินการ
ypercubeᵀᴹ

2
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 หลีกเลี่ยงความจำเป็นในการค้นหาย่อยสองรายการแยกกัน

มีเหตุผลเสมอที่จะใช้การสืบค้นซ้ำในทางที่ผิด!

(BTW: mysql ดูเหมือนจะไม่มีการสืบค้นซ้ำ)


+1 สำหรับการค้นหาวิธีที่ดีอีกครึ่งทาง! ฉันเพิ่มคำถามของคุณลงในเกณฑ์มาตรฐานแล้ว หวังว่าจะโอเคกับคุณ :)
Erwin Brandstetter

ไม่เป็นไร. แต่ตั้งใจเป็นเรื่องตลกแน่นอน CTE ทำงานได้ดีจริง ๆ หากมีการเพิ่มประวัติสโมสรนักเรียนที่ 'หลงทาง' มากขึ้น (สำหรับการทดสอบฉันใช้นักเรียน 1,000 คน * 100 คลับและลบ 80% แบบสุ่ม)
wildplasser

1

แผนการสืบค้นที่แตกต่างกันในแบบสอบถาม 2) และ 10)

ฉันทดสอบในฐานข้อมูลชีวิตจริงดังนั้นชื่อจึงแตกต่างจากรายการ catkin เป็นสำเนาสำรองจึงไม่มีอะไรเปลี่ยนแปลงระหว่างการทดสอบทั้งหมด (ยกเว้นการเปลี่ยนแปลงเล็กน้อยในแคตตาล็อก)

แบบสอบถาม 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 ห้องแชทเทอะทะฉันจึงตอบผิดที่นี่
Erwin Brandstetter

ฉากแปลก ๆ โดยทั่วไป QP เดียวกันที่นี่ (9.0.1-beta-something) สำหรับ CTE: seq scan + bitmap แทนการสแกนดัชนี + ผสาน อาจเป็นข้อบกพร่องในการวิเคราะห์ต้นทุนของเครื่องมือเพิ่มประสิทธิภาพ? ฉันจะผลิต CTE ที่ไม่เหมาะสมอีก ...
wildplasser

1

@ 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;

มันเหมือนเลข 6) โดย @sean แค่สะอาดกว่าฉันเดา


2
คุณต้องรู้ว่า - การ@แจ้งเตือนใช้ได้เฉพาะในความคิดเห็นเท่านั้นไม่ใช่ในคำตอบ ฉันสะดุดกับกระทู้นี้โดยบังเอิญ แผนการสืบค้นข้อมูลและประสิทธิภาพของแบบสอบถามของคุณเหมือนกับการสืบค้นของฌอน มันเหมือนกันอย่างมีประสิทธิภาพ แต่การสืบค้นของ Sean ที่มีJOINไวยากรณ์ที่ชัดเจนเป็นรูปแบบที่ต้องการโดยทั่วไปเนื่องจากมีความชัดเจนกว่า +1 สำหรับอีกคำตอบที่ถูกต้องแม้ว่า!
Erwin Brandstetter

0
-- 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 หรือไม่
Erwin Brandstetter

0
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
;

การใช้ตัวแปรที่เร็วที่สุด (Mr. Sean ในแผนภูมิ Mr. Brandstetter) อาจเป็นตัวแปรที่มีเพียงการเข้าร่วมเดียวกับเมทริกซ์ student_club เท่านั้นที่มีสิทธิ์อยู่ ดังนั้นแบบสอบถามที่ยาวที่สุดจะมีเพียงสองคอลัมน์ในการคำนวณแนวคิดคือทำให้แบบสอบถามบางลง


1
แม้ว่าข้อมูลโค้ดนี้จะช่วยแก้ปัญหาได้ แต่คำอธิบายจะช่วยปรับปรุงคุณภาพของโพสต์ของคุณได้มาก จำไว้ว่าคุณกำลังตอบคำถามสำหรับผู้อ่านในอนาคตไม่ใช่แค่คนที่ถามตอนนี้! โปรดแก้ไขคำตอบของคุณเพื่อเพิ่มคำอธิบายและระบุข้อ จำกัด และสมมติฐานที่ใช้
BrokenBinary
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.