Вы, кажется, строите своего рода систему рецептов. Некоторые объекты создаются из разных количеств других объектов. С точки зрения структуры данных, вы могли бы иметь что-то вроде
struct Thing {
std::vector<Ingredient> parts;
// ... other attributes here
};
struct Ingredient {
Thing component;
int count;
};
Это можно сохранить с помощью
CREATE TABLE `Things` (
`id` int(11) NOT NULL AUTO_INCREMENT,
# ... other attributes here
PRIMARY KEY `id`
);
CREATE TABLE `Ingredients` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`result_id` int(11) NOT NULL,
`component_id` int(11) NOT NULL,
`count` int(11) NOT NULL,
PRIMARY KEY `id`
);
ALTER TABLE `Ingredients` FOREIGN KEY (`result_id`) REFERENCES `Thing` (`id`);
ALTER TABLE `Ingredients` FOREIGN KEY (`component_id`) REFERENCES `Thing` (`id`);
Чтобы сохранить рецепт для Thing
, просто вставьте все его компоненты и их количество в качестве ингредиентов. Чтобы получить рецепт, просто SELECT
все ингредиенты с этим result_id
. Чтобы увидеть, где Thing
используется в рецепте, просто запросите его использование в качестве компонента.
Представьте себе, что A
построен из B
и C
; и что B
может быть построен из D
и E
. Затем вы можете выполнить многоэтапный запрос, чтобы выяснить из A
= B
+ C
, что A
= (D
+ E
) + C
. Для этого требуется один дополнительный выбор для каждого компонента, чтобы увидеть, есть ли у него ингредиенты. Однако, это будет иметь неприятные последствия, если ваши рецепты не сформируют дерево: что если A
требует B
и B
может быть построено из A
?. Еще хуже: что, если есть несколько способов получить A
, скажем, от B
+ C
или от X
+ Y
? Если вам необходимо различать эти альтернативные рецепты, вам понадобится дополнительная таблица:
struct Thing {
std::vector<Recipe> recipes;
// ... other attributes here
};
struct Recipe {
std::vector<Ingredient> ingredients;
// ... other attributes here
};
struct Ingredient {
Thing component;
int count;
};
И, в SQL,
CREATE TABLE `Recipes` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`result_id` int(11) NOT NULL,
PRIMARY KEY `id`
);
CREATE TABLE `Ingredients` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`recipe_id` int(11) NOT NULL,
`component_id` int(11) NOT NULL,
`count` int(11) NOT NULL,
PRIMARY KEY `id`
);
ALTER TABLE `Recipes` FOREIGN KEY (`result_id`) REFERENCES `Thing` (`id`);
ALTER TABLE `Ingredients` FOREIGN KEY (`recipe_id`) REFERENCES `Recipe` (`id`);
ALTER TABLE `Ingredients` FOREIGN KEY (`component_id`) REFERENCES `Thing` (`id`);
Если вам нужно восстановить многоступенчатые рецепты для чего-то подобного, то вы пытаетесь выполнить графовые запросы к реляционной базе данных, и может оказаться намного проще использовать базу данных объектных графов, предназначенную только для этих типы запросов.
Редактировать: при условии отсутствия циклов и не более 1 рецепта на Thing
, как мне найти, что готовить, учитывая список ингредиентов? Возможны несколько подходов.
- Если количество рецептов не очень велико, и рецепты меняются не часто, просто загрузите их все в память и используйте набор пересечений для обнаружения возможных рецептов. Это будет быстрее, чем при использовании БД.
- Если вы настаиваете на использовании БД, это работает (и вы можете проверить это онлайн :
SELECT possible.id
FROM (
SELECT i.result_id AS id, COUNT(*) AS total
FROM `Ingredients` AS i GROUP BY i.result_id
) AS possible, (
SELECT result_id, COUNT(result_id) AS total
FROM `Ingredients` AS i WHERE
(i.component_id = 1 AND i.count<=1) OR
(i.component_id = 2 AND i.count<=3) OR
(i.component_id = 42 AND i.count<=1)
GROUP BY i.result_id
) AS valid
WHERE possible.id = valid.result_id AND possible.total = valid.total;