我可以解决这个纯粹的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 |