FIND_IN_SET() alternative?

前端 未结 3 608
执念已碎
执念已碎 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:05

    Alternative: properly normalize the schema.

    FIND_IN_SET is not Sargable and the index cannot be used.

    0 讨论(0)
  • 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 ║
    ╚════╩═════════════╩══════════╩═══════╩═══════════════╩═════════════╩═════════╩═══════╩══════╩══════════════════════════╝
    
    0 讨论(0)
  • 2021-01-15 03:26

    1- fulltext index is not a good idea in this case because: length of the string you searching for is small (1) in this case, and this won't be found (It is configurable though, but not a good idea)

    2- If this query is frequent, I suggest changing the tables' structure as follows:

    • table1 (col_a PK, col_b, col_c)
    • table2 (col_a FK, value_of_sub_d) where value_of_sub_d is one of (2, 3, ...)

    In this one-to-many relationship, you can either do a join, or get the PK from table2 where condition is met, and select that ID's row from table1 Example:

    Select * from table1 t1 inner join table2 t2 on t1.col_a=t2.col_a WHERE t2.value_of_sub_d=2
    
    0 讨论(0)
提交回复
热议问题