我可以解决这个纯粹的MySQL? (在一列中join';'分隔值)

长话短说:我需要把几张表格中的数据放在一起,为了不必画出一张大桌子,我简化了它们。

我需要在一个查询中这样做,而且我不能使用PHP或任何其他语言来处理结果。 (如果我可以简单地使用我的方法,我会使用PHP)

这不会是一个问题,如果我有一个链接表连接t1行到t2,但不幸的是我不能也不能引入一个。

User table: (alias t1) user(varchar 150),resources(varchar 250) +-------+-------+ | user1 | 1;2;4 | +-------+-------+ | user2 | 2 | +-------+-------+ | user3 | 3;4 | +-------+-------+ Resources table: (alias t2) id(int 11 AI), data(text) +---+-------+ | 1 | data1 | +---+-------+ | 2 | data2 | +---+-------+ | 3 | data3 | +---+-------+ | 4 | data4 | +---+-------+ | 5 | data5 | +---+-------+ 

多个用户可以连接到相同的资源,用户可以访问一个或多个资源。

我想结果接近:

 user,data +-------+-------+ | user1 | data1 | +-------+-------+ | user1 | data2 | +-------+-------+ | user1 | data4 | +-------+-------+ | user2 | data2 | +-------+-------+ 

….等等

我有基本的MySQL知识,但是这个是我的知识范围。 有什么办法可以内部连接t2?

在这篇文章之前我读过的线程: 如何在连接字段中使用逗号分隔列表连接两个表

mysql用逗号分隔的ID连接两个表

如果user_resources (t1)是每个user => resource组合的“标准化表”,那么获取答案的查询就像将表连接在一起一样简单。

唉,它的resources列是非denormalized的:'资源ID列表'分隔';' 字符。

如果我们可以将“资源”列转换成行,那么当表连接变得简单时,很多困难就消失了。

生成输出的查询要求:

 SELECT user_resource.user, resource.data FROM user_resource JOIN integerseries AS isequence ON isequence.id <= COUNT_IN_SET(user_resource.resources, ';') /* normalize */ JOIN resource ON resource.id = VALUE_IN_SET(user_resource.resources, ';', isequence.id) ORDER BY user_resource.user, resource.data 

输出:

 user data ---------- -------- sampleuser abcde sampleuser azerty sampleuser qwerty stacky qwerty testuser abcde testuser azerty 

怎么样:

“诀窍”是有一个表格,其中包含从1到某些限制的数字。 我把它叫做integerseries 。 它可以用来转换'水平'的东西,如: ';' delimited strings ';' delimited strings rows

这样做的方式是当你与integerseries “连接”时,你正在做一个cross join ,这是“内部连接”自然发生的事情。

每一行都使用一个不同的“序列号”来复制,这个序列号是我们在列表中用作“资源”索引的integerseries

这个想法是:

  • 统计列表中的项目数量。
  • 根据列表中的位置提取每个项目。
  • 使用integerseries将一行转换为一组行提取用户的个人“资源ID”。 我们一起走的resources

我决定使用两个function:

  • 给定“分隔string列表”和“索引”的函数将返回列表中位置的值。 我称之为: VALUE_IN_SET 。 即给定“A; B; C”和“索引”为2,则返回“B”。

  • 给定“分隔string列表”的函数将返回列表中项目数量的计数。 我把它称为: COUNT_IN_SET 。 即给定'A; B; C'将返回3

事实certificate,这两个函数和integerseries应该提供一个通用的解决scheme来delimited items list in a column

它工作吗?

';' delimited string in column创build“规范化”表的查询 ';' delimited string in column 。 它显示了所有的列,包括由于'cross_join'( isequence.id as resources_index )生成的值:

 SELECT user_resource.user, user_resource.resources, COUNT_IN_SET(user_resource.resources, ';') AS resources_count, isequence.id AS resources_index, VALUE_IN_SET(user_resource.resources, ';', isequence.id) AS resources_value FROM user_resource JOIN integerseries AS isequence ON isequence.id <= COUNT_IN_SET(user_resource.resources, ';') ORDER BY user_resource.user, isequence.id 

“规范化”的表格输出:

 user resources resources_count resources_index resources_value ---------- --------- --------------- --------------- ----------------- sampleuser 1;2;3 3 1 1 sampleuser 1;2;3 3 2 2 sampleuser 1;2;3 3 3 3 stacky 2 1 1 2 testuser 1;3 2 1 1 testuser 1;3 2 2 3 

使用上面的“标准化” user_resources表,这是一个简单的连接提供所需的输出:

需要的function这些是可以在任何地方使用的一般function

注意:这些函数的名字与mysql的FIND_IN_SET函数有关 。 即他们在string列表方面做了类似的事情?

COUNT_IN_SET函数:返回列中character delimited items的计数。

 DELIMITER $$ DROP FUNCTION IF EXISTS `COUNT_IN_SET`$$ CREATE FUNCTION `COUNT_IN_SET`(haystack VARCHAR(1024), delim CHAR(1) ) RETURNS INTEGER BEGIN RETURN CHAR_LENGTH(haystack) - CHAR_LENGTH( REPLACE(haystack, delim, '')) + 1; END$$ DELIMITER ; 

VALUE_IN_SET函数:将delimited list视为one based array并返回给定“索引”处的值。

 DELIMITER $$ DROP FUNCTION IF EXISTS `VALUE_IN_SET`$$ CREATE FUNCTION `VALUE_IN_SET`(haystack VARCHAR(1024), delim CHAR(1), which INTEGER ) RETURNS VARCHAR(255) CHARSET utf8 COLLATE utf8_unicode_ci BEGIN RETURN SUBSTRING_INDEX(SUBSTRING_INDEX(haystack, delim, which), delim, -1); END$$ DELIMITER ; 

相关信息:

  • 最后解决了如何获得SQLFiddle工作代码来编译函数。

  • 有一个这样的版本,适用于SQLite数据库以及SQLite – 规范化拼接字段,并join它?

表格(含数据):

 CREATE TABLE `integerseries` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=500 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; /*Data for the table `integerseries` */ insert into `integerseries`(`id`) values (1); insert into `integerseries`(`id`) values (2); insert into `integerseries`(`id`) values (3); insert into `integerseries`(`id`) values (4); insert into `integerseries`(`id`) values (5); insert into `integerseries`(`id`) values (6); insert into `integerseries`(`id`) values (7); insert into `integerseries`(`id`) values (8); insert into `integerseries`(`id`) values (9); insert into `integerseries`(`id`) values (10); 

资源:

 CREATE TABLE `resource` ( `id` int(11) NOT NULL, `data` varchar(250) COLLATE utf8_unicode_ci DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; /*Data for the table `resource` */ insert into `resource`(`id`,`data`) values (1,'abcde'); insert into `resource`(`id`,`data`) values (2,'qwerty'); insert into `resource`(`id`,`data`) values (3,'azerty'); 

User_resource:

 CREATE TABLE `user_resource` ( `user` varchar(50) COLLATE utf8_unicode_ci NOT NULL, `resources` varchar(250) COLLATE utf8_unicode_ci DEFAULT NULL, PRIMARY KEY (`user`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; /*Data for the table `user_resource` */ insert into `user_resource`(`user`,`resources`) values ('sampleuser','1;2;3'); insert into `user_resource`(`user`,`resources`) values ('stacky','3'); insert into `user_resource`(`user`,`resources`) values ('testuser','1;3'); 

如果你更换;,你可以使用FIND_IN_SET函数来join你的表格:

 select u.user, r.data from User u join Resources r on find_in_set(r.id, replace(u.resources, ';', ',')) order by u.user, r.id 

结果:

 | user | data | |-------|-------| | user1 | data1 | | user1 | data2 | | user1 | data4 | | user2 | data2 | | user3 | data3 | | user3 | data4 | 

http://sqlfiddle.com/#!9/a0792b/5