我必须创建一些学生完成报告.每个学生都属于一个客户.以下是表格(此问题已简化).
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()函数是有原因的.
到现在为止,我想在我的SQL数据库中保留这些以逗号分隔的列表 - 充分了解所有警告!
我一直认为它们比查找表(它提供了一种规范化数据库的方法)有好处.经过几天的拒绝,我看到了光明:
在一个字段中使用逗号分隔值时,使用查找表不会导致比那些丑陋的字符串操作更多的代码.
查找表允许本机数字格式,因此不大于那些csv字段.虽然它更小.
涉及的字符串操作在高级语言代码(SQL和PHP)中很小,但与使用整数数组相比却很昂贵.
数据库并不意味着人类可读,并且由于其可读性/直接可编辑性而试图坚持结构是非常愚蠢的,正如我所做的那样.
简而言之,MySQL中没有原生SPLIT()函数是有原因的.
看到这是一个相当受欢迎的问题 - 答案是肯定的.
对于包含所有逗号分隔值的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转换为合理的数据库结构,而不是在代码中的任何位置使用.如果您必须在生产中使用它,请重新考虑您的数据库结构
您可以为此创建一个函数:
/** * 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...
根据上面的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唯一的字符串分割功能是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)