当前位置:  开发笔记 > 编程语言 > 正文

你可以拆分/爆炸MySQL查询中的字段吗?

如何解决《你可以拆分/爆炸MySQL查询中的字段吗?》经验,为你挑选了5个好方法。

我必须创建一些学生完成报告.每个学生都属于一个客户.以下是表格(此问题已简化).

CREATE TABLE  `clients` (
  `clientId` int(10) unsigned NOT NULL auto_increment,
  `clientName` varchar(100) NOT NULL default '',
  `courseNames` varchar(255) NOT NULL default ''
)

courseNames字段包含以逗号分隔的课程名称字符串,例如"AB01,AB02,AB03"

CREATE TABLE  `clientenrols` (
  `clientEnrolId` int(10) unsigned NOT NULL auto_increment,
  `studentId` int(10) unsigned NOT NULL default '0',
  `courseId` tinyint(3) unsigned NOT NULL default '0'
)

courseId此处的字段是clients.courseNames字段中课程名称的索引.因此,如果客户courseNames是"AB01,AB02,AB03",并且courseId注册的是2,则学生在AB03.

有没有办法可以在包含课程名称的这些表上进行单一选择?请记住,将有来自不同客户的学生(因此具有不同的课程名称,并非所有课程名称都是连续的,例如:"NW01,NW03")

基本上,如果我可以拆分该字段并从结果数组中返回单个元素,那将是我正在寻找的.这就是我在魔法伪代码中的意思:

SELECT e.`studentId`, SPLIT(",", c.`courseNames`)[e.`courseId`]
FROM ...

小智.. 30

到现在为止,我想在我的SQL数据库中保留这些以逗号分隔的列表 - 充分了解所有警告!

我一直认为它们比查找表(它提供了一种规范化数据库的方法)有好处.经过几天的拒绝,我看到了光明:

在一个字段中使用逗号分隔值时,使用查找表不会导致比那些丑陋的字符串操作更多的代码.

查找表允许本机数字格式,因此不大于那些csv字段.虽然它更小.

涉及的字符串操作在高级语言代码(SQL和PHP)中很小,但与使用整数数组相比却很昂贵.

数据库并不意味着人类可读,并且由于其可读性/直接可编辑性而试图坚持结构是非常愚蠢的,正如我所做的那样.

简而言之,MySQL中没有原生SPLIT()函数是有原因的.



1> 小智..:

到现在为止,我想在我的SQL数据库中保留这些以逗号分隔的列表 - 充分了解所有警告!

我一直认为它们比查找表(它提供了一种规范化数据库的方法)有好处.经过几天的拒绝,我看到了光明:

在一个字段中使用逗号分隔值时,使用查找表不会导致比那些丑陋的字符串操作更多的代码.

查找表允许本机数字格式,因此不大于那些csv字段.虽然它更小.

涉及的字符串操作在高级语言代码(SQL和PHP)中很小,但与使用整数数组相比却很昂贵.

数据库并不意味着人类可读,并且由于其可读性/直接可编辑性而试图坚持结构是非常愚蠢的,正如我所做的那样.

简而言之,MySQL中没有原生SPLIT()函数是有原因的.


是的,好的,**但是**:_my_想要一个SPLIT()去聚合函数的目的是规范化当前未规范化的数据,以便将来可以将它们保持为标准化格式!如果您的评论是正确的,那么MySQL缺少此功能会阻碍我在规范数据方面的进展!如果MySQL提供**GROUP_CONCAT()**函数,而不是相应的反向:SPLIT()函数,这是非常奇怪和不对称的!看起来我将不得不与SUBSTRING_INDEX()一起破解"解决方案"!

2> eithed..:

看到这是一个相当受欢迎的问题 - 答案是肯定的.

对于包含所有逗号分隔值的column表格table中的列:

CREATE TEMPORARY TABLE temp (val CHAR(255));
SET @S1 = CONCAT("INSERT INTO temp (val) VALUES ('",REPLACE((SELECT GROUP_CONCAT( DISTINCT  `column`) AS data FROM `table`), ",", "'),('"),"');");
PREPARE stmt1 FROM @s1;
EXECUTE stmt1;
SELECT DISTINCT(val) FROM temp;

但请记住,不要在您的数据库中存储CSV


Per @Mark Amery - 因为这会将昏迷分隔的值转换为INSERT语句,所以在未经过数据处理的数据上运行它时要小心


重申一下,请不要将CSV存储在数据库中; 此功能旨在将CSV转换为合理的数据库结构,而不是在代码中的任何位置使用.如果您必须在生产中使用它,请重新考虑您的数据库结构


-1; 好主,这种疯狂是什么?如果`table`中的值包含引号,这可能会失败,可能会让您接受SQL注入.

3> Josias Iquab..:

您可以为此创建一个函数:

/**
* Split a string by string (Similar to the php function explode())
*
* @param VARCHAR(12) delim The boundary string (delimiter).
* @param VARCHAR(255) str The input string.
* @param INT pos The index of the string to return
* @return VARCHAR(255) The (pos)th substring
* @return VARCHAR(255) Returns the [pos]th string created by splitting the str parameter on boundaries formed by the delimiter.
* @{@example
*     SELECT SPLIT_STRING('|', 'one|two|three|four', 1);
*     This query
* }
*/
DROP FUNCTION IF EXISTS SPLIT_STRING;
CREATE FUNCTION SPLIT_STRING(delim VARCHAR(12), str VARCHAR(255), pos INT)
RETURNS VARCHAR(255) DETERMINISTIC
RETURN
    REPLACE(
        SUBSTRING(
            SUBSTRING_INDEX(str, delim, pos),
            LENGTH(SUBSTRING_INDEX(str, delim, pos-1)) + 1
        ),
        delim, ''
    );

将魔法伪代码转换为使用此代码,您将拥有:

SELECT e.`studentId`, SPLIT_STRING(',', c.`courseNames`, e.`courseId`)
FROM...



4> DarkSide..:

根据上面的Alex回答(/sf/ask/17360801/),我提出了更好的解决方案.解决方案不包含确切的一个记录ID.

假设逗号分隔列表在表中data.list,并且它包含来自其他表的代码列表classification.code,您可以执行以下操作:

SELECT 
    d.id, d.list, c.code
FROM 
    classification c
    JOIN data d
        ON d.list REGEXP CONCAT('[[:<:]]', c.code, '[[:>:]]');

所以如果你有这样的表和数据:

CLASSIFICATION (code varchar(4) unique): ('A'), ('B'), ('C'), ('D')
MY_DATA (id int, list varchar(255)): (100, 'C,A,B'), (150, 'B,A,D'), (200,'B')

以上SELECT将返回

(100, 'C,A,B', 'A'),
(100, 'C,A,B', 'B'),
(100, 'C,A,B', 'C'),
(150, 'B,A,D', 'A'),
(150, 'B,A,D', 'B'),
(150, 'B,A,D', 'D'),
(200, 'B', 'B'),


在MySQL中,如果它是逗号分隔的字段,那么使用FIND_IN_SET而不是连接的正则表达式可能会更有效.

5> Mark Amery..:

MySQL唯一的字符串分割功能是SUBSTRING_INDEX(str, delim, count).你可以使用它,例如:

在字符串中的第一个分隔符之前返回该项:

mysql> SELECT SUBSTRING_INDEX('foo#bar#baz#qux', '#', 1);
+--------------------------------------------+
| SUBSTRING_INDEX('foo#bar#baz#qux', '#', 1) |
+--------------------------------------------+
| foo                                        |
+--------------------------------------------+
1 row in set (0.00 sec)

在字符串中的最后一个分隔符后返回该项:

mysql> SELECT SUBSTRING_INDEX('foo#bar#baz#qux', '#', -1);
+---------------------------------------------+
| SUBSTRING_INDEX('foo#bar#baz#qux', '#', -1) |
+---------------------------------------------+
| qux                                         |
+---------------------------------------------+
1 row in set (0.00 sec)

返回字符串中第三个分隔符之前的所有内容:

mysql> SELECT SUBSTRING_INDEX('foo#bar#baz#qux', '#', 3);
+--------------------------------------------+
| SUBSTRING_INDEX('foo#bar#baz#qux', '#', 3) |
+--------------------------------------------+
| foo#bar#baz                                |
+--------------------------------------------+
1 row in set (0.00 sec)

通过链接两个调用返回字符串中的第二项:

mysql> SELECT SUBSTRING_INDEX(SUBSTRING_INDEX('foo#bar#baz#qux', '#', 2), '#', -1);
+----------------------------------------------------------------------+
| SUBSTRING_INDEX(SUBSTRING_INDEX('foo#bar#baz#qux', '#', 2), '#', -1) |
+----------------------------------------------------------------------+
| bar                                                                  |
+----------------------------------------------------------------------+
1 row in set (0.00 sec)

通常,获取#分离字符串的第n个元素的一种简单方法(假设您确定它至少具有n个元素)是:

SUBSTRING_INDEX(SUBSTRING_INDEX(your_string, '#', n), '#', -1);

内部SUBSTRING_INDEX调用将丢弃第n个分隔符及其后的所有内容,然后外部SUBSTRING_INDEX调用将丢弃除剩余的最终元素之外的所有内容.

如果你想要一个更健壮的解决方案,NULL如果你要求一个不存在的元素(例如,要求第五个元素'a#b#c#d'),则返回,那么你可以计算分隔符REPLACE,然后NULL使用条件返回IF():

IF(
    LENGTH(your_string) - LENGTH(REPLACE(your_string, '#', '')) / LENGTH('#') < n - 1,
    NULL,
    SUBSTRING_INDEX(SUBSTRING_INDEX(your_string, '#', n), '#', -1)
)

当然,这非常难看,难以理解!所以你可能想把它包装在一个函数中:

CREATE FUNCTION split(string TEXT, delimiter TEXT, n INT)
RETURNS TEXT DETERMINISTIC
RETURN IF(
    (LENGTH(string) - LENGTH(REPLACE(string, delimiter, ''))) / LENGTH(delimiter) < n - 1,
    NULL,
    SUBSTRING_INDEX(SUBSTRING_INDEX(string, delimiter, n), delimiter, -1)
);

然后你可以使用这样的函数:

mysql> SELECT SPLIT('foo,bar,baz,qux', ',', 3);
+----------------------------------+
| SPLIT('foo,bar,baz,qux', ',', 3) |
+----------------------------------+
| baz                              |
+----------------------------------+
1 row in set (0.00 sec)

mysql> SELECT SPLIT('foo,bar,baz,qux', ',', 5);
+----------------------------------+
| SPLIT('foo,bar,baz,qux', ',', 5) |
+----------------------------------+
| NULL                             |
+----------------------------------+
1 row in set (0.00 sec)

mysql> SELECT SPLIT('foo###bar###baz###qux', '###', 2);
+------------------------------------------+
| SPLIT('foo###bar###baz###qux', '###', 2) |
+------------------------------------------+
| bar                                      |
+------------------------------------------+
1 row in set (0.00 sec)

推荐阅读
路人甲
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有