SQL将值拆分为多行

AFD*_*AFD 63 mysql sql delimiter delimiter-separated-values

我有桌子:

id | name    
1  | a,b,c    
2  | b
Run Code Online (Sandbox Code Playgroud)

我想要像这样的输出:

id | name    
1  | a    
1  | b    
1  | c    
2  | b
Run Code Online (Sandbox Code Playgroud)

fth*_*lla 96

如果您可以创建一个数字表,其中包含从1到要拆分的最大字段的数字,您可以使用如下解决方案:

select
  tablename.id,
  SUBSTRING_INDEX(SUBSTRING_INDEX(tablename.name, ',', numbers.n), ',', -1) name
from
  numbers inner join tablename
  on CHAR_LENGTH(tablename.name)
     -CHAR_LENGTH(REPLACE(tablename.name, ',', ''))>=numbers.n-1
order by
  id, n
Run Code Online (Sandbox Code Playgroud)

请看这里的小提琴.

如果您无法创建表,那么解决方案可以是:

select
  tablename.id,
  SUBSTRING_INDEX(SUBSTRING_INDEX(tablename.name, ',', numbers.n), ',', -1) name
from
  (select 1 n union all
   select 2 union all select 3 union all
   select 4 union all select 5) numbers INNER JOIN tablename
  on CHAR_LENGTH(tablename.name)
     -CHAR_LENGTH(REPLACE(tablename.name, ',', ''))>=numbers.n-1
order by
  id, n
Run Code Online (Sandbox Code Playgroud)

这里有一个例子.

  • @ user2577038你可以在没有数字表的情况下做到这一点,请在http://sqlfiddle.com/#!2/a213e4/1查看 (11认同)
  • 需要注意的重要一点是在第二个示例中,由逗号分隔的“字段”的最大数量为 5。您可以通过以下方法检查字符串中的出现次数:http://stackoverflow.com/问题/12344795/count-the-number-of-occurences-of-a-string-in-a-varchar-field。继续将“select [number] union all”子句添加到“numbers”内联视图,直到返回的行数停止增加。 (2认同)

Pau*_*gel 11

如果该name列是一个 JSON 数组(如'["a","b","c"]'),那么您可以使用JSON_TABLE()(自 MySQL 8.0.4 起可用提取/解压它:

select t.id, j.name
from mytable t
join json_table(
  t.name,
  '$[*]' columns (name varchar(50) path '$')
) j;
Run Code Online (Sandbox Code Playgroud)

结果:

| id  | name |
| --- | ---- |
| 1   | a    |
| 1   | b    |
| 1   | c    |
| 2   | b    |
Run Code Online (Sandbox Code Playgroud)

在 DB Fiddle 上查看

如果您以简单的 CSV 格式存储值,那么您首先需要将其转换为 JSON:

select t.id, j.name
from mytable t
join json_table(
  replace(json_array(t.name), ',', '","'),
  '$[*]' columns (name varchar(50) path '$')
) j
Run Code Online (Sandbox Code Playgroud)

结果:

| id  | name |
| --- | ---- |
| 1   | a    |
| 1   | b    |
| 1   | c    |
| 2   | b    |
Run Code Online (Sandbox Code Playgroud)

在 DB Fiddle 上查看


Har*_*arx 6

这是我的尝试:第一个选择将 csv 字段呈现给拆分。使用递归 CTE,我们可以创建一个数字列表,该列表仅限于 csv 字段中的术语数量。术语的数量只是 csv 字段的长度与它本身的长度之差,其中删除了所有分隔符。然后加入这些数字, substring_index 提取该术语。

with recursive
    T as ( select 'a,b,c,d,e,f' as items),
    N as ( select 1 as n union select n + 1 from N, T
        where n <= length(items) - length(replace(items, ',', '')))
    select distinct substring_index(substring_index(items, ',', n), ',', -1)
group_name from N, T
Run Code Online (Sandbox Code Playgroud)

  • @Kermit MySQL 从 7 年前开始支持 CTE。 (3认同)

Luv*_*Luv 5

我从这里获取了更改列名的引用.

DELIMITER $$

CREATE FUNCTION strSplit(x VARCHAR(65000), delim VARCHAR(12), pos INTEGER) 
RETURNS VARCHAR(65000)
BEGIN
  DECLARE output VARCHAR(65000);
  SET output = REPLACE(SUBSTRING(SUBSTRING_INDEX(x, delim, pos)
                 , LENGTH(SUBSTRING_INDEX(x, delim, pos - 1)) + 1)
                 , delim
                 , '');
  IF output = '' THEN SET output = null; END IF;
  RETURN output;
END $$


CREATE PROCEDURE BadTableToGoodTable()
BEGIN
  DECLARE i INTEGER;

  SET i = 1;
  REPEAT
    INSERT INTO GoodTable (id, name)
      SELECT id, strSplit(name, ',', i) FROM BadTable
      WHERE strSplit(name, ',', i) IS NOT NULL;
    SET i = i + 1;
    UNTIL ROW_COUNT() = 0
  END REPEAT;
END $$

DELIMITER ;
Run Code Online (Sandbox Code Playgroud)


And*_*rey 5

我的变体:将表名、字段名和分隔符作为参数的存储过程。灵感来自帖子http://www.marcogoncalves.com/2011/03/mysql-split-column-string-into-rows/

delimiter $$

DROP PROCEDURE IF EXISTS split_value_into_multiple_rows $$
CREATE PROCEDURE split_value_into_multiple_rows(tablename VARCHAR(20),
    id_column VARCHAR(20), value_column VARCHAR(20), delim CHAR(1))
  BEGIN
    DECLARE id INT DEFAULT 0;
    DECLARE value VARCHAR(255);
    DECLARE occurrences INT DEFAULT 0;
    DECLARE i INT DEFAULT 0;
    DECLARE splitted_value VARCHAR(255);
    DECLARE done INT DEFAULT 0;
    DECLARE cur CURSOR FOR SELECT tmp_table1.id, tmp_table1.value FROM 
        tmp_table1 WHERE tmp_table1.value IS NOT NULL AND tmp_table1.value != '';
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;

    SET @expr = CONCAT('CREATE TEMPORARY TABLE tmp_table1 (id INT NOT NULL, value VARCHAR(255)) ENGINE=Memory SELECT ',
        id_column,' id, ', value_column,' value FROM ',tablename);
    PREPARE stmt FROM @expr;
    EXECUTE stmt;
    DEALLOCATE PREPARE stmt;

    DROP TEMPORARY TABLE IF EXISTS tmp_table2;
    CREATE TEMPORARY TABLE tmp_table2 (id INT NOT NULL, value VARCHAR(255) NOT NULL) ENGINE=Memory;

    OPEN cur;
      read_loop: LOOP
        FETCH cur INTO id, value;
        IF done THEN
          LEAVE read_loop;
        END IF;

        SET occurrences = (SELECT CHAR_LENGTH(value) -
                           CHAR_LENGTH(REPLACE(value, delim, '')) + 1);
        SET i=1;
        WHILE i <= occurrences DO
          SET splitted_value = (SELECT TRIM(SUBSTRING_INDEX(
              SUBSTRING_INDEX(value, delim, i), delim, -1)));
          INSERT INTO tmp_table2 VALUES (id, splitted_value);
          SET i = i + 1;
        END WHILE;
      END LOOP;

      SELECT * FROM tmp_table2;
    CLOSE cur;
    DROP TEMPORARY TABLE tmp_table1;
  END; $$

delimiter ;
Run Code Online (Sandbox Code Playgroud)

用法示例(归一化):

CALL split_value_into_multiple_rows('my_contacts', 'contact_id', 'interests', ',');

CREATE TABLE interests (
  interest_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  interest VARCHAR(30) NOT NULL
) SELECT DISTINCT value interest FROM tmp_table2;

CREATE TABLE contact_interest (
  contact_id INT NOT NULL,
  interest_id INT NOT NULL,
  CONSTRAINT fk_contact_interest_my_contacts_contact_id FOREIGN KEY (contact_id) REFERENCES my_contacts (contact_id),
  CONSTRAINT fk_contact_interest_interests_interest_id FOREIGN KEY (interest_id) REFERENCES interests (interest_id)
) SELECT my_contacts.contact_id, interests.interest_id
    FROM my_contacts, tmp_table2, interests
    WHERE my_contacts.contact_id = tmp_table2.id AND interests.interest = tmp_table2.value;
Run Code Online (Sandbox Code Playgroud)