如何使用IN,FIND_IN_SET比较逗号分隔字符串中的多个值?

时间:2014-03-18 09:24:49

标签: php mysql sql

我有逗号分隔的多个值

(1,3,5)想要与(2,3,4,5,7,5)进行比较,这个集合指的是列值。所以它应该返回3和5

这个值是动态的

我用过

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'))依此类推

但它非常让我知道任何更好的解决方案。

2 个答案:

答案 0 :(得分:2)

简短回答

你应该避免这种情况。虽然它实际上可以完成,但您当前的架构至少违反了first NF。这是不好的情况。存储分隔符分隔列表仅在您需要处理整个字符串时才适用,但不适用于单独的值本身。因此,最合适的解决方案是:创建附加表并将值放在那里。

答案很长

这可以被视为某种谜题 - 但我强烈不建议在实际应用中使用它。所以,我们假设我们有表t

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

我们希望用字符串'1,3,35'“交叉”我们的字符串。我假设您的字符串源自应用程序 - 因此,您可以使用它做一些准备工作。

最终SQL将如下所示:

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

(演示可用here)。

工作原理

有一些技巧,用于此SQL。首先,迭代变量。 MySQL支持user-defined variables,它们可用于查询中的某种迭代。我们正在使用它将有效的偏移量和长度传递给我们的字符串 - 通过SUBSTR()得到它。

下一招:我们需要产生一定数量的行 - 否则迭代将无效。这可以通过以下方式完成:计算每行中的分隔符并使用该计数+ 1重复它。 MySQL没有序列,但还有第三个技巧:通过巨大的CROSS JOIN创建所需的计数(使用2的幂的总和来获得连续的数字)。这就是内部LEFT JOIN的内容。事实上,我在one的问题中遇到了这个问题。

最后,我们对整个结果进行INNER JOIN以获取相交的值。注意:这是你需要为你的字符串做一些准备的部分。但是在应用程序中拆分字符串很容易,需要UNION ALL部分查询。

问题是什么

  • 无效的字符串。不会对'1,,,,4,5'之类的内容进行检查。真的 - 这不是这种方法的意图
  • 无效的非数字值。由于我们正在替换0..9(那个巨大的REPLACE部分) - 我们不能动态地做到这一点 - MySQL不能“替换任何字符,除了......”这是一个瓶颈,是的 - 但是,再次 - 不是方法的意图

答案 1 :(得分:1)

虽然我不建议在实时代码中执行此操作,但无需变量即可完成: -

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

这样做是选择0到9联合,并将其加入到自身。这得到100个组合,并通过一点乘法得到数字0到100.然后将其与要检查的表交叉连接,并使用此数字作为SUBSTRING_INDEX的参数将其拆分为逗号。因此,它可以处理您要检查的逗号分隔字符串中的~100个数字。缺点是它会复制其中一些数字,因此需要删除重复数据。

结果数字随后可与FIND_IN_SET()一起使用,以检查逗号分隔字段中包含这些数字的行。

然后我使用带有DISTINCT的GROUP_CONCAT来显示该行的匹配数字。

这里的SQL小提琴: -

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