Хранение и расчет отношений в системе «один ко многим» - PullRequest
0 голосов
/ 05 июля 2019

Я чешу голову, ища разумный способ добиться этого. Название было действительно трудно понять, поэтому извините, если вы вошли в этот пост в надежде на что-то еще. Это и база данных, и логический вопрос.

У меня есть 100 записей в "коллекции". Давайте назовем каждого объектом. Каждый объект ссылается на запись в базе данных и имеет некоторое имя (скажем, obj1, obj2 ...). Некоторые из этих объектов являются «соединением» других объектов. Так, например, если obj1 и obj2 являются входными данными, obj11 является выходными данными (obj1, obj2 -> obj11). Возможно даже несколько входов одного и того же объекта: obj1 x 4, obj2, obj11 -> obj21.

Я ищу способ непрерывного вычисления результирующего объекта при изменении входных данных, и, таким образом, эти отношения должны иметь какой-то умный способ хранения в базе данных, чтобы такой расчет можно было сделать в довольно элегантном мода.

Естественно, я подумал о таблице «многие к одному», но не смог найти логического способа найти результат для заданных входных данных без анализа всей таблицы.

У кого-нибудь есть мысли по этому поводу?

1 Ответ

1 голос
/ 05 июля 2019

Вы, кажется, строите своего рода систему рецептов. Некоторые объекты создаются из разных количеств других объектов. С точки зрения структуры данных, вы могли бы иметь что-то вроде

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