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