将MYSQL中的GROUP_CONCAT字符串拆分成一个类似于表达式列表的数组,以便IN()函数可以理解。

18

这个问题是基于MYSQL join results set wiped results during IN () in where clause?的。

简单来说,如何将GROUP_CONCAT返回的字符串转换成逗号分隔的表达式列表,使IN()将其作为多个项的列表来循环处理?

N.B. MySQL文档似乎将IN()使用的“(逗号, 分隔的, 列表)”称为“表达式列表”,有趣的是,MySQL文档中几乎只有关于IN()的页面才涉及到表达式列表。因此,我不确定用于制作数组或临时表的函数是否在这里有用。


问题示例:从像这样的2个表的数据库中:

SELECT id, name, GROUP_CONCAT(tag_id) FROM person INNER JOIN tag ON person.id = tag.person_id GROUP BY person.id;
+----+------+----------------------+
| id | name | GROUP_CONCAT(tag_id) |
+----+------+----------------------+
|  1 | Bob  | 1,2                  |
|  2 | Jill | 2,3                  |
+----+------+----------------------+

我该如何将这个表达式转化为逻辑等价于 (1=X) AND (2=X) 的形式,因为它使用了字符串。

SELECT name, GROUP_CONCAT(tag.tag_id) FROM person LEFT JOIN tag ON person.id = tag.person_id 
GROUP BY person.id HAVING ( ( 1 IN (GROUP_CONCAT(tag.tag_id) ) ) AND ( 2 IN (GROUP_CONCAT(tag.tag_id) ) ) );
Empty set (0.01 sec)

将GROUP_CONCAT结果视为列表进行处理,这样对于Bob来说,它就相当于:

SELECT name, GROUP_CONCAT(tag.tag_id) FROM person INNER JOIN tag ON person.id = tag.person_id AND person.id = 1 
GROUP BY person.id HAVING ( ( 1 IN (1,2) ) AND ( 2 IN (1,2) ) );
+------+--------------------------+
| name | GROUP_CONCAT(tag.tag_id) |
+------+--------------------------+
| Bob  | 1,2                      |
+------+--------------------------+
1 row in set (0.00 sec)

...对于Jill来说,它等价于:

SELECT name, GROUP_CONCAT(tag.tag_id) FROM person INNER JOIN tag ON person.id = tag.person_id AND person.id = 2 
GROUP BY person.id HAVING ( ( 1 IN (2,3) ) AND ( 2 IN (2,3) ) );
Empty set (0.00 sec)

那么总体结果将是一个排除搜索条款,要求所有列出的标签都不使用HAVING COUNT(DISTINCT...)吗?

(注意:此逻辑在没有AND的情况下也适用于字符串的第一个字符。例如:

SELECT name, GROUP_CONCAT(tag.tag_id) FROM person LEFT JOIN tag ON person.id = tag.person_id 
  GROUP BY person.id HAVING ( ( 2 IN (GROUP_CONCAT(tag.tag_id) ) ) );
+------+--------------------------+
| name | GROUP_CONCAT(tag.tag_id) |
+------+--------------------------+
| Jill | 2,3                      |
+------+--------------------------+
1 row in set (0.00 sec)

2
这很好,但值得注意的是,在所有的FIND_IN_SET()中,您不需要执行GROUP_CONCAT()。您只需选择SELECT GROUP_CONCAT(tag.tag_id) AS tags_list,然后使用HAVING FIND_IN_SET(20, tags_list)即可。 - Treffynnon
2个回答

45

除了使用 IN(),使用FIND_IN_SET()是否也是一个选项?

http://dev.mysql.com/doc/refman/5.0/en/string-functions.html#function_find-in-set

mysql> SELECT FIND_IN_SET('b','a,b,c,d');
    -> 2

以下是一个完整的例子,基于问题中提出的范例问题,并已被问题提出者在早期编辑问题时确认过测试:

SELECT name FROM person LEFT JOIN tag ON person.id = tag.person_id GROUP BY person.id 
  HAVING ( FIND_IN_SET(1, GROUP_CONCAT(tag.tag_id)) ) AND ( FIND_IN_SET(2, GROUP_CONCAT(tag.tag_id)) );
+------+
| name |
+------+
| Bob  |
+------+

太好了!一个提示:不要忘记,由于它类似于函数调用,在开括号/括号之前不能有空格,否则你会得到一个错误Function dbname.FIND_IN_SET does not exist - user56reinstatemonica8
这是一个解决方案,但我发现FIND_IN_SET比IN要低效得多...有人知道是否有任何方法真正实现问题所说的将字符串拆分为类似数组的变量的方法吗? - Andrés Monge Moreno
1
@AndrésMongeMoreno:在我回答这个问题的时候,Mysql 5.0是最新版本,因此选择不多。如果使用当前版本,我可能会建议使用JSON函数:http://dev.mysql.com/doc/refman/5.7/en/json-functions.html - Wolph
@Wolph 我明白了,没问题 =D。只是想留点东西给未来的读者。根据我的经验,FIND_IN_SET 比任何其他选择都要慢很多(即使在 group by 之前使用游标循环结果)。 - Andrés Monge Moreno
@AndrésMongeMoreno 如果有可能的话,我建议你用postgresql替换mysql/mariadb。这样肯定会给你带来更好的性能。 - Wolph

5
您可以将字符串作为数组传递,并使用拆分分隔符在函数中进行分割,该方法将使用结果工作。
举个简单的例子,如果您有这样一个字符串数组:'one|two|tree|four|five',并且想知道是否存在“two”,您可以按照以下方式进行操作:
create function str_in_array( split_index varchar(10), arr_str varchar(200), compares varchar(20) )
  returns boolean
  begin
  declare resp boolean default 0;
  declare arr_data varchar(20);

  -- While the string is not empty
  while( length( arr_str ) > 0  ) do

  -- if the split index is in the string
  if( locate( split_index, arr_str ) ) then

      -- get the last data in the string
    set arr_data = ( select substring_index(arr_str, split_index, -1) );

    -- remove the last data in the string
    set arr_str = ( select
      replace(arr_str,
        concat(split_index,
          substring_index(arr_str, split_index, -1)
        )
      ,'')
    );
  --  if the split index is not in the string
  else
    -- get the unique data in the string
    set arr_data = arr_str;
    -- empties the string
    set arr_str = '';
  end if;

  -- in this trivial example, it returns if a string is in the array
  if arr_data = compares then
    set resp = 1;
  end if;

 end while;

return resp;
end
|

delimiter ;

我想创建一组有用的mysql函数来使用此方法。有兴趣的人请与我联系。

更多示例,请访问http://blog.idealmind.com.br/mysql/how-to-use-string-as-array-in-mysql-and-work-with/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接