2010-06-12 62 views
17

Với bảng sau đây:truy vấn SQL thông qua một bảng trung gian

Recipes 
| id | name 
| 1 | 'chocolate cream pie' 
| 2 | 'banana cream pie' 
| 3 | 'chocolate banana surprise' 

Ingredients 
| id | name 
| 1 | 'banana' 
| 2 | 'cream' 
| 3 | 'chocolate' 

RecipeIngredients 
| recipe_id | ingredient_id 
|  1  |  2 
|  1  |  3 
|  2  |  1 
|  2  |  2 
|  3  |  1 
|  3  |  3 

Làm thế nào để xây dựng một truy vấn SQL để tìm công thức nấu ăn nơi ingredients.name = 'sô cô la' và ingredients.name = 'kem'?

+2

fyi ... Các recipeIngredients được gọi là một MappingTable ... –

+3

@Garis Suero: Có * rất nhiều * từ đồng nghĩa - xref, tra cứu, ánh xạ, liên kết - có định nghĩa tên chuẩn cho một bảng cung cấp mối quan hệ nhiều-nhiều . –

Trả lời

9

Đây được gọi là bộ phận quan hệ. Một loạt các kỹ thuật được thảo luận here.

Một thay thế chưa được đưa ra là đôi NOT EXISTS

SELECT r.id, r.name 
FROM Recipes r 
WHERE NOT EXISTS (SELECT * FROM Ingredients i 
        WHERE name IN ('chocolate', 'cream') 
        AND NOT EXISTS 
         (SELECT * FROM RecipeIngredients ri 
         WHERE ri.recipe_id = r.id 
         AND ri.ingredient_id = i.id)) 
+0

Martin, cảm ơn bạn đã tham khảo và câu trả lời hoạt động! Có một lỗi nhỏ ở dòng cuối cùng, phải là "AND ri.ingredient_id = i.id))" –

+0

@Bryan, Cheers. Tôi đã sửa nó trên câu trả lời của tôi chỉ để hoàn thành. –

2

Nếu bạn đang tìm kiếm nhiều liên kết thì cách đơn giản nhất để viết truy vấn là sử dụng nhiều điều kiện EXISTS thay vì một đơn thẳng JOIN.

SELECT r.id, r.name 
FROM Recipes r 
WHERE EXISTS 
(
    SELECT 1 
    FROM RecipeIngredients ri 
    INNER JOIN Ingredients i 
     ON i.id = ri.ingredient_id 
    WHERE ri.recipe_id = r.id 
    AND i.name = 'chocolate' 
) 
AND EXISTS 
(
    SELECT 1 
    FROM RecipeIngredients ri 
    INNER JOIN Ingredients i 
     ON i.id = ri.ingredient_id 
    WHERE ri.recipe_id = r.id 
    AND i.name = 'cream' 
) 

Nếu bạn biết chắc chắn rằng các hiệp hội là duy nhất (tức là một công thức duy nhất chỉ có thể có một trường hợp duy nhất của mỗi thành phần), sau đó bạn có thể ăn gian một chút sử dụng một subquery nhóm với một COUNT chức năng và có thể là tốc độ nó lên (hiệu suất sẽ phụ thuộc vào DBMS):

SELECT r.id, r.Name 
FROM Recipes r 
INNER JOIN RecipeIngredients ri 
    ON ri.recipe_id = r.id 
INNER JOIN Ingredients i 
    ON i.id = ri.ingredient_id 
WHERE i.name IN ('chocolate', 'cream') 
GROUP BY r.id, r.Name 
HAVING COUNT(*) = 2 

Hoặc, nếu một công thức có thể có nhiều trường hợp của các thành phần tương tự (không UNIQUE hạn chế trên bàn RecipeIngredients hiệp hội), bạn có thể thay thế các dòng cuối cùng với:

HAVING COUNT(DISTINCT i.name) = 2 
+0

@OMG Ngựa vằn: Lời nhắc nhở này đã được đề cập trong đoạn thứ hai. Nhưng tôi cho rằng nó sẽ không gây tổn thương để thêm thay thế (nó sẽ chạy chậm hơn). – Aaronaught

+0

Xin lỗi, tôi không đọc kỹ –

2
select r.* 
from Recipes r 
inner join (
    select ri.recipe_id 
    from RecipeIngredients ri 
    inner join Ingredients i on ri.ingredient_id = i.id 
    where i.name in ('chocolate', 'cream') 
    group by ri.recipe_id 
    having count(distinct ri.ingredient_id) = 2 
) rm on r.id = rm.recipe_id 
11

Sử dụng:

SELECT r.name 
    FROM RECIPES r 
    JOIN RECIPEINGREDIENTS ri ON ri.recipe_id = r.id 
    JOIN INGREDIENTS i ON i.id = ri.ingredient_id 
         AND i.name IN ('chocolate', 'cream') 
GROUP BY r.name 
    HAVING COUNT(DISTINCT i.name) = 2 

Điểm mấu chốt ở đây là số lượng phải bằng số tên thành phần. Nếu nó không phải là một số khác biệt, có nguy cơ dương tính giả do trùng lặp.

+0

Tùy chọn đầu tiên trông giống như những gì tôi muốn. Tôi đã thử nó với niềm vui không sử dụng PostgreSQL 8.3.5 và SQLite3. Tôi đã thử chỉ với một thành phần và cho mỗi thành phần của cây, lần lượt, tôi nhận được 2 hàng trở lại như tôi mong đợi. Nhưng, khi tôi thử với 2 thành phần, tôi nhận được 0 hàng. –

+0

Đó là những gì bạn nhận được vì không chỉ định cơ sở dữ liệu mà bạn đang thử nghiệm. S trong SQL không có nghĩa là "Chuẩn hóa"; ngoài một SELECT cơ bản, hầu như không có cơ hội 100% di động cho tất cả các nhà cung cấp. –

+0

Cơ sở dữ liệu nào sẽ hoạt động với? Có lẽ tôi có thể so sánh cú pháp và xem cách áp dụng nó cho PostgreSQL. Phong cách của bạn về giải pháp dễ dàng hơn cho tôi để hiểu hơn là không tồn tại –

1
SELECT DISTINCT r.id, r.name 
FROM Recipes r 
INNER JOIN RecipeIngredients ri ON 
    ri.recipe_id = r.id 
INNER JOIN Ingredients i ON 
    i.id = ri.ingredient_id 
WHERE 
    i.name IN ('cream', 'chocolate') 

Đã chỉnh sửa bình luận sau, cảm ơn! Đây là đúng cách sau đó:

SELECT DISTINCT r.id, r.name 
FROM Recipes r 
INNER JOIN RecipeIngredients ri ON 
    ri.recipe_id = r.id 
INNER JOIN Ingredients i ON 
    i.id = ri.ingredient_id AND 
    i.name = 'cream' 
INNER JOIN Ingredients i2 ON 
    i2.id = ri.ingredient_id AND 
    i2.name = 'chocolate' 
+3

Điều này cũng sẽ trả về công thức nấu ăn nơi kem được sử dụng mà không có sô cô la hoặc sô cô la được sử dụng mà không có kem. –

1

một cách khác nhau:

Phiên bản 2 (thủ tục như lưu trữ) sửa đổi

select r.name 
from recipes r 
where r.id = (select t1.recipe_id 
     from RecipeIngredients t1 inner join 
    RecipeIngredients  t2 on t1.recipe_id = t2.recipe_id 
    and  t1.ingredient_id = @recipeId1 
    and  t2.ingredient_id = @recipeId2) 

Chỉnh sửa 2: [trước khi mọi người bắt đầu la hét] :)

Điều này có thể được đặt ở đầu phiên bản 2, điều này sẽ cho phép truy vấn theo tên thay vì chuyển vào id.

select @recipeId1 = recipe_id from Ingredients where name = @Ingredient1 
select @recipeId2 = recipe_id from Ingredients where name = @Ingredient2 

Tôi đã thử nghiệm phiên bản 2 và hoạt động. Hầu hết người dùng nơi liên kết trên bảng Thành phần, trong trường hợp này là hoàn toàn không cần thiết!

Chỉnh sửa 3: (kết quả kiểm tra);

Khi quy trình lưu trữ này được chạy, đây là kết quả.

Kết quả là các định dạng (First Recipe_id; Thứ hai Recipe_id, quả)

1,1, Failed 
1,2, 'banana cream pie' 
1,3, 'chocolate banana surprise' 
2,1, 'banana cream pie' 
2,2, Failed 
2,3, 'chocolate cream pie' 
3,1, 'chocolate banana surprise' 
3,2, 'chocolate cream pie' 
3,3, Failed 

Rõ ràng truy vấn này không xử lý trường hợp khi cả hai trở ngại đều giống nhau, nhưng hoạt động cho tất cả các trường hợp khác.

Sửa 4: (xử lý cùng hạn chế trường hợp):

thay thế dòng này:

r.id = (select t1... 

để

r.id in (select t1... 

làm việc với các trường hợp thất bại trong việc đưa ra:

1,1, 'banana cream pie' and 'chocolate banana surprise' 
2,2, 'chocolate cream pie' and 'banana cream pie' 
3,3, 'chocolate cream pie' and 'chocolate banana surprise' 
+0

Bạn dường như giả định rằng chỉ có một recipe_id có thể phù hợp và không có gì giới hạn nó với sô cô la và kem –

+0

Hãy thử phiên bản 2 (dựa trên ý tưởng tương tự như phiên bản đầu tiên). Tôi đã thử nghiệm nó và nó hoạt động chính xác. – Darknight

+0

Bạn vẫn đang sử dụng 'r.id =' giả định chỉ có một kết quả. Thành phần là cần thiết tất nhiên nếu bạn đang phù hợp với tên thành phần nhưng tôi đoán khả năng này sẽ đến từ một danh sách và các Id sẽ được biết đến. –

Các vấn đề liên quan