【问题标题】:Can you split/explode a field in a MySQL query?您可以拆分/分解 MySQL 查询中的字段吗?
【发布时间】:2010-10-03 01:59:29
【问题描述】:

我必须针对一些学生的完成情况创建一份报告。每个学生都属于一个客户。以下是表格(针对此问题进行了简化)。

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”,而注册的courseId2,那么学生在AB03。

有没有一种方法可以让我在这些包含课程名称的表格上进行单选?请记住,会有来自不同客户的学生(因此有不同的课程名称,并非所有名称都是连续的,例如:“NW01,NW03”)

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

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

【问题讨论】:

    标签: mysql


    【解决方案1】:

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

    我一直认为它们优于查找表(它提供了一种标准化数据库的方法)。经过几天的拒绝,我看到了曙光

    • 在一个字段中使用逗号分隔值时,使用查找表不会导致比那些丑陋的字符串操作更多的代码。
    • 查找表允许本地数字格式,因此不会比那些 csv 字段大。但它更小。
    • 在高级语言代码(SQL 和 PHP)中所涉及的字符串操作很少,但与使用整数数组相比成本很高。
    • 数据库并不是为了人类可读的,而且像我一样,由于它们的可读性/直接可编辑性而试图坚持结构是很愚蠢的。

    总之,MySQL中没有原生的SPLIT()函数是有原因的。

    【讨论】:

    • 当用户过滤自己输入的数据时,我使用它来工作。即,当您有“已接受的扩展”之类的未决问题时,用户需要过滤条件。为了保存相关数据,最好的方法是 - 正如你所说 - 使用单独的相关表
    • 没有办法将数组传递给mysql存储的例程......所以如果没有办法在例程中传递文本和拆分,我现在该怎么办?! (
    • 是的,好的,然而我的想要一个 SPLIT() 去聚合函数的目的是规范化当前未规范化的数据,这样我以后就可以将它们保持为标准化格式!如果您的评论是正确的,那么 MySQL 缺少此功能正在阻碍我在规范化数据方面的进展!如果 MySQL 提供了一个 GROUP_CONCAT() 函数,但没有相应的逆:SPLIT() 函数,那就太奇怪和不对称了!看来我必须将“解决方案”与 SUBSTRING_INDEX() 一起搞砸!
    • 将 MySQL 的这个特性集解读为:如果你的数据在 base 中,那就太迟了。通过设计 /before/ 插入来处理标准化。 /reading/ 数据的规范化与我们无关。同样,这正是我从中学到的。
    • “为什么 MySQL 中没有原生 SPLIT() 函数” 我希望答案是因为函数返回单个值。并且根据定义,Split() 返回多个值(传统上是一个数组)。也许是一个存储过程?但是你需要对输出做一些事情......
    【解决方案2】:

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

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

    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 存储在您的数据库中


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


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

    【讨论】:

    • 我有一个包含管道分隔数据(而不是逗号)的可怕列,但是您使用的括号组合以及逗号被替换的事实使得第二行很难分解,我不能用管道而不是逗号替换来重写它。
    • @BadHorsie - SET @S1 = CONCAT("INSERT INTO temp (val) VALUES ('",REPLACE((SELECT GROUP_CONCAT( DISTINCT `column`) AS data FROM `table`), "|", "'),('"),"');");
    • 谢谢,我成功了。在 Stackoverflow 的语法高亮中更容易看到。 phpMyAdmin 不是很好的语法高亮。
    • -1;天哪,这是什么疯子?如果table 中的值包含引号,这将失败,可能会将您暴露于 SQL 注入。
    • @MarkAmery 您可以在 REPLACE 之前使用 QUOTE(column) 并删除第一个和最后一个单引号。见example
    【解决方案3】:

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

    /**
    * 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...
    

    【讨论】:

    • 请注意,上述代码不适用于 UTF-8 等多字节字符。我认为使用 CHAR_LENGTH 而不是 LENGTH 应该可以解决问题。
    【解决方案4】:

    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 调用丢弃除最后一个元素之外的所有内容。

    如果您想要一个更强大的解决方案,如果您要求一个不存在的元素(例如,要求 'a#b#c#d' 的第 5 个元素)返回 NULL,那么您可以 count the delimiters using REPLACE 然后有条件地返回使用IF()返回NULL

    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)
    

    【讨论】:

      【解决方案5】:

      基于上面 Alex 的回答 (https://stackoverflow.com/a/11022431/1466341),我想出了更好的解决方案。不包含确切的一条记录 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'),
      

      【讨论】:

      • 编辑为不使用 ',?'在正则表达式中,但使用单词边界检查[[:&lt;:]]word[[:&gt;:]]。否则,它可以从值“WEB”中提取多个值 - 例如 E 或 B 或 EB 或 WE(任何组合)。
      • 在 MySQL 中,如果它是一个逗号分隔的字段,那么使用 FIND_IN_SET 而不是正则表达式进行连接可能会更有效。
      • @Kickstart 可能您是对的,但您能否使用 FIND_IN_SET 发布完整的 SELECT 查询以完成您的评论?
      • 非常老的线程。但是您可以将其用于 ON 子句。 ON FIND_IN_SET(c.code, d.list) > 0
      【解决方案6】:

      在 Alwin Kesler 的解决方案的基础上,这里有一个更实用的现实示例。

      假设逗号分隔的列表在 my_table.list 中,并且它是 my_other_table.id 的 ID 列表,您可以执行以下操作:

      SELECT 
          * 
      FROM 
          my_other_table 
      WHERE 
          (SELECT list FROM my_table WHERE id = '1234') REGEXP CONCAT(',?', my_other_table.id, ',?');
      

      【讨论】:

      • 不错的解决方案,但我们如何摆脱 id='1234' 参数?我的意思是,我想为 my_table 的所有记录提取值,而不仅仅是一个。
      【解决方案7】:

      我已经用正则表达式模式解决了这种问题。它们往往比常规查询慢,但它是一种在逗号分隔的查询列中检索数据的简单方法

      SELECT * 
      FROM `TABLE`
      WHERE `field` REGEXP ',?[SEARCHED-VALUE],?';
      

      贪心问号有助于在字符串的开头或结尾进行搜索。

      希望对未来的任何人有所帮助

      【讨论】:

        【解决方案8】:

        可以在 MySQL SELECT 语句中分解字符串。

        首先生成一系列数字,直到您希望分解的最大分隔值数量。要么来自整数表,要么将数字联合在一起。下面生成 100 行,给出 1 到 100 的值。它可以很容易地扩展以给出更大的范围(添加另一个子查询,为数百个给出值 0 到 9 - 因此给出 0 到 999 等)。

        SELECT 1 + units.i + tens.i * 10 AS aNum
        FROM (SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) units
        CROSS JOIN (SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) tens
        

        这可以与您的表交叉连接以提供值。请注意,您使用 SUBSTRING_INDEX 将分隔值获取到某个值,然后使用 SUBSTRING_INDEX 获取该值,不包括以前的值。

        SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(clients.courseNames, ',', sub0.aNum), ',', -1) AS a_course_name
        FROM clients
        CROSS JOIN
        (
            SELECT 1 + units.i + tens.i * 10 AS aNum, units.i + tens.i * 10 AS aSubscript
            FROM (SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) units
            CROSS JOIN (SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) tens
        ) sub0
        

        如您所见,这里有一个小问题,即最后一个分隔值重复了很多次。要摆脱这种情况,您需要根据有多少定界符来限制数字的范围。这可以通过获取定界字段的长度并将其与定界字段的长度进行比较来完成,其中定界符更改为“”(以删除它们)。从中可以得到分隔符的数量:-

        SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(clients.courseNames, ',', sub0.aNum), ',', -1) AS a_course_name
        FROM clients
        INNER JOIN
        (
            SELECT 1 + units.i + tens.i * 10 AS aNum
            FROM (SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) units
            CROSS JOIN (SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) tens
        ) sub0
        ON (1 + LENGTH(clients.courseNames) - LENGTH(REPLACE(clients.courseNames, ',', ''))) >= sub0.aNum
        

        在原始示例字段中,您可以(例如)基于此计算每门课程的学生人数。请注意,我已更改获取数字范围的子查询以带回 2 个数字,1 用于确定课程名称(因为这些基于从 1 开始),另一个获取下标(因为它们基于开始在 0)。

        SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(clients.courseNames, ',', sub0.aNum), ',', -1) AS a_course_name, COUNT(clientenrols.studentId)
        FROM clients
        INNER JOIN
        (
            SELECT 1 + units.i + tens.i * 10 AS aNum, units.i + tens.i * 10 AS aSubscript
            FROM (SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) units
            CROSS JOIN (SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) tens
        ) sub0
        ON (1 + LENGTH(clients.courseNames) - LENGTH(REPLACE(clients.courseNames, ',', ''))) >= sub0.aNum
        LEFT OUTER JOIN clientenrols
        ON clientenrols.courseId = sub0.aSubscript
        GROUP BY a_course_name
        

        如您所见,这是可能的,但非常混乱。而且几乎没有机会使用索引,它不会是有效的。此外,范围必须处理最大数量的分隔值,并通过排除大量重复来工作;如果分隔值的最大数量非常大,那么这将大大减慢速度。总的来说,适当地规范化数据库通常要好得多。

        【讨论】:

          【解决方案9】:

          有一个更简单的方法,有一个链接表,即:

          表 1:客户、客户信息、等等等等

          表 2:课程、课程信息、等等

          表3:clientid、courseid

          然后进行 JOIN,然后您就可以参加比赛了。

          【讨论】:

          • 是的,我现在痛苦地意识到使用逗号分隔列表是多么糟糕的决定。
          • 重新设计永远不会太晚 :-)
          【解决方案10】:
          SELECT
            tab1.std_name, tab1.stdCode, tab1.payment,
            SUBSTRING_INDEX(tab1.payment, '|', 1) as rupees,
            SUBSTRING(tab1.payment, LENGTH(SUBSTRING_INDEX(tab1.payment, '|', 1)) + 2,LENGTH(SUBSTRING_INDEX(tab1.payment, '|', 2))) as date
          FROM (
            SELECT DISTINCT
              si.std_name, hfc.stdCode,
              if(isnull(hfc.payDate), concat(hfc.coutionMoneyIn,'|', year(hfc.startDtae), '-',  monthname(hfc.startDtae)), concat(hfc.payMoney, '|', monthname(hfc.payDate), '-', year(hfc.payDate))) AS payment
            FROM hostelfeescollection hfc
            INNER JOIN hostelfeecollectmode hfm ON hfc.tranId = hfm.tranId
            INNER JOIN student_info_1 si ON si.std_code = hfc.stdCode
            WHERE hfc.tranId = 'TRAN-AZZZY69454'
          ) AS tab1
          

          【讨论】:

            【解决方案11】:

            如果您需要从带有分隔符的字符串中获取表格:

            SET @str = 'function1;function2;function3;function4;aaa;bbbb;nnnnn';
            SET @delimeter = ';';
            SET @sql_statement = CONCAT('SELECT '''
                            ,REPLACE(@str, @delimeter, ''' UNION ALL SELECT ''')
                            ,'''');
            SELECT @sql_statement;
            SELECT 'function1' UNION ALL SELECT 'function2' UNION ALL SELECT 'function3' UNION ALL SELECT 'function4' UNION ALL SELECT 'aaa' UNION ALL SELECT 'bbbb' UNION ALL SELECT 'nnnnn'
            

            【讨论】:

              【解决方案12】:

              我使用了上述逻辑,但稍作修改。 我的输入格式为: “apple:100|pinapple:200|orange:300”存储在变量@updtAdvanceKeyVal

              这里是功能块:

              set @res = "";
              
              set @i = 1;
              set @updtAdvanceKeyVal = updtAdvanceKeyVal;
              
              REPEAT
              
              
               -- set r =  replace(SUBSTRING(SUBSTRING_INDEX(@updtAdvanceKeyVal, "|", @i),
               --  LENGTH(SUBSTRING_INDEX(@updtAdvanceKeyVal, "|", @i -1)) + 1),"|","");
              
              -- wrapping the function in "replace" function as above causes to cut off a character from
               -- the 2nd splitted value if the value is more than 3 characters. Writing it in 2 lines causes no such problem and the output is as expected
              -- sample output by executing the above function :
              -- orange:100
              -- pi apple:200    !!!!!!!!strange output!!!!!!!!
              -- tomato:500
              
                    set @r =  SUBSTRING(SUBSTRING_INDEX(@updtAdvanceKeyVal, "|", @i),
                                LENGTH(SUBSTRING_INDEX(@updtAdvanceKeyVal, "|", @i -1)) + 1);
              
                    set @r = replace(@r,"|","");
              
                    if @r <> "" then
              
                            set @key = SUBSTRING_INDEX(@r, ":",1);
                            set @val = SUBSTRING_INDEX(@r, ":",-1);
              
                            select @key, @val;
                    end if;
              
                    set @i = @i + 1;
              
                   until @r = ""
              END REPEAT;
              

              【讨论】:

                【解决方案13】:

                我刚刚在一个类似的领域遇到了类似的问题,但我以不同的方式解决了这个问题。我的用例需要将这些 id 放在逗号分隔的列表中以用于连接。

                我能够使用like来解决它,但它变得更容易了,因为除了逗号分隔符之外,ID也被引用如下:

                keys "1","2","6","12"

                正因为如此,我才能点赞

                SELECT twwf.id, jtwi.id joined_id FROM table_with_weird_field twwf INNER JOIN join_table_with_ids jtwi ON twwf.delimited_field LIKE CONCAT("%\"", jtwi.id, "\"%")

                这基本上只是查看您尝试加入的表中的 id 是否出现在集合中,此时您可以很容易地加入它并返回您的记录。你也可以从这样的东西创建一个视图。

                它在我处理以所述方式管理关系的 Wordpress 插件的用例中效果很好。引号确实有帮助,因为否则您将面临部分匹配的风险(又名 - 18 内的 id 1 等)。

                【讨论】:

                  【解决方案14】:

                  在包含逗号分隔值的列中搜索

                  MySQL 有一个专用函数FIND_IN_SET(),如果在包含逗号分隔值的字符串中找到该值,则返回字段索引。

                  例如,以下语句在字符串A,B,C,D 中返回值C 的从一开始的索引。

                  SELECT FIND_IN_SET('C', 'A,B,C,D') AS result;
                  +--------+
                  | result |
                  +--------+
                  |      3 |
                  +--------+
                  

                  如果没有找到给定的值,FIND_IN_SET()函数返回0

                  SELECT FIND_IN_SET('Z', 'A,B,C,D') AS result;
                  +--------+
                  | result |
                  +--------+
                  |      0 |
                  +--------+
                  

                  【讨论】:

                    【解决方案15】:

                    您可以在较新的 MySQL 版本中使用 JSON 来执行此操作。这是一个爆炸。我们将快速准备创建一个数字表。然后首先我们创建一个中间表来将逗号分隔的字符串转换为 json 数组,然后我们将使用json_extract 将它们分开。我将字符串封装在引号中,小心地转义现有引号,因为我有分号分隔的包含逗号的字符串。

                    所以要创建数字表,希望您的客户多于课程,如果没有,请选择一个足够大的表。

                    CREATE TABLE numbers (n int PRIMARY KEY);
                    INSERT INTO numbers 
                    SELECT @row := @row + 1
                    FROM clients JOIN (select @row:=0) t2;
                    

                    如果您知道自己只有 50 门课程,请添加 LIMIT 50。现在,这很容易,不是吗?现在开始真正的工作,老实说,是引号使它更丑陋,但至少它更通用:

                    CREATE TABLE json_coursenames 
                    SELECT clientId,clientName,CONCAT('["', REPLACE(REPLACE(courseName,'"','\\"'), ',', '","'), '"]') AS a
                    FROM clients;
                    
                    CREATE TABLE extracted
                    SELECT clientId,clientName,REPLACE(TRIM(TRIM('"' FROM JSON_EXTRACT(a, concat('$[', n, ']')))), '\\"', '"')
                    FROM json_coursenames
                    INNER JOIN numbers ON n < JSON_LENGTH(a);
                    

                    哇!

                    这里的主要内容是这两个:CONCAT('["', REPLACE(coursename, ',', '","'), '"]')(我删除了第二个REPLACE 以使其更明显)会将foo,bar,bar 转换为"foo","bar","baz"。另一个技巧是JSON_EXTRACT(a, concat('$[', n, ']') 将变为JSON_EXTRACT(a, $[12]),这是数组中的第13 个元素,请参阅JSON Path syntax

                    【讨论】:

                      【解决方案16】:

                      以下是您为 SQL Server 执行此操作的方法。其他人可以将其翻译成 MySQL。 Parsing CSV Values Into Multiple Rows.

                      SELECT Author, 
                      NullIf(SubString(',' + Phrase + ',' , ID , CharIndex(',' , ',' + Phrase + ',' , ID) - ID) , '') AS Word 
                      FROM Tally, Quotes 
                      WHERE ID <= Len(',' + Phrase + ',') AND SubString(',' + Phrase + ',' , ID - 1, 1) = ',' 
                      AND CharIndex(',' , ',' + Phrase + ',' , ID) - ID > 0
                      

                      这个想法是交叉连接到包含整数 1 到 8000(或任何足够大的数字)的预定义表 Tally 并运行 SubString 以找到正确的单词位置。

                      【讨论】:

                        【解决方案17】:

                        这是我到目前为止所得到的(在提到的 Ben Alpert 页面上找到它):

                        SELECT REPLACE(
                            SUBSTRING(
                                SUBSTRING_INDEX(c.`courseNames`, ',', e.`courseId` + 1)
                                , LENGTH(SUBSTRING_INDEX(c.`courseNames`, ',', e.`courseId`)
                            ) + 1)
                            , ','
                            , ''
                        )
                        FROM `clients` c INNER JOIN `clientenrols` e USING (`clientId`)
                        

                        【讨论】:

                          【解决方案18】:

                          好吧,我用的都没有用,所以我决定创建一个真正简单的拆分函数,希望对您有所帮助:

                              DECLARE inipos INTEGER;
                              DECLARE endpos INTEGER;
                              DECLARE maxlen INTEGER;
                              DECLARE item VARCHAR(100);
                              DECLARE delim VARCHAR(1);
                          
                              SET delim = '|';
                              SET inipos = 1;
                              SET fullstr = CONCAT(fullstr, delim);
                              SET maxlen = LENGTH(fullstr);
                          
                              REPEAT
                                  SET endpos = LOCATE(delim, fullstr, inipos);
                                  SET item =  SUBSTR(fullstr, inipos, endpos - inipos);
                          
                                  IF item <> '' AND item IS NOT NULL THEN           
                                      USE_THE_ITEM_STRING;
                                  END IF;
                                  SET inipos = endpos + 1;
                              UNTIL inipos >= maxlen END REPEAT;
                          

                          【讨论】:

                            猜你喜欢
                            • 2010-11-08
                            • 1970-01-01
                            • 1970-01-01
                            • 2016-12-30
                            • 2016-09-21
                            • 2021-04-01
                            • 1970-01-01
                            相关资源
                            最近更新 更多