FIND_IN_SET() alternative?

前端 未结 3 609
执念已碎
执念已碎 2021-01-15 02:33

I have a query that currently looks like:

SELECT [column a], [column b], [column c], [column d]
FROM [table] 
WHERE FIND_IN_SET(2, column d)
ORDER BY [colu         


        
3条回答
  •  攒了一身酷
    2021-01-15 03:26

    One possible optimization is to define [column d] as type SET. As documentation says:

    MySQL stores SET values numerically, with the low-order bit of the stored value corresponding to the first set member. If you retrieve a SET value in a numeric context, the value retrieved has bits set corresponding to the set members that make up the column value.

    Here's a quick and simple example:

    CREATE TABLE `tbl_name` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `set_col` set('a','b','c','d') NOT NULL,
      PRIMARY KEY (`id`),
      KEY `set_col_idx` (`set_col`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    INSERT INTO `tbl_name` (`set_col`) VALUES
    ('a'),
    ('b'),
    ('c'),
    ('d'),
    ('a,b'),
    ('a,c'),
    ('a,d'),
    ('b,c'),
    ('b,d'),
    ('c,d'),
    ('a,b,c'),
    ('a,b,d'),
    ('a,c,d'),
    ('b,c,d'),
    ('a,b,c,d');
    

    A column set_col specified as SET('a','b','c','d') have members with following decimal and binary values:

    ╔════════════╦═══════════════╦══════════════╗
    ║ SET Member ║ Decimal Value ║ Binary Value ║
    ╠════════════╬═══════════════╬══════════════╣
    ║     'a'    ║       1       ║     0001     ║
    ╠════════════╬═══════════════╬══════════════╣
    ║     'b'    ║       2       ║     0010     ║
    ╠════════════╬═══════════════╬══════════════╣
    ║     'c'    ║       4       ║     0100     ║
    ╠════════════╬═══════════════╬══════════════╣
    ║     'd'    ║       8       ║     1000     ║
    ╚════════════╩═══════════════╩══════════════╝
    

    So, if you need to retrive records with value a,c,d, it's first, third and fourth member, which is 1 + 4 + 8, which is 13.

    If you run query:

    EXPLAIN SELECT * FROM `tbl_name` WHERE `tbl_name`.`set_col` = 13;
    

    You'll get:

    ╔════╦═════════════╦══════════╦══════╦═══════════════╦═════════════╦═════════╦═══════╦══════╦═════════════╗
    ║ id ║ select_type ║ table    ║ type ║ possible_keys ║ key         ║ key_len ║ ref   ║ rows ║ Extra       ║
    ╠════╬═════════════╬══════════╬══════╬═══════════════╬═════════════╬═════════╬═══════╬══════╬═════════════╣
    ║  1 ║ SIMPLE      ║ tbl_name ║ ref  ║ set_col_idx   ║ set_col_idx ║ 1       ║ const ║ 1    ║ Using index ║
    ╚════╩═════════════╩══════════╩══════╩═══════════════╩═════════════╩═════════╩═══════╩══════╩═════════════╝
    

    You don't need to manualy get know decimal values of SET options – you can use SUM, e.g.:

    SELECT *
    FROM `tbl_name`
    WHERE `set_col` = (SELECT SUM(`set_col`)
     FROM `tbl_name`
     WHERE `set_col` IN ('a', 'c', 'd')
    );
    
    ╔════╦═════════════╦══════════╦═══════╦═══════════════╦═════════════╦═════════╦═══════╦══════╦══════════════════════════╗
    ║ id ║ select_type ║ table    ║ type  ║ possible_keys ║ key         ║ key_len ║ ref   ║ rows ║ Extra                    ║
    ╠════╬═════════════╬══════════╬═══════╬═══════════════╬═════════════╬═════════╬═══════╬══════╬══════════════════════════╣
    ║  1 ║ PRIMARY     ║ tbl_name ║ index ║ set_col_idx   ║ set_col_idx ║ 1       ║ NULL  ║ 15   ║ Using where; Using index ║
    ╠════╬═════════════╬══════════╬═══════╬═══════════════╬═════════════╬═════════╬═══════╬══════╬══════════════════════════╣
    ║  1 ║ SUBQUERY    ║ tbl_name ║ range ║ set_col_idx   ║ set_col_idx ║ 1       ║ NULL  ║ 3    ║ Using where; Using index ║
    ╚════╩═════════════╩══════════╩═══════╩═══════════════╩═════════════╩═════════╩═══════╩══════╩══════════════════════════╝
    

提交回复
热议问题