简体   繁体   中英

How to compare multiple value in comma separated string using IN, FIND_IN_SET?

I have multiple values in comma separated

(1,3,5) want to compare with (2,3,4,5,7,5) and this set refer to column value. So it should return 3 and 5

and this values are dynamic

I have used

SELECT * FROM table WHERE FIND_IN_SET('3', ('2,3,4,5,7,5')) AND FIND_IN_SET('5', ('2,3,4,5,7,5')) and so on

but it very tedius let me know any better solution for this.

Short answer

You should avoid this. While it actually can be done, your current architecture is violating at least first NF . And that's bad case. Storing delimiter-separated list is applicable only if you need to work with entire string, but not separate value itself. Therefore, most proper solution would be: create additional table and put your values there.

Long answer

This can be treated as some sort of puzzle - but I strongly do not recommend to use it on real application. So, let's suppose we have table t :

+------+------------------+
| id   | col              |
+------+------------------+
|    1 | 1,35,61,12,8     |
|    4 | 82,12,99,100,1,3 |
|    6 | 35,99,1          |
+------+------------------+

And we want to 'intersect' our strings with string '1,3,35' . I assume that your string is derived from application - therefore, you're able to do some preparations with it.

Final SQL will look like:

SELECT
  resulted.id,
  GROUP_CONCAT(resulted.sub) AS result
FROM
  (SELECT
    r.id, 
    TRIM(BOTH ',' FROM SUBSTR(
      r.col, 
      @cur,
      LOCATE(',', r.col, @cur+1)-@cur
    )) AS sub,
    @cur:=IF(
      CHAR_LENGTH(r.col)=LOCATE(',', r.col, @cur+1),
      1,
      LOCATE(',', r.col, @cur+1)
    ) AS cur
  FROM
    (SELECT
    id,
    CONCAT(TRIM(BOTH ',' FROM t.col), ',') AS col,
    CHAR_LENGTH(
      REPLACE(
      REPLACE(
      REPLACE(
      REPLACE(
      REPLACE(
      REPLACE(
      REPLACE(
      REPLACE(
      REPLACE(
      REPLACE(col
      , '9', '')
      , '8', '')
      , '7', '')
      , '6', '')
      , '5', '')
      , '4', '')
      , '3', '')
      , '2', '')
      , '1', '')
      , '0', '')
    ) + 1 AS repeats
    FROM t) AS r
    LEFT JOIN
    (SELECT
      (two_1.id + two_2.id + two_4.id + 
      two_8.id + two_16.id) AS id
     FROM
      (SELECT 0 AS id UNION ALL SELECT 1 AS id) AS two_1
      CROSS JOIN (SELECT 0 id UNION ALL SELECT 2 id) AS two_2
      CROSS JOIN (SELECT 0 id UNION ALL SELECT 4 id) AS two_4
      CROSS JOIN (SELECT 0 id UNION ALL SELECT 8 id) AS two_8
      CROSS JOIN (SELECT 0 id UNION ALL SELECT 16 id) AS two_16
     ) AS init
    ON init.id<r.repeats
    CROSS JOIN
      (SELECT @cur:=1) AS vars
   ) AS resulted
  INNER JOIN
  (SELECT '1' AS sub UNION ALL
   SELECT '3' UNION ALL
   SELECT '35'
  ) AS input
    ON resulted.sub=input.sub
GROUP BY
  resulted.id

(the demo is available here ).

How it works

There are some tricks, that were used for this SQL. First, iteration variable. MySQL supports user-defined variables and they can be used for some sort of iterations in queries. And we're using it to pass valid offset and length into our string - to get piece of it via SUBSTR() .

Next trick: we need to produce certain amount of rows - otherwise iteration won't work. That can be done the following way: count delimiters in each row and repeat it with that count+1. MySQL has no sequences, but there is third trick: to create desired count via huge CROSS JOIN (with summation of powers of 2 to get consecutive numbers). And that's for what internal LEFT JOIN is. In fact, I've faced this issue in one of my questions.

And, finally, we're doing INNER JOIN on entire result to get our intersected values. Note: this is the part, for which you'll need to make some preparations on your string. But it's easy to split string in application, getting needed UNION ALL part of query above.

What is out of the issue

  • Invalid strings. No checks will be done for things like '1,,,,4,5' . Really - it's not an intention of this method
  • Invalid non-numeric values. Since we're replacing 0..9 (that huge REPLACE part) - we can't do that dynamically - MySQL can't "replace any char, except.." This is a bottleneck, yes - but, again - not intention of the method

While I wouldn't recommend doing this in live code, it can be done without the need for variables:-

SELECT id, some_col, GROUP_CONCAT(DISTINCT SUBSTRING_INDEX(SUBSTRING_INDEX('1,3,5', ',', AnInt), ',', -1) ORDER BY 1) AS anItem
FROM some_table
CROSS JOIN
(
    SELECT 1 + Units.i + Tens.i * 10 as AnInt
    FROM
    (SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) Units,
    (SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) Tens
) Sub1
WHERE FIND_IN_SET(SUBSTRING_INDEX(SUBSTRING_INDEX('1,3,5', ',', AnInt), ',', -1), some_col)  
GROUP BY id, some_col

What this is doing is selecting 0 to 9 unioned, and joining this against itself. This gets 100 combinations, and by a bit of multiplication it gets the numbers 0 to 100. It then cross joins this against the table ou want to check, and uses this number as a parameter to SUBSTRING_INDEX to split it up on the commas. As such it can cope with ~100 numbers in the comma separated string you want to check. Down side is that it will duplicate some of these numbers, hence duplicates need to be removed.

The resulting numbers can then be used with FIND_IN_SET() to check the rows that contain these numbers in their comma separated field.

I have then used GROUP_CONCAT with DISTINCT to display the matching numbers for that row.

SQL Fiddle for it here:-

http://www.sqlfiddle.com/#!2/edf97/3

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