SQL 값을 여러 행으로 분할
테이블이 있습니다.
id | name
1 | a,b,c
2 | b
다음과 같은 출력을 원합니다.
id | name
1 | a
1 | b
1 | c
2 | b
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
여기 바이올린 좀 봐주세요.
테이블을 작성할 수 없는 경우 솔루션은 다음과 같습니다.
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
바이올린의 예가 여기 있습니다.
이 경우,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;
결과:
| id | name |
| --- | ---- |
| 1 | a |
| 1 | b |
| 1 | c |
| 2 | b |
값을 단순한 CSV 형식으로 저장하는 경우 먼저 JSON으로 변환해야 합니다.
select t.id, j.name
from mytable t
join json_table(
replace(json_array(t.name), ',', '","'),
'$[*]' columns (name varchar(50) path '$')
) j
결과:
| id | name |
| --- | ---- |
| 1 | a |
| 1 | b |
| 1 | c |
| 2 | b |
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 ;
제 시도는 다음과 같습니다.첫 번째 선택에서는 분할에 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
My variant: 테이블 이름, 필드 이름 및 딜리미터를 인수로 사용하는 저장 프로시저.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 ;
사용 예(정규화):
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;
CREATE PROCEDURE `getVal`()
BEGIN
declare r_len integer;
declare r_id integer;
declare r_val varchar(20);
declare i integer;
DECLARE found_row int(10);
DECLARE row CURSOR FOR select length(replace(val,"|","")),id,val from split;
create table x(id int,name varchar(20));
open row;
select FOUND_ROWS() into found_row ;
read_loop: LOOP
IF found_row = 0 THEN
LEAVE read_loop;
END IF;
set i = 1;
FETCH row INTO r_len,r_id,r_val;
label1: LOOP
IF i <= r_len THEN
insert into x values( r_id,SUBSTRING(replace(r_val,"|",""),i,1));
SET i = i + 1;
ITERATE label1;
END IF;
LEAVE label1;
END LOOP label1;
set found_row = found_row - 1;
END LOOP;
close row;
select * from x;
drop table x;
END
원래 질문은 MySQL과 SQL에 대한 일반적인 질문이었습니다.다음 예시는 MySQL의 새로운 버전용입니다.안타깝게도 SQL 서버에서 작동하는 일반 쿼리는 불가능합니다.CTE를 지원하지 않는 서버도 있고 substring_index를 지원하지 않는 서버도 있지만 문자열을 여러 행으로 분할하는 기능이 내장되어 있는 서버도 있습니다.
--- 답은 뒤에 있다 ---
재귀 쿼리는 서버가 기본 제공 기능을 제공하지 않을 때 편리합니다.병목현상이 될 수도 있습니다.
다음 쿼리는 MySQL 버전 8.0.16에서 작성 및 테스트되었습니다.버전 5.7-에서는 동작하지 않습니다.이전 버전은 Common Table Expression(CTE; 공통 테이블 표현)을 지원하지 않으므로 재귀 쿼리를 지원하지 않습니다.
with recursive
input as (
select 1 as id, 'a,b,c' as names
union
select 2, 'b'
),
recurs as (
select id, 1 as pos, names as remain, substring_index( names, ',', 1 ) as name
from input
union all
select id, pos + 1, substring( remain, char_length( name ) + 2 ),
substring_index( substring( remain, char_length( name ) + 2 ), ',', 1 )
from recurs
where char_length( remain ) > char_length( name )
)
select id, name
from recurs
order by id, pos;
위의 예에서 "select number union all"을 계속 추가해야 하기 때문에 분할이 많은 경우 문제가 될 수 있습니다.
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
저는 각 자릿수에 숫자 행만 추가하는 것이 더 나은 방법이라고 생각했습니다.다음 예시는 1-1000에 다른 행을 추가하여 1-10000 등에 적합합니다.
select
tablename.id,
SUBSTRING_INDEX(SUBSTRING_INDEX(tablename.name, ',', numbers.n), ',', -1) name
from(SELECT @row := @row + 1 AS n FROM
(select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as t,
(select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as t2,
(select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as t3,
(SELECT @row:=0) as numbers)as numbers INNER JOIN tablename
on CHAR_LENGTH(tablename.name)
-CHAR_LENGTH(REPLACE(tablename.name, ',', ''))>=numbers.n-1
order by
id, n
베스트 프랙티스결과:
SELECT
SUBSTRING_INDEX(SUBSTRING_INDEX('ab,bc,cd',',',help_id+1),',',-1) AS oid
FROM
(
SELECT @xi:=@xi+1 as help_id from
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) xc1,
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) xc2,
(SELECT @xi:=-1) xc0
) a
WHERE
help_id < LENGTH('ab,bc,cd')-LENGTH(REPLACE('ab,bc,cd',',',''))+1
먼저 숫자 테이블을 만듭니다.
SELECT @xi:=@xi+1 as help_id from
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) xc1,
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) xc2,
(SELECT @xi:=-1) xc0;
| help_id |
| --- |
| 0 |
| 1 |
| 2 |
| 3 |
| ... |
| 24 |
둘째, 스트링을 분할합니다.
SELECT SUBSTRING_INDEX(SUBSTRING_INDEX('ab,bc,cd',',',help_id+1),',',-1) AS oid
FROM
numbers_table
WHERE
help_id < LENGTH('ab,bc,cd')-LENGTH(REPLACE('ab,bc,cd',',',''))+1
| oid |
| --- |
| ab |
| bc |
| cd |
이것이 나의 해결책이다.
-- Create the maximum number of words we want to pick (indexes in n)
with recursive n(i) as (
select
1 i
union all
select i+1 from n where i < 1000
)
select distinct
s.id,
s.oaddress,
-- n.i,
-- use the index to pick the nth word, the last words will always repeat. Remove the duplicates with distinct
if(instr(reverse(trim(substring_index(s.oaddress,' ',n.i))),' ') > 0,
reverse(substr(reverse(trim(substring_index(s.oaddress,' ',n.i))),1,
instr(reverse(trim(substring_index(s.oaddress,' ',n.i))),' '))),
trim(substring_index(s.oaddress,' ',n.i))) oth
from
app_schools s,
n
SELECT id, unnest(string_to_array(name, ',')) AS names
FROM datatable
이것이 도움이 되기를 바랍니다.d
언급URL : https://stackoverflow.com/questions/17942508/sql-split-values-to-multiple-rows
'itsource' 카테고리의 다른 글
django.core. 예외.잘못 구성됨:MySQLdb 모듈 로드 중 오류 발생:MySQLdb라는 이름의 모듈이 없습니다. (0) | 2023.01.15 |
---|---|
특정 날짜 이후 MySQL 데이터 가져오기 (0) | 2023.01.15 |
last / url 뒤의 문자 (0) | 2023.01.15 |
Chartkick.js를 사용하여 축을 숨기는 방법 (0) | 2023.01.15 |
MariaDB가 원격 연결을 허용하지 않음 (0) | 2023.01.15 |