これは単純な N:M (多対多) の関係であり、あなたが提示したアプローチは、効率と管理の面で最悪の結果をもたらす可能性があります。
これがあなたの状況です:
- と の 2 つのエンティティが
recipes
ありingredients
ます。
- 1 つの材料が多くのレシピの一部になる場合があります。
- 1 つのレシピが多くの材料で構成されている場合があります。
任意の 2 つのエンティティ間にこの関係がある場合は常に、2 つではなく 3 つのテーブルが必要になります。
+-----------+ +-------------------------+ +-------------------+
| recipes | | recipes_has_ingredients | | ingredients |
+-----------+ +-------------------------+ +-------------------+
| recipe_id | | recipe_id | | ingredient_id |
| name | | ingredient_id | | name |
| ... | +-------------------------+ | calories |
+-----------+ +-------------------+
recipes
およびベース テーブルingredients
と呼ばれるもので、特定のエンティティに関する固有の情報が格納されます。
このrecipes_has_ingredients
テーブルは相互参照テーブル(または "XREF") と呼ばれるもので、2 つのエンティティ間の関連付けが格納されます。このテーブルのフィールド:recipe_id
およびingredient_id
両方ともそれぞれのベース テーブルにリンクしており、XREF テーブルの各行の 2 つの組み合わせは一意です。基本的に、それぞれrecipe_id
が持つ可能性のある多くの関連付けを異なるにマップし、ingredient_id
その逆も同様です。
この設計が多対多の関係を促進するのはなぜですか? その XREF テーブルのデータは次のように表示されるためです。
+-----------------------------+
| recipe_id | ingredient_id |
+-----------------------------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 1 |
| 2 | 2 |
| 2 | 3 |
| 3 | 1 |
| 3 | 2 |
| 3 | 3 |
+-----------------------------+
明らかなように、1 つのレシピが多くの (3) の材料に関連付けられており、1 つの材料が多くの (3) のレシピに関連付けられています。また、いずれかの列の値を繰り返すことができるが、2 つの列の組み合わせは一意であることに注意してください。これは、N:M 関係を機能させるこの設計の重要な側面です。
したがって、この設計を使用してデータを簡単に取得および管理する方法の簡単な例を次に示します。
// Given a particular recipe_id, retrieve all ingredients used in that recipe:
SELECT name
FROM recipes_has_ingredients
INNER JOIN ingredients USING (ingredient_id)
WHERE recipe_id = <id>
// Retrieve the name of recipe (of id 4), and total amount of calories it has:
SELECT a.name,
SUM(c.calories) AS calorie_count
FROM recipes a
INNER JOIN recipes_has_ingredients b ON a.recipe_id = b.recipe_id
INNER JOIN ingredients c ON b.ingredient_id = c.ingredient_id
WHERE a.recipe_id = 4
GROUP BY a.recipe_id,
a.name
// Given a list of ingredient_id's, retrieve all recipes that contain
// ALL of the listed ingredients
SELECT name
FROM recipes
INNER JOIN recipes_has_ingredients USING (recipe_id)
WHERE ingredient_id IN (1,2,3)
GROUP BY recipe_id
HAVING COUNT(*) = 3
// Given a particular recipe_id (id 6), add two more ingredients
// that it has (ids 4 & 9):
INSERT INTO recipes_has_ingredients VALUES (6,4), (6,9);
// Delete a particular recipe:
DELETE FROM recipe WHERE recipe_id = 4
^ リレーションシップ間の CASCADE ルールを適切に定義した場合、上記のDELETE
操作により、そのレシピのアソシエーションもすべて削除されます。
元のデザインを振り返って、レシピに含まれる特定の食材を更新または削除したい場合や、食材の名前を変更したい場合はどうしますか? csv 文字列の正しい位置を変更するにはハックな手続き型コードが必要になるか、テーブルのすべての行を更新して単一の成分のわずかな変更を反映する必要があります。
次のような、オリジナルのデザインを使用しなければ答えられなかった多くの説得力のある質問もあります。
- カロリー数が最高/最低のレシピは?
- 最も多くのレシピに含まれる食材は?
...リストは続きますが、この設計を実装する利点は十分に役立ちます。物事を正しく行うことで、多くの困難や苦しみから身を守ることができます。=)