MySQL“IN”子句中的逗号分隔值

Posted

技术标签:

【中文标题】MySQL“IN”子句中的逗号分隔值【英文标题】:Comma separated values in MySQL "IN" clause 【发布时间】:2012-05-15 20:55:05 【问题描述】:

我的一个表中有一个列,其中存储了多个用逗号分隔的 ID。 有没有一种方法可以在查询的“IN”子句中使用该列的值。

列(city) 的值类似于6,7,8,16,21,2

我需要用作

select * from table where e_ID in (Select city from locations where e_Id=?)

我对 Crozin 的回答很满意,但我愿意接受建议、意见和选择。

随时分享您的观点。

【问题讨论】:

【参考方案1】:

在@Jeremy Smith 的 FIND_IN_SET() 示例的基础上,您可以使用连接来完成,因此您不必运行子查询。

SELECT * FROM table t
JOIN locations l ON FIND_IN_SET(t.e_ID, l.city) > 0
WHERE l.e_ID = ?

众所周知,它的性能很差,因为它必须进行表扫描,为tablelocations 中的每个 行组合评估FIND_IN_SET() 函数。它不能使用索引,也没有办法改进它。

我知道您说过您正在努力充分利用糟糕的数据库设计,但您必须了解这是多么糟糕。

解释:假设我要让你查找电话簿中第一个、中间或最后一个首字母是“J”的每个人。在这种情况下,图书的排序顺序没有任何帮助,因为无论如何您都必须扫描每一页。

@fthiella 给出的LIKE 解决方案在性能方面也存在类似问题。它无法被索引。

另请参阅我对Is storing a delimited list in a database column really that bad? 的回答,了解这种存储非规范化数据的方式的其他缺陷。

如果您可以创建补充表来存储索引,则可以将位置映射到城市列表中的每个条目:

CREATE TABLE location2city (
 location INT,
 city INT,
 PRIMARY KEY (location, city)
); 

假设您有一个所有可能城市的查找表(不仅仅是table 中提到的那些),您可以承受一次生成映射的低效率:

INSERT INTO location2city (location, city)
  SELECT l.e_ID, c.e_ID FROM cities c JOIN locations l
  ON FIND_IN_SET(c.e_ID, l.city) > 0;

现在您可以运行更高效的查询来查找 table 中的条目:

SELECT * FROM location2city l
JOIN table t ON t.e_ID = l.city
WHERE l.e_ID = ?;

这可以利用索引。现在您只需要注意 locations 中的任何 INSERT/UPDATE/DELETE 行也会在 location2city 中插入相应的映射行。

【讨论】:

我使用了 FIND_IN_SET 选项。这是此处可用的最佳答案。而不是连接和子查询,这最适合。但除非它非常重要,否则不要尝试将逗号分隔的值存储到列中。这不适合 RDBMS。我之所以使用它,是因为表和数据是在很久以前创建的,并且有很多数据我不想通过数据传输来冒险。 完全解决了我的需要。谢谢。【参考方案2】:

mysql 的角度来看,您存储的不是多个用逗号分隔的 id - 您存储的是 text 值,其含义与“Hello World”或“I like cakes”完全相同!” - 即它没有任何意义。

您要做的是创建一个单独的表,它将数据库中的两个对象链接在一起。详细了解基于 SQL 的数据库中的 many-to-many 或 one-to-many(取决于您的要求)关系。

【讨论】:

感谢您的建议,我承认设计很差,但我有一些无法更改的限制。有没有办法实现这个....【参考方案3】:

不要在查询中使用IN,而是使用FIND_IN_SET (docs):

SELECT * FROM table 
WHERE 0 < FIND_IN_SET(e_ID, (
             SELECT city FROM locations WHERE e_ID=?))

关于第一种形式规范化的常见注意事项适用(数据库不应在单个列中存储多个值),但如果您坚持使用它,那么上述语句应该会有所帮助。

【讨论】:

感谢您的回答。这非常有帮助。性能上有什么亮点吗? 性能会比较差,因为FIND_IN_SET不能使用任何索引。如果您担心性能(并且有权这样做),您确实需要使用 1nf 将您的 locations.city 列拆分到另一个表中,然后您可以使用索引来帮助您提高性能。 正如@JeremySmyth 所说,理论上将逗号分隔的数字列表存储在一列中违反了数据库设计的基本规则,即原子性规则(一个单元格,一个值)。因此,最好重新设计它。但是,如果约束不允许这样做,请使用FIND_IN_SET【参考方案4】:

这不使用 IN 子句,但它应该做你需要的:

Select *
from table
where
  CONCAT(',', (Select city from locations where e_Id=?), ',')
  LIKE
  CONCAT('%,', e_ID, ',%')

但您必须确保e_ID 不包含任何逗号或任何欢乐字符。

例如

CONCAT(',', '6,7,8,16,21,2', ',') returns ',6,7,8,16,21,2,'

e_ID=1  --> ',6,7,8,16,21,2,' LIKE '%,1,%'  ? FALSE
e_ID=6  --> ',6,7,8,16,21,2,' LIKE '%,6,%'  ? TRUE
e_ID=21 --> ',6,7,8,16,21,2,' LIKE '%,21,%' ? TRUE
e_ID=2  --> ',6,7,8,16,21,2,' LIKE '%,2,%'  ? TRUE
e_ID=3  --> ',6,7,8,16,21,2,' LIKE '%,3,%'  ? FALSE
etc.

【讨论】:

yes ... 将比较字段从 int 转换为字符串,并使用 like。这行得通。我知道问题是由糟糕的数据库设计引起的,但不幸的是,有时我们不得不处理这种糟糕的数据库设计,有时没有足够的时间对其进行重新设计。【参考方案5】:

不知道这是否是你想要完成的。使用 MySQL 有一个功能可以连接组中的值 GROUP_CONCAT

你可以试试这样的:

select * from table where e_ID in (Select GROUP_CONCAT(city SEPARATOR ',') from locations where e_Id=?)

【讨论】:

【参考方案6】:

这个for oracle ..这里的字符串连接是由wm_concat完成的

select * from table where e_ID in (Select wm_concat(city) from locations where e_Id=?)

是的,我同意 raheel shan 的观点。为了将这个“in”子句放入,我们需要将该列放入代码下面的行中,以便完成这项工作。

select * from table  where to_char(e_ID) 
in (
  select substr(city,instr(city,',',1,rownum)+1,instr(city,',',1,rownum+1)-instr(city,',',1,rownum)-1) from 
  (
  select ','||WM_CONCAT(city)||',' city,length(WM_CONCAT(city))-length(replace(WM_CONCAT(city),','))+1 CNT from locations where e_Id=? ) TST 
  ,ALL_OBJECTS OBJ where TST.CNT>=rownum
    ) ;

【讨论】:

【参考方案7】:

你应该使用

FIND_IN_SET返回值在逗号分隔值字符串中的位置

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

【讨论】:

【参考方案8】:

您需要“拆分”城市列值。它会是这样的:

SELECT *
  FROM table
 WHERE e_ID IN (SELECT TO_NUMBER(
                                 SPLIT_STR(city /*string*/
                                           , ',' /*delimiter*/
                                           , 1 /*start_position*/
                                           )
                                 )
                  FROM locations);

您可以在此处阅读有关 MySQL split_str 函数的更多信息:http://blog.fedecarg.com/2009/02/22/mysql-split-string-function/

另外,我在这里使用了 Oracle 的 TO_NUMBER 函数。请用适当的 MySQL 函数替换它。

【讨论】:

【参考方案9】:

IN 需要行,因此使用逗号分隔的列进行搜索不会满足您的要求,但如果您提供这样的数据('1'、'2'、'3'),这将起作用,但您不能像这样保存数据在您的字段中,无论您在列中插入什么,它都会将整个内容作为一个字符串。

【讨论】:

【参考方案10】:

您可以像this一样动态创建准备好的语句

set @sql = concat('select * from city where city_id in (',
                  (select cities from location where location_id = 3),
                  ')');
prepare in_stmt from @sql;
execute in_stmt;
deallocate prepare in_stmt;

【讨论】:

【参考方案11】:

参考:Use a comma-separated string in an IN () in MySQL

最近我遇到了同样的问题,这就是我解决它的方法。

它对我有用,希望这就是你想要的。

select * from table_name t where (select (CONCAT(',',(Select city from locations l where l.e_Id=?),',')) as city_string) LIKE CONCAT('%,',t.e_ID,',%');

示例: 看起来像这样

select * from table_name t where ',6,7,8,16,21,2,' LIKE '%,2,%';

【讨论】:

以上是关于MySQL“IN”子句中的逗号分隔值的主要内容,如果未能解决你的问题,请参考以下文章

如何转换逗号分隔的 varchar 以用于 pl/sql 中的“IN”子句?

在 Visual Studio 2008 中使用设计器将逗号分隔列表作为参数传递给 db2 查询的 IN 子句

MySql - 添加逗号分隔符

按mysql中select语句“in”子句中的值顺序排序

SQL IN 逗号分隔参数与内部查询

如何在选择语句的“NOT IN”子句中使用逗号分隔的字符串列表作为 pl/sql 存储的函数参数