J'ai un tableau comme ci-dessous:
-------------
ID | NAME
-------------
1001 | A,B,C
1002 | D,E,F
1003 | C,E,G
-------------
Je veux que ces valeurs soient affichées comme:
-------------
ID | NAME
-------------
1001 | A
1001 | B
1001 | C
1002 | D
1002 | E
1002 | F
1003 | C
1003 | E
1003 | G
-------------
J'ai essayé de faire:
select split('A,B,C,D,E,F', ',') from dual; -- WILL RETURN COLLECTION
select column_value
from table (select split('A,B,C,D,E,F', ',') from dual); -- RETURN COLUMN_VALUE
Essayez d'utiliser la requête ci-dessous:
WITH T AS (SELECT 'A,B,C,D,E,F' STR FROM DUAL) SELECT
REGEXP_SUBSTR (STR, '[^,]+', 1, LEVEL) SPLIT_VALUES FROM T
CONNECT BY LEVEL <= (SELECT LENGTH (REPLACE (STR, ',', NULL)) FROM T)
Requête ci-dessous avec ID:
WITH TAB AS
(SELECT '1001' ID, 'A,B,C,D,E,F' STR FROM DUAL
)
SELECT ID,
REGEXP_SUBSTR (STR, '[^,]+', 1, LEVEL) SPLIT_VALUES FROM TAB
CONNECT BY LEVEL <= (SELECT LENGTH (REPLACE (STR, ',', NULL)) FROM TAB);
EDIT: Essayez d'utiliser la requête ci-dessous pour plusieurs ID et séparation multiple:
WITH TAB AS
(SELECT '1001' ID, 'A,B,C,D,E,F' STR FROM DUAL
UNION
SELECT '1002' ID, 'D,E,F' STR FROM DUAL
UNION
SELECT '1003' ID, 'C,E,G' STR FROM DUAL
)
select id, substr(STR, instr(STR, ',', 1, lvl) + 1, instr(STR, ',', 1, lvl + 1) - instr(STR, ',', 1, lvl) - 1) name
from
( select ',' || STR || ',' as STR, id from TAB ),
( select level as lvl from dual connect by level <= 100 )
where lvl <= length(STR) - length(replace(STR, ',')) - 1
order by ID, NAME
Il y a plusieurs options. Voir Fractionner les chaînes délimitées par des virgules dans une table dans Oracle .
Utilisation de REGEXP_SUBSTR:
SQL> WITH sample_data AS(
2 SELECT 10001 ID, 'A,B,C' str FROM dual UNION ALL
3 SELECT 10002 ID, 'D,E,F' str FROM dual UNION ALL
4 SELECT 10003 ID, 'C,E,G' str FROM dual
5 )
6 -- end of sample_data mimicking real table
7 SELECT distinct id, trim(regexp_substr(str, '[^,]+', 1, LEVEL)) str
8 FROM sample_data
9 CONNECT BY LEVEL <= regexp_count(str, ',')+1
10 ORDER BY ID
11 /
ID STR
---------- -----
10001 A
10001 B
10001 C
10002 D
10002 E
10002 F
10003 C
10003 E
10003 G
9 rows selected.
SQL>
Utilisation de XMLTABLE:
SQL> WITH sample_data AS(
2 SELECT 10001 ID, 'A,B,C' str FROM dual UNION ALL
3 SELECT 10002 ID, 'D,E,F' str FROM dual UNION ALL
4 SELECT 10003 ID, 'C,E,G' str FROM dual
5 )
6 -- end of sample_data mimicking real table
7 SELECT id,
8 trim(COLUMN_VALUE) str
9 FROM sample_data,
10 xmltable(('"'
11 || REPLACE(str, ',', '","')
12 || '"'))
13 /
ID STR
---------- ---
10001 A
10001 B
10001 C
10002 D
10002 E
10002 F
10003 C
10003 E
10003 G
9 rows selected.
j'ai résolu le même problème de cette façon ...
select YT.ID,
REPLACE(REGEXP_SUBSTR(','||YT.STR||',',',.*?,',1,lvl.lvl),',','') AS STR
from YOURTABLE YT
join (select level as lvl
from dual
connect by level <= (select max(regexp_count(STR,',')+1) from YOURTABLE)
) lvl on lvl.lvl <= regexp_count(YT.STR,',')+1
N'utilisez pas CONNECT BY ou REGEXP, ce qui entraînerait un produit cartésien sur une requête complexe. De plus, les solutions ci-dessus supposent que vous connaissiez les résultats possibles (A, B, C, D, E, F) plutôt qu’une liste de combinaisons.
Utilisez XMLTable:
SELECT c.fname, c.lname,
trim(COLUMN_VALUE) EMAIL_ADDRESS
FROM
CONTACTS c, CONTACT_STATUS s,
xmltable(('"'
|| REPLACE(EMAIL_ADDRESS, ';', '","')
|| '"'))
where c.status = s.id
COLUMN_VALUE est une pseudo-colonne qui appartient à xmltable. Ceci est rapide et correct et vous permet de référencer une colonne sans connaître ses valeurs.
Ceci prend la colonne et crée une table de valeurs "item", "item2", "item3" et se joint automatiquement à sa table source (CONTACTS). Cela a été testé sur des milliers de lignes
Remarque le ';' dans le xmltable est le séparateur dans le champ de la colonne.
Vous pouvez essayer quelque chose comme ça:
CREATE OR REPLACE TYPE "STR_TABLE"
as table of varchar2
create or replace function GetCollection( iStr varchar2, iSplit char default ',' ) return STR_TABLE as
pStr varchar2(4000) := trim(iStr);
rpart varchar(255);
pColl STR_TABLE := STR_TABLE();
begin
while nvl(length(pStr),0) > 0 loop
pos := inStr(pStr, iSplit );
if pos > 0 then
rpart := substr(pStr,1, pos-1);
pStr := substr(pStr,pos+1,length(pStr));
else
rpart := pStr;
pStr := null;
end if;
if rpart is not null then
pColl.Extend;
pColl(pColl.Count) := rpart;
end if;
end loop;
return pColl;
end;
J'ai essayé la solution de Lalit Kumar B et cela a fonctionné jusqu'à présent. Mais avec plus de données, j'ai rencontré un problème de performances (> 60 lignes,> 7 niveaux). Par conséquent, j’ai utilisé une variante plus statique, que je voudrais partager comme alternative.
WITH T AS (
SELECT 1001 AS ID, 'A,B,C' AS NAME FROM DUAL
UNION SELECT 1002 AS ID, 'D,E,F' AS NAME FROM DUAL
UNION SELECT 1003 AS ID, 'C,E,G' AS NAME FROM DUAL
) --SELECT * FROM T
SELECT ID as ID,
distinct_column AS NAME
FROM ( SELECT t.ID,
trim(regexp_substr(t.NAME, '[^,]+', 1,1)) AS c1,
trim(regexp_substr(t.NAME, '[^,]+', 1,2)) AS c2,
trim(regexp_substr(t.NAME, '[^,]+', 1,3)) AS c3,
trim(regexp_substr(t.NAME, '[^,]+', 1,4)) AS c4 -- etc.
FROM T )
UNPIVOT ( distinct_column FOR cn IN ( c1, c2, c3, c4 ) )
ID NAME
------ ------
1001 A
1001 B
1001 C
1002 D
1002 E
1002 F
1003 C
1003 E
1003 G
9 Zeilen gewählt
cette version fonctionne aussi avec des chaînes de plus d'un caractère:
select regexp_substr('A,B,C,Karl-Heinz,D','[^,]+', 1, level) from dual
connect by regexp_substr('A,B,C,Karl-Heinz,D', '[^,]+', 1, level) is not null;
see Comment diviser une chaîne séparée par des virgules et passer à la clause IN de l'instruction select