我可以用纯mysql解决这个问题吗? (加入“;”列中的分隔值)
Posted
技术标签:
【中文标题】我可以用纯mysql解决这个问题吗? (加入“;”列中的分隔值)【英文标题】:Can I resolve this with pure mysql? (joining on ';' separated values in a column) 【发布时间】:2015-11-18 14:32:36 【问题描述】:长话短说:我有几个表格中的数据需要一起抓取,我已经简化了它们,以便不必绘制一个巨大的表格。
我需要在一个查询中执行此操作,并且我不能使用 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 吗?
在发这篇文章之前我读过的帖子: How to join two tables using a comma-separated-list in the join field
mysql join two table with comma separated ids
【问题讨论】:
如果您可以通过使用子查询将user table (t1)
转换为“正确的表”,那么您可以在一个查询中解决它吗?如果是这样,问题就变成了将user1 | 1;2;4
转换为“行”的问题。这在标准 SQL 中是否可行。是的,如果你用一个连续的“整数”表加入它。我调用'整数'。在我测试它的时候,我最初会把它放在一个视图中。我还没有尝试过,但它应该可以工作。
如果您设置一个带有测试数据的SQLFiddle 会有所帮助。我想快速测试查询而不是设置表和测试数据。我做了类似的事情here
@RyanVincent ,今天晚些时候当我有一台可用的电脑时会这样做,目前我发表这篇文章 sqlfiddle 无法访问:(
@RyanVincent sqlfiddle.com/#!9/52d9b
整理并添加了一个有效的SQLFiddle。
【参考方案1】:
如果user_resources
(t1) 是一个“规范化表”,每个user => resource
组合对应一行,那么获取答案的查询将像joining
将表放在一起一样简单。
唉,denormalized
将 resources
列作为:“资源 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
到rows
。
其工作原理是,当您使用integerseries
“加入”时,您正在执行cross join
,这是使用“内部联接”“自然”发生的情况。
每一行都与integerseries
表中的不同“序列号”重复,我们将其用作列表中我们要用于row
的“资源”的“索引”。
这个想法是:
计算列表中的项目数。 根据其在列表中的位置提取每个项目。 使用integerseries
将一行转换为一组行,从user
.resources
中提取单个“资源ID”。
我决定使用两个函数:
给定“分隔字符串列表”和“索引”的函数将返回列表中位置的值。我称之为:VALUE_IN_SET
。即给定 'A;B;C' 和 'index' 2 然后它返回 'B'。
给定“分隔字符串列表”的函数将返回列表中项目数的计数。我称之为:COUNT_IN_SET
。即给定 'A;B;C' 将返回 3
事实证明,这两个函数和integerseries
应该为delimited items list in a column
提供通用解决方案。
有用吗?
从';' 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
表,它是一个简单的连接来提供所需的输出:
需要的功能(这些是通用功能,可以在任何地方使用)
注意:这些函数的名称与mysqlFIND_IN_SET function相关。即他们在字符串列表方面做类似的事情?
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 - working code编译函数。
有一个版本适用于 SQLite
数据库以及 SQLite- Normalizing a concatenated field and joining with it?
表格(有数据):
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');
用户资源:
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');
【讨论】:
我已经用函数重做了这个,它更整洁了。将很快发布更新。 感谢您在这里为帮助我付出的巨大努力 :) 视图解决方案足以让我现在完成工作,但我真的很期待您的其他解决方案,也用于学习目的。如果您将此作为单独的答案发布,我可以投票。【参考方案2】:如果您将 ;
替换为 ,
,您可以使用 FIND_IN_SET 函数来加入您的表:
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
【讨论】:
以上是关于我可以用纯mysql解决这个问题吗? (加入“;”列中的分隔值)的主要内容,如果未能解决你的问题,请参考以下文章