Ich weiß, dass dies bis zu einem gewissen Grad mit PHP und MYSQL beantwortet wurde, aber ich habe mich gefragt, ob mir jemand die einfachste Methode beibringen kann, einen String (durch Kommas getrennt) in mehrere Zeilen in Oracle 10g aufzuteilen ( vorzugsweise) und 11 g.
Die Tabelle sieht wie folgt aus:
Name | Project | Error
108 test Err1, Err2, Err3
109 test2 Err1
Ich möchte Folgendes erstellen:
Name | Project | Error
108 Test Err1
108 Test Err2
108 Test Err3
109 Test2 Err1
Ich habe ein paar mögliche Lösungen für den Stapel gesehen, die jedoch nur eine einzige Spalte ausmachten (die durch Kommas getrennte Zeichenfolge). Jede Hilfe wäre sehr dankbar.
Die akzeptierte Antwort weist eine schlechte Leistung auf, wenn große Datensätze verwendet werden.
Dies kann ein verbesserter Weg sein (auch mit regexp und connect by):
with temp as
(
select 108 Name, 'test' Project, 'Err1, Err2, Err3' Error from dual
union all
select 109, 'test2', 'Err1' from dual
)
select distinct
t.name, t.project,
trim(regexp_substr(t.error, '[^,]+', 1, levels.column_value)) as error
from
temp t,
table(cast(multiset(select level from dual connect by level <= length (regexp_replace(t.error, '[^,]+')) + 1) as sys.OdciNumberList)) levels
order by name
[~ # ~] edit [~ # ~] : Hier ist eine einfache (wie "nicht eingehende") Erklärung der Abfrage.
length (regexp_replace(t.error, '[^,]+')) + 1
verwendet regexp_replace
, um alles zu löschen, was nicht das Trennzeichen ist (in diesem Fall Komma), und length +1
, um zu ermitteln, wie viele Elemente (Fehler) vorhanden sind.Die Funktion select level from dual connect by level <= (...)
verwendet eine hierarchische Abfrage , um eine Spalte mit einer zunehmenden Anzahl gefundener Übereinstimmungen von 1 bis zur Gesamtzahl der Fehler zu erstellen .
Vorschau:
select level, length (regexp_replace('Err1, Err2, Err3', '[^,]+')) + 1 as max
from dual connect by level <= length (regexp_replace('Err1, Err2, Err3', '[^,]+')) + 1
table(cast(multiset(.....) as sys.OdciNumberList))
führt ein Casting von Oracle-Typen durch. cast(multiset(.....)) as sys.OdciNumberList
transformiert mehrere Sammlungen (eine Sammlung für jede Zeile im Originaldatensatz) in eine einzige Sammlung von Zahlen, OdciNumberList.table()
wandelt eine Auflistung in eine Ergebnismenge um.FROM
ohne Join erstellt einen Cross-Join zwischen Ihrem Dataset und dem Multiset. Infolgedessen wird eine Zeile im Datensatz mit 4 Übereinstimmungen viermal wiederholt (mit zunehmender Nummer in der Spalte "column_value").
Vorschau:
select * from
temp t,
table(cast(multiset(select level from dual connect by level <= length (regexp_replace(t.error, '[^,]+')) + 1) as sys.OdciNumberList)) levels
trim(regexp_substr(t.error, '[^,]+', 1, levels.column_value))
verwendet den column_value
als n-ten_Auftritt/Vorkommen Parameter für regexp_substr
.t.name, t.project
Als Beispiel) zur einfachen Visualisierung hinzufügen.Einige Verweise auf Oracle-Dokumente:
reguläre Ausdrücke sind eine wunderbare Sache :)
with temp as (
select 108 Name, 'test' Project, 'Err1, Err2, Err3' Error from dual
union all
select 109, 'test2', 'Err1' from dual
)
SELECT distinct Name, Project, trim(regexp_substr(str, '[^,]+', 1, level)) str
FROM (SELECT Name, Project, Error str FROM temp) t
CONNECT BY instr(str, ',', 1, level - 1) > 0
order by Name
Es gibt einen großen Unterschied zwischen den folgenden beiden:
Wenn Sie die Zeilen nicht einschränken, erzeugt die CONNECT BY -Klausel mehrere Zeilen und wird nicht die gewünschte Ausgabe geben.
Abgesehen von regulären Ausdrücken verwenden einige andere Alternativen:
Setup
SQL> CREATE TABLE t (
2 ID NUMBER GENERATED ALWAYS AS IDENTITY,
3 text VARCHAR2(100)
4 );
Table created.
SQL>
SQL> INSERT INTO t (text) VALUES ('Word1, Word2, Word3');
1 row created.
SQL> INSERT INTO t (text) VALUES ('Word4, Word5, Word6');
1 row created.
SQL> INSERT INTO t (text) VALUES ('Word7, Word8, Word9');
1 row created.
SQL> COMMIT;
Commit complete.
SQL>
SQL> SELECT * FROM t;
ID TEXT
---------- ----------------------------------------------
1 Word1, Word2, Word3
2 Word4, Word5, Word6
3 Word7, Word8, Word9
SQL>
Verwenden von [~ # ~] xmltable [~ # ~] :
SQL> SELECT id,
2 trim(COLUMN_VALUE) text
3 FROM t,
4 xmltable(('"'
5 || REPLACE(text, ',', '","')
6 || '"'))
7 /
ID TEXT
---------- ------------------------
1 Word1
1 Word2
1 Word3
2 Word4
2 Word5
2 Word6
3 Word7
3 Word8
3 Word9
9 rows selected.
SQL>
Verwenden der [~ # ~] Modell [~ # ~] -Klausel:
SQL> WITH
2 model_param AS
3 (
4 SELECT id,
5 text AS orig_str ,
6 ','
7 || text
8 || ',' AS mod_str ,
9 1 AS start_pos ,
10 Length(text) AS end_pos ,
11 (Length(text) - Length(Replace(text, ','))) + 1 AS element_count ,
12 0 AS element_no ,
13 ROWNUM AS rn
14 FROM t )
15 SELECT id,
16 trim(Substr(mod_str, start_pos, end_pos-start_pos)) text
17 FROM (
18 SELECT *
19 FROM model_param MODEL PARTITION BY (id, rn, orig_str, mod_str)
20 DIMENSION BY (element_no)
21 MEASURES (start_pos, end_pos, element_count)
22 RULES ITERATE (2000)
23 UNTIL (ITERATION_NUMBER+1 = element_count[0])
24 ( start_pos[ITERATION_NUMBER+1] = instr(cv(mod_str), ',', 1, cv(element_no)) + 1,
25 end_pos[iteration_number+1] = instr(cv(mod_str), ',', 1, cv(element_no) + 1) )
26 )
27 WHERE element_no != 0
28 ORDER BY mod_str ,
29 element_no
30 /
ID TEXT
---------- --------------------------------------------------
1 Word1
1 Word2
1 Word3
2 Word4
2 Word5
2 Word6
3 Word7
3 Word8
3 Word9
9 rows selected.
SQL>
Ein paar weitere Beispiele dafür:
SELECT trim(regexp_substr('Err1, Err2, Err3', '[^,]+', 1, LEVEL)) str_2_tab
FROM dual
CONNECT BY LEVEL <= regexp_count('Err1, Err2, Err3', ',')+1
/
SELECT trim(regexp_substr('Err1, Err2, Err3', '[^,]+', 1, LEVEL)) str_2_tab
FROM dual
CONNECT BY LEVEL <= length('Err1, Err2, Err3') - length(REPLACE('Err1, Err2, Err3', ',', ''))+1
/
Verwenden Sie möglicherweise auch DBMS_UTILITY.comma_to_table & table_to_comma: http://www.Oracle-base.com/articles/9i/useful-procedures-and-functions-9i.php#DBMS_UTILITY.comma_to_table
Ich möchte einen anderen Ansatz mit einer PIPELINED-Tabellenfunktion vorschlagen. Dies ähnelt in gewisser Weise der XMLTABLE-Technik, mit der Ausnahme, dass Sie Ihre eigene benutzerdefinierte Funktion zum Teilen der Zeichenfolge bereitstellen:
-- Create a collection type to hold the results
CREATE OR REPLACE TYPE typ_str2tbl_nst AS TABLE OF VARCHAR2(30);
/
-- Split the string according to the specified delimiter
CREATE OR REPLACE FUNCTION str2tbl (
p_string VARCHAR2,
p_delimiter CHAR DEFAULT ','
)
RETURN typ_str2tbl_nst PIPELINED
AS
l_tmp VARCHAR2(32000) := p_string || p_delimiter;
l_pos NUMBER;
BEGIN
LOOP
l_pos := INSTR( l_tmp, p_delimiter );
EXIT WHEN NVL( l_pos, 0 ) = 0;
PIPE ROW ( RTRIM( LTRIM( SUBSTR( l_tmp, 1, l_pos-1) ) ) );
l_tmp := SUBSTR( l_tmp, l_pos+1 );
END LOOP;
END str2tbl;
/
-- The problem solution
SELECT name,
project,
TRIM(COLUMN_VALUE) error
FROM t, TABLE(str2tbl(error));
Ergebnisse:
NAME PROJECT ERROR
---------- ---------- --------------------
108 test Err1
108 test Err2
108 test Err3
109 test2 Err1
Das Problem bei dieser Art von Ansatz ist, dass der Optimierer die Kardinalität der Tabellenfunktion häufig nicht kennt und eine Vermutung anstellen muss. Dies kann potenziell schädlich für Ihre Ausführungspläne sein. Daher kann diese Lösung erweitert werden, um Ausführungsstatistiken für das Optimierungsprogramm bereitzustellen.
Sie können diese Optimierungsschätzung anzeigen, indem Sie in der obigen Abfrage einen EXPLAIN PLAN ausführen:
Execution Plan
----------------------------------------------------------
Plan hash value: 2402555806
----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 16336 | 366K| 59 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 16336 | 366K| 59 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL | T | 2 | 42 | 3 (0)| 00:00:01 |
| 3 | COLLECTION ITERATOR PICKLER FETCH| STR2TBL | 8168 | 16336 | 28 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------
Obwohl die Auflistung nur 3 Werte enthält, schätzt das Optimierungsprogramm 8168 Zeilen (Standardwert). Dies mag zunächst irrelevant erscheinen, aber es kann ausreichen, dass sich der Optimierer für einen suboptimalen Plan entscheidet.
Die Lösung besteht darin, die Optimierererweiterungen zu verwenden, um Statistiken für die Sammlung bereitzustellen:
-- Create the optimizer interface to the str2tbl function
CREATE OR REPLACE TYPE typ_str2tbl_stats AS OBJECT (
dummy NUMBER,
STATIC FUNCTION ODCIGetInterfaces ( p_interfaces OUT SYS.ODCIObjectList )
RETURN NUMBER,
STATIC FUNCTION ODCIStatsTableFunction ( p_function IN SYS.ODCIFuncInfo,
p_stats OUT SYS.ODCITabFuncStats,
p_args IN SYS.ODCIArgDescList,
p_string IN VARCHAR2,
p_delimiter IN CHAR DEFAULT ',' )
RETURN NUMBER
);
/
-- Optimizer interface implementation
CREATE OR REPLACE TYPE BODY typ_str2tbl_stats
AS
STATIC FUNCTION ODCIGetInterfaces ( p_interfaces OUT SYS.ODCIObjectList )
RETURN NUMBER
AS
BEGIN
p_interfaces := SYS.ODCIObjectList ( SYS.ODCIObject ('SYS', 'ODCISTATS2') );
RETURN ODCIConst.SUCCESS;
END ODCIGetInterfaces;
-- This function is responsible for returning the cardinality estimate
STATIC FUNCTION ODCIStatsTableFunction ( p_function IN SYS.ODCIFuncInfo,
p_stats OUT SYS.ODCITabFuncStats,
p_args IN SYS.ODCIArgDescList,
p_string IN VARCHAR2,
p_delimiter IN CHAR DEFAULT ',' )
RETURN NUMBER
AS
BEGIN
-- I'm using basically half the string lenght as an estimator for its cardinality
p_stats := SYS.ODCITabFuncStats( CEIL( LENGTH( p_string ) / 2 ) );
RETURN ODCIConst.SUCCESS;
END ODCIStatsTableFunction;
END;
/
-- Associate our optimizer extension with the PIPELINED function
ASSOCIATE STATISTICS WITH FUNCTIONS str2tbl USING typ_str2tbl_stats;
Testen des resultierenden Ausführungsplans:
Execution Plan
----------------------------------------------------------
Plan hash value: 2402555806
----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 23 | 59 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 1 | 23 | 59 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL | T | 2 | 42 | 3 (0)| 00:00:01 |
| 3 | COLLECTION ITERATOR PICKLER FETCH| STR2TBL | 1 | 2 | 28 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------
Wie Sie sehen können, ist die Kardinalität im obigen Plan nicht mehr der 8196-Schätzwert. Es ist immer noch nicht korrekt, da wir eine Spalte anstelle eines String-Literal an die Funktion übergeben.
Einige Anpassungen am Funktionscode wären notwendig, um in diesem speziellen Fall eine genauere Schätzung vorzunehmen, aber ich denke, das Gesamtkonzept wird hier ziemlich genau erklärt.
Die in dieser Antwort verwendete Funktion str2tbl wurde ursprünglich von Tom Kyte entwickelt: https://asktom.Oracle.com/pls/asktom/f?p=100:11:0:::P11_QUESTION_ID:110612348061 =
Weitere Informationen zum Zuordnen von Statistiken zu Objekttypen finden Sie in diesem Artikel: http://www.Oracle-developer.net/display.php?id=427
Die hier beschriebene Technik funktioniert in 10g +.
Ich denke, die beste Art und Weise, wie ich mich verbinde, und regexp-Funktion
with temp as (
select 108 Name, 'test' Project, 'Err1, Err2, Err3' Error from dual
union all
select 109, 'test2', 'Err1' from dual
)
SELECT distinct Name, Project, trim(regexp_substr(str, '[^,]+', 1, level)) str
FROM (SELECT Name, Project, Error str FROM temp) t
CONNECT BY instr(str, ',', 1, level - 1) > 0
order by Name
REGEXP_COUNT wurde erst mit Oracle 11i hinzugefügt. Hier ist eine Oracle 10g-Lösung, die von Art's Lösung übernommen wurde.
SELECT trim(regexp_substr('Err1, Err2, Err3', '[^,]+', 1, LEVEL)) str_2_tab
FROM dual
CONNECT BY LEVEL <=
LENGTH('Err1, Err2, Err3')
- LENGTH(REPLACE('Err1, Err2, Err3', ',', ''))
+ 1;
Hier ist eine alternative Implementierung mit XMLTABLE, die das Umwandeln in verschiedene Datentypen ermöglicht:
select
xmltab.txt
from xmltable(
'for $text in tokenize("a,b,c", ",") return $text'
columns
txt varchar2(4000) path '.'
) xmltab
;
... oder wenn Ihre durch Trennzeichen getrennten Zeichenfolgen in einer oder mehreren Zeilen einer Tabelle gespeichert sind:
select
xmltab.txt
from (
select 'a;b;c' inpt from dual union all
select 'd;e;f' from dual
) base
inner join xmltable(
'for $text in tokenize($input, ";") return $text'
passing base.inpt as "input"
columns
txt varchar2(4000) path '.'
) xmltab
on 1=1
;
Ab Oracle 12c können Sie JSON_TABLE
und JSON_ARRAY
:
CREATE TABLE tab(Name, Project, Error) AS
SELECT 108,'test' ,'Err1, Err2, Err3' FROM dual UNION
SELECT 109,'test2','Err1' FROM dual;
Und fragen Sie:
SELECT *
FROM tab t
OUTER APPLY (SELECT TRIM(p) AS p
FROM JSON_TABLE(REPLACE(JSON_ARRAY(t.Error), ',', '","'),
'$[*]' COLUMNS (p VARCHAR2(4000) PATH '$'))) s;
Ausgabe:
┌──────┬─────────┬──────────────────┬──────┐
│ Name │ Project │ Error │ P │
├──────┼─────────┼──────────────────┼──────┤
│ 108 │ test │ Err1, Err2, Err3 │ Err1 │
│ 108 │ test │ Err1, Err2, Err3 │ Err2 │
│ 108 │ test │ Err1, Err2, Err3 │ Err3 │
│ 109 │ test2 │ Err1 │ Err1 │
└──────┴─────────┴──────────────────┴──────┘
Ohne connect by oder regexp zu verwenden:
with mytable as (
select 108 name, 'test' project, 'Err1,Err2,Err3' error from dual
union all
select 109, 'test2', 'Err1' from dual
)
,x as (
select name
,project
,','||error||',' error
from mytable
)
,iter as (SELECT rownum AS pos
FROM all_objects
)
select x.name,x.project
,SUBSTR(x.error
,INSTR(x.error, ',', 1, iter.pos) + 1
,INSTR(x.error, ',', 1, iter.pos + 1)-INSTR(x.error, ',', 1, iter.pos)-1
) error
from x, iter
where iter.pos < = (LENGTH(x.error) - LENGTH(REPLACE(x.error, ','))) - 1;
Ich möchte eine andere Methode hinzufügen. Dieser verwendet rekursive Abfragen, was ich in den anderen Antworten nicht gesehen habe. Es wird von Oracle seit 11gR2 unterstützt.
with cte0 as (
select phone_number x
from hr.employees
), cte1(xstr,xrest,xremoved) as (
select x, x, null
from cte0
union all
select xstr,
case when instr(xrest,'.') = 0 then null else substr(xrest,instr(xrest,'.')+1) end,
case when instr(xrest,'.') = 0 then xrest else substr(xrest,1,instr(xrest,'.') - 1) end
from cte1
where xrest is not null
)
select xstr, xremoved from cte1
where xremoved is not null
order by xstr
Es ist sehr flexibel mit dem Splitting-Charakter. Ändern Sie es einfach in den INSTR
Aufrufen.
Ich hatte das gleiche Problem und xmltable hat mir geholfen:
SELECT id, beschneide (COLUMN_VALUE) Text FROM t, xmltable (('"' || REPLACE (text, ',', '", "') || '"')