【问题标题】:How to compare multiple value in comma separated string using IN, FIND_IN_SET?如何使用 IN、FIND_IN_SET 比较逗号分隔字符串中的多个值?
【发布时间】:2014-03-18 09:24:49
【问题描述】:

我有多个以逗号分隔的值

(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'))

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

【问题讨论】:

  • 提示:规范化您的数据。将您的值存储在单独的表中。这样做的充分理由是:您需要使用单独的值,而不是整个字符串
  • @AlmaDo 是的,我可以理解这一点,但我现在无法更改数据库,我必须解决这个问题,因为对于规范化数据库,它需要在站点中进行如此多的更改
  • 如您所说,您的架构是 'tedius'。因此,您需要应用这样的解决方案。另一种方法可能是使用应用程序(array_intersect() 用于 PHP)
  • @AlmaDo 但我必须比较的另一个字符串是动态的并且是从数据库生成的
  • 所以:是的,它是:全选,然后 array_intersect 在每一行的循环中。是不是更短了?是的。它更快吗?不,但你需要决定你想要什么

标签: php mysql sql


【解决方案1】:

简答

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

长答案

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

+--------+------------------+ |编号 |上校 | +--------+------------------+ | 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 不能“替换任何字符,除了..” 这是一个瓶颈,是的 - 但是,再次 - 不是该方法的意图

【讨论】:

  • 感谢您的努力...最后我决定在循环中设置查找.. 将来我会避免这样做会记住规范化
  • 这很棘手。但是——这是解决 Jaywalker 的一种方法。当然,规范化将拯救世界(而且,我认为,看看答案中发布的 SQL,应该可以说服任何人快点并规范化他们的数据库)
【解决方案2】:

虽然我不建议在实时代码中这样做,但它可以在不需要变量的情况下完成:-

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() 一起使用,以检查在其逗号分隔字段中包含这些数字的行。

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

这里的 SQL Fiddle:-

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

【讨论】:

  • 可能很有趣,但在某些情况下failing
  • @AlmaDo - 似乎工作正常。您添加了 100 作为数字,仅在一个地方检查(在执行 group_concat 找到的值时,也需要在此处添加 100)
  • 确实(高线超出屏幕)。它适用于这种情况,而我仍然更喜欢调度整个值集,以便它能够进一步使用它做任何事情
  • @AlmaDo - 很容易改变它来做到这一点。只需将其更改为执行 SELECT DISTINCT。我只是使用 GROUP_CONCAT 来显示它将在一行中检索的元素,而不是分布在多行中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-16
  • 1970-01-01
相关资源
最近更新 更多