2012-02-07 9 views
6

Tôi có một câu hỏi đơn giản về cách hiệu quả nhất để thực hiện một phép nối cụ thể.Tham gia giữa bảng ánh xạ (giao lộ) với cardinality cụ thể

Thực hiện các ba bảng, tên thật đã được thay đổi để bảo vệ sự vô tội:

Bảng: động vật

 
animal_id name ... 
====================== 
1   bunny 
2   bear 
3   cat 
4   mouse 

Bảng: thẻ

 
tag_id  tag 
================== 
1   fluffy 
2   brown 
3   cute 
4   small 

Mapping Bảng: animal_tag

 
animal_id tag_id 
================== 
1   1 
1   2 
1   3 
2   2 
3   4 
4   2 

Tôi muốn tìm tất cả các động vật được gắn thẻ là 'fluffy', 'màu nâu' và 'dễ thương'. Đó là để nói rằng động vật phải được gắn thẻ với tất cả ba. Trong thực tế, số lượng thẻ bắt buộc có thể thay đổi, nhưng không liên quan đến cuộc thảo luận này. Đây là truy vấn tôi đã đưa ra:

SELECT * FROM animal 
JOIN (
     SELECT at.animal_id FROM animal_tag at 
     WHERE at.tag_id IN (
          SELECT tg.tag_id FROM tag tg 
          WHERE tg.tag='fluffy' OR tg.tag='brown' OR tg.tag='cute' 
         ) 
     GROUP BY at.animal_id HAVING COUNT(at.tag_id)=3 
    ) AS jt 
ON animal.animal_id=jt.animal_id 

Trên bàn có hàng nghìn động vật và hàng trăm 'thẻ', truy vấn này thực hiện một cách đáng kính ... 10s của mili giây. Tuy nhiên, khi tôi nhìn vào kế hoạch truy vấn (Apache Derby là DB), chi phí ước tính của trình tối ưu hóa là khá cao (9945.12) và kế hoạch khá rộng rãi. Đối với một truy vấn này "đơn giản" tôi thường cố gắng để có được kế hoạch truy vấn với chi phí ước tính của một hoặc hai chữ số.

Vì vậy, câu hỏi của tôi là, có cách nào tốt hơn để thực hiện truy vấn này không? Có vẻ như một truy vấn đơn giản, nhưng tôi đã bị choáng ngợp với bất cứ điều gì tốt hơn.

+0

tôi nghĩ bạn nên sử dụng 'AND' thay vì' OR' trong 'WHERE tg.tag = 'fluffy' HOẶC tg.tag = 'brown' HOẶC tg.tag = 'cute'' –

+0

@johntotetwoo Không có hàng _single_ nào trong 'thẻ' khớp với nhiều hơn một giá trị duy nhất, vì vậy việc sử dụng AND sẽ không tạo ra các hàng phù hợp. –

+0

@BrankoDimitrijevic bạn nói đúng! lỗi của tôi. tôi đang nghĩ gì –

Trả lời

1

Trước hết, một lời cảm ơn rất lớn đến tất cả những người đã tham gia. Cuối cùng câu trả lời là, như được tham chiếu bởi nhiều người bình luận, phân chia quan hệ.

Trong khi tôi đã tham gia một khóa học về mô hình dữ liệu quan hệ của Codd nhiều mặt trăng trước đây, khóa học giống như nhiều, không thực sự bao gồm sự phân chia quan hệ. Vô tình, truy vấn ban đầu của tôi thực sự là một ứng dụng của bộ phận quan hệ.

Đề cập đến trang trình bày 26-27 trong this presentation về phân chia quan hệ, truy vấn của tôi áp dụng kỹ thuật so sánh tập hợp các yếu tố. Tôi đã thử một số phương pháp khác được đề cập để áp dụng bộ phận quan hệ nhưng, ít nhất là trong trường hợp của tôi, phương pháp đếm cung cấp thời gian chạy nhanh nhất. Tôi khuyến khích bất cứ ai quan tâm đến vấn đề này để đọc các slide nói trên, cũng như các bài viết tham chiếu trên trang này của Mikael Eriksson. Một lần nữa, cảm ơn tất cả mọi người.

1

Bạn có thể tạo bảng tạm thời bằng cách sử dụng DECLARE GLOBAL TEMPORARY TABLE Và sau đó thực hiện INNER JOIN để loại bỏ "WHERE IN". Làm việc với các tham gia được thiết lập dựa trên thường hiệu quả hơn nhiều so với các câu lệnh phải được đánh giá cho mỗi hàng.

+2

trong thực tế truy vấn bên trong WHERE IN được tối ưu hóa bởi cơ sở dữ liệu sao cho nó chỉ chạy một lần, vì nó không phụ thuộc vào truy vấn bên ngoài. Ngoài ra vì nó chỉ trả về (trong trường hợp này là 3 hàng hoặc một số nhỏ trong thực tế), chi phí tạo và chọn vào một bảng tạm thời lớn hơn chi phí truy vấn ban đầu. – brettw

1

thử điều này:

SELECT DISTINCT f.Animal_ID, g.Name 
FROM Animal f INNER JOIN 
    (SELECT a.Animal_ID, a.Name, COUNT(*) as iCount 
    FROM Animal a INNER JOIN Animal_Tag b 
        ON a.Animal_ID = b.animal_ID 
        INNER JOIN Tags c 
        On b.tag_ID = c.tag_ID 
    WHERE c.tag IN ('fluffy', 'brown', 'cute') -- list all tags here 
    GROUP BY a.Animal_ID) g 
WHERE g.iCount = 3 -- No. of tags 

CẬP NHẬT

SELECT DISTINCT a.Animal_ID, a.Name, COUNT(*) as iCount 
    FROM Animal a INNER JOIN Animal_Tag b 
        ON a.Animal_ID = b.animal_ID 
        INNER JOIN Tags c 
        On b.tag_ID = c.tag_ID 
    WHERE c.tag IN ('fluffy', 'brown', 'cute') -- list all tags here 
    GROUP BY Animal_ID 
    HAVING iCount = 3 -- No. of tags 
+0

Cảm ơn, tôi đánh giá cao nỗ lực này. Truy vấn này là chính xác ở chỗ nó tạo ra kết quả tương tự như truy vấn của tôi. Thật không may, khi cắm nó vào mã của chúng tôi nó có một chi phí ước tính cao hơn một chút và thời gian chạy lâu hơn một chút (truy vấn của chúng tôi là 0,28s, của bạn là 0,32s). Về cơ bản tương đương về hiệu suất (ít nhất là với tập dữ liệu của chúng tôi). Cảm ơn một lần nữa. – brettw

+0

@brettw i đã cập nhật truy vấn đó. nó có làm giảm chi phí ước tính đó không? –

+0

@johntotewoo Tôi không biết tại sao, nhưng Derby không thích truy vấn đó. Lỗi: Tham chiếu cột 'A.NAME' không hợp lệ hoặc là một phần của biểu thức không hợp lệ. Đối với danh sách SELECT có GROUP BY, các cột và biểu thức được chọn chỉ có thể chứa các biểu thức nhóm hợp lệ và các biểu thức tổng hợp hợp lệ. – brettw

1

này cung cấp cho một spin:

SELECT a.* 
FROM animal a 
INNER JOIN 
    ( 
    SELECT at.animal_id 
    FROM tag t 
    INNER JOIN animal_tag at ON at.tag_id = t.tag_id 
    WHERE tag IN ('fluffy', 'brown', 'cute') 
    GROUP BY at.animal_id 
    HAVING count(*) = 3 
) f ON a.animal_id = f.animal_id 

Đây là một lựa chọn khác, chỉ dành riêng cho những niềm vui của nó:

SELECT a.* 
FROM animal a 
INNER JOIN animal_tag at1 on at1.animal_id = a.animal_id 
INNER JOIN tag t1 on t1.tag_id = at1.tag_id 
INNER JOIN animal_tag at2 on at2.animal_id = a.animal_id 
INNER JOIN tag t2 on t2.tag_id = at2.tag_id 
INNER JOIN animal_tag at3 on at3.animal_id = a.animal_id 
INNER JOIN tag t3 on t3.tag_id = at3.tag_id 
WHERE t1.tag = 'fluffy' AND t2.tag = 'brown' AND t3.tag = 'cute' 

Tôi không thực sự mong đợi tùy chọn cuối cùng này làm tốt ... các tùy chọn khác tránh cần phải quay lại bảng thẻ nhiều lần để giải quyết tên thẻ từ id ... nhưng bạn không bao giờ biết trình tối ưu hóa truy vấn sẽ làm cho đến khi bạn thử.

+0

Tuyệt vời. Truy vấn đầu tiên không phải là một tùy chọn với Apache Derby vì nó không hỗ trợ câu lệnh WITH. Nhưng lựa chọn thứ hai là thú vị. Nó đi kèm với một chi phí tối ưu hóa thấp hơn (5966.82) so với bản gốc của tôi, nhưng trong thực tế thời gian chạy là khoảng 10% dài hơn (trung bình trên 10 chạy). – brettw

+0

@brettw - viết lại truy vấn đầu tiên để bỏ qua cte. –

+0

Điều thú vị là truy vấn đầu tiên được sửa đổi của bạn biên dịch chính xác cùng một kế hoạch truy cập như truy vấn của tôi, bao gồm chi phí ước tính chính xác (9945,12). – brettw

0

Tôi đã tự hỏi làm thế nào xấu nó sẽ được sử dụng một bộ phận quan hệ ở đó. Bạn có thể vui lòng cho nó chạy không? Tôi biết điều này sẽ mất nhiều hơn, nhưng tôi bị hấp dẫn bởi bao nhiêu :) Nếu bạn có thể cung cấp cả chi phí ước tính và thời gian, nó sẽ là tuyệt vời.

select a2.animal_id, a2.animal_name from animal2 a2 
where not exists (
    select * from animal1 a1, tags t1 
    where not exists (
     select * from animal_tag at1 
     where at1.animal_id = a1.animal_id and at1.animal_tag = t1.tag_id 
    ) and a2.animal_id = a1.animal_id and t1.tag in ('fluffy', 'brown', 'cute') 
) 

Hiện đang tìm kiếm truy vấn nhanh, tôi không thể nghĩ nhanh hơn john hay của bạn. Trên thực tế, john có thể chậm hơn một chút vì anh ấy thực hiện các hoạt động không cần thiết (xóa riêng biệt và xóa số (*) khỏi chọn):

SELECT a.Animal_ID, a.Name FROM Animal a 
INNER JOIN Animal_Tag b ON a.Animal_ID = b.animal_ID 
INNER JOIN Tags c On b.tag_ID = c.tag_ID 
WHERE c.tag IN ('fluffy', 'brown', 'cute') -- list all tags here 
GROUP BY Animal_ID, a.Name 
HAVING count(*) = 3 -- No. of tags 

Điều này phải nhanh như bạn.

PS: Có cách nào để loại bỏ số damned 3 mà không nhân đôi mệnh đề where? Não của tôi đang sôi :)

+0

CTE's sẽ cho phép bạn xóa sự thừa vì bạn có thể tham khảo CTE hai lần trong truy vấn chính (lần thứ hai sẽ là truy vấn đếm (*) để lấy số). Nhưng Derby không ủng hộ họ. –