简体   繁体   中英

Storing and calculating relationships in a one-to-many system

I'm scratching my head, looking for a reasonable way of achieving this. The title was really difficult to figure out, so I'm sorry if you entered this post hoping for something else. It's both a database and a logic question.

I have 100 entries in a "collection". Let's call each one an object. Each object refers to an entry in a database, and has some name (let's say obj1, obj2 ...). Some of these objects are a "compound" of other objects. So, for instance, if obj1 and obj2 are inputs, obj11 is the output (obj1, obj2 -> obj11). Maybe even multiple inputs of the same object: obj1 x 4, obj2, obj11 -> obj21.

I'm looking for a way to calculate the resulting object continuously while changing the input, and thus these relations must have some clever way of being stored in a database, so that a calculation like this can be done in a rather elegant fashion.

Naturally I thought of a many-to-one table, but couldn't find a logical way to then find a result for given inputs without parsing the whole table.

Does anyone have some thoughts on this?

You seem to be building a sort of recipe system. Some objects are created from different quantities of other objects. In terms of datastructure, you could have something like

   struct Thing {
     std::vector<Ingredient> parts;
     // ... other attributes here
   };

   struct Ingredient {
     Thing component;
     int count;
   };

This can be stored using

   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`);

To store the recipe for a Thing , just insert all its components and their quantities as ingredients. To retrieve the recipe, just SELECT all ingredients with that result_id . To see where a Thing is used in a recipe, just query for its use as a component.

Imagine that A is built from B and C ; and that B can be built from D and E . Then you may want to perform a multi-stage query, to figure out from A = B + C that A = ( D + E ) + C . This requires one extra select on each component, to see if it has, itself, ingredients. However, this will backfire unless your recipes form a tree: what if A requires B and B can be built from A ?. Even worse: what if there are several means to obtain A , say from B + C or from X + Y ? If you need to distinguish among these alternative recipes, you would need an additional table:

   struct Thing {
     std::vector<Recipe> recipes;
     // ... other attributes here
   };

   struct Recipe {
     std::vector<Ingredient> ingredients;
     // ... other attributes here
   };

   struct Ingredient {
     Thing component;
     int count;
   };

And, in 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`);      

If you need to recover multi-stage recipes on something like this, then you are trying to perform graph queries on a relational database, and may find it a lot easier to use an object-graph database that is intended for just those types of queries.


Edit: assuming no cycles, and at most 1 recipe per Thing , how do I find what to cook given a list of ingredients? Several approaches are possible.

  • If the number of recipes is not truly huge, and the recipes do not change often, just load them all into memory and use set intersection to detect possible recipes. This will be faster than using the DB.
  • If you insist in using the DB, this works (and you can test it online :
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;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM