MySQL permutation

前端 未结 3 766
攒了一身酷
攒了一身酷 2021-01-03 06:15

I have two tables. One has products and the other has bundles that go with it. I need to figure out the SQL that allows me to find all the combinations in which I can sell

相关标签:
3条回答
  • 2021-01-03 07:04

    Every extras can be in the bundle or not, making that a binary property.
    A way to visualize the combination is to create a word with a bit for every extra, 1 mean that the extra is in the list, 0 mean the that it is not.
    For example Bench + undershelf + overshelf is 110 (or 011 if the binary string is read in the opposite order)

    Generating every combination of n bit will give every combination of n extras, it will also give every number from 0 to 2^n - 1.

    We can work back from here:
    1. generate the list of number from 0 to 2^n - 1;
    2. convert the number to binary, to list the combination of extras
    3. match every bit with an extra
    4. concatenate the names of the extras in the bundle description.

    SELECT CONCAT(b.Name
                , COALESCE(CONCAT(' + '
                                , GROUP_CONCAT(x.Name SEPARATOR ' + '))
                         , '')) Combination
    FROM   (SELECT p.Name, p.id
                         , LPAD(BIN(u.N + t.N * 10), e.Dim, '0') bitmap
                    FROM   Products p
                           CROSS JOIN (SELECT 0 N UNION ALL SELECT 1 
                             UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
                             UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7
                             UNION ALL SELECT 8 UNION ALL SELECT 9) u
                           CROSS JOIN (SELECT 0 N UNION ALL SELECT 1 
                             UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
                             UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7
                             UNION ALL SELECT 8 UNION ALL SELECT 9) t
                           INNER JOIN (SELECT COUNT(1) Dim
                                           , `Parent ID` pID
                                       FROM Extra) E ON e.pID = p.ID
                    WHERE  u.N + t.N * 10 < Pow(2, e.Dim)
           ) B
           LEFT  JOIN (SELECT @rownum := @rownum + 1 ID
                            , `Parent ID` pID
                            , Name
                       FROM   Extra
                            , (Select @rownum := 0) r) X
                              ON x.pID = b.ID
                             AND SUBSTRING(b.bitmap, x.ID, 1) = '1'
    GROUP BY b.Name, b.bitmap
    

    this query will work up to six extras, then it'll need another digit table (one digit every three extras).

    How it Works

    The subquery E count the number of the extras, this is used in C to limit the elements generated by the digit tables u and t (unit and tens) to 2^dim.

    The number is converted to binary by BIN(u.N + t.N * 10), then left padded with '0' to the number of elements, generating a combination bitmap.

    To use the generated bitmap each extras need a fake id that will match a position in it, that's what the subquery X is meant for.

    The two subqueries are JOINed by the nth char of the bitmap: if the char is 1 the extra is in the bundle, LEFT joined to not loose the product without extras.

    0 讨论(0)
  • 2021-01-03 07:10

    Possible entirely within MySQL, though not simple. This example can handle up to 5 "extras", and is easily extensible for more:

    CREATE TABLE products (name varchar(100), id int primary key);
    INSERT INTO products (name, id) VALUES ('Bench', 1);
    
    CREATE TABLE extra (name varchar(100), id int primary key, parent_id int references products.id, qty int);
    INSERT INTO extra (name, id, parent_id, qty) VALUES 
      ('undershelf', 1, 1, 1), ('overshelf', 2, 1, 1), ('wheels', 3, 1, 1);
    
    CREATE TABLE boolean_values (x boolean);
    INSERT INTO boolean_values VALUES (TRUE), (FALSE);
    
    CREATE VIEW product_extras_interim_vw AS
    SELECT p.id product_id, p.name product_name, e.id extra_id, e.name extra_name, x
      FROM products p
      JOIN extra e ON (e.parent_id = p.id)
      CROSS JOIN boolean_values;
    
    SELECT DISTINCT a.product_name
    , CASE WHEN a.x THEN CONCAT(' + ', a.extra_name) END extra1
        , CASE WHEN b.x THEN CONCAT(' + ', b.extra_name) END extra2
        , CASE WHEN c.x THEN CONCAT(' + ', c.extra_name) END extra3
        , CASE WHEN d.x THEN CONCAT(' + ', d.extra_name) END extra4
        , CASE WHEN e.x THEN CONCAT(' + ', e.extra_name) END extra5
    FROM product_extras_interim_vw a
    LEFT JOIN product_extras_interim_vw b
      ON ( a.product_id = b.product_id
          AND b.extra_id > a.extra_id
      AND a.x )
    LEFT JOIN product_extras_interim_vw c
      ON ( a.product_id = c.product_id
      AND c.extra_id > b.extra_id
          AND b.x )
    LEFT JOIN product_extras_interim_vw d
      ON ( a.product_id = d.product_id
          AND d.extra_id > c.extra_id
          AND c.x)
    LEFT JOIN product_extras_interim_vw e
      ON ( a.product_id = e.product_id
          AND e.extra_id > d.extra_id
          AND d.x)
    ORDER BY product_name, extra1, extra2, extra3, extra4, extra5;
    

    Output:

    Bench
    Bench + overshelf
    Bench + overshelf + wheels
    Bench + undershelf
    Bench + undershelf + overshelf
    Bench + undershelf + overshelf + wheels
    Bench + undershelf + wheels
    Bench + wheels
    
    0 讨论(0)
  • 2021-01-03 07:16

    I cannot think of any ingenious way of doing this in mysql, but it is very easy in a scripting language. Here in PHP:

    <?php
    $extra = array('undershelf', 'overshelf', 'sheels');
    $possible_combinations = pow(2, count($extra));
    for ($i = 0; $i < $possible_combinations; $i++) {
        $combo = array('Bench');
        foreach ($extra as $j => $item) {
            if ($i & pow(2, $j)) {
                $combo[] = $item;
            }
        }
    
        echo implode(' + ', $combo) . "\n";
    }
    

    prints

    Bench
    Bench + undershelf
    Bench + overshelf
    Bench + undershelf + overshelf
    Bench + sheels
    Bench + undershelf + sheels
    Bench + overshelf + sheels
    Bench + undershelf + overshelf + sheels
    
    0 讨论(0)
提交回复
热议问题