これはPHPとMYSQLである程度答えられていますが、Oracle 10gで文字列(カンマ区切り)を複数の行に分割する最も簡単な方法を誰かに教えてもらえればと思いました。 11g。
表は以下のとおりです。
Name | Project | Error
108 test Err1, Err2, Err3
109 test2 Err1
以下を作成したいです。
Name | Project | Error
108 Test Err1
108 Test Err2
108 Test Err3
109 Test2 Err1
私はスタックの周りにいくつかの潜在的な解決策を見ました、しかしそれらは単一の列(カンマで区切られた文字列であること)だけを説明しました。任意の助けは大歓迎です。
大規模なデータセットを使用する場合、受け入れられた回答はパフォーマンスが良くありません。
これは(regexpや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:これはクエリの簡単な説明です(「深くない」など)。
length (regexp_replace(t.error, '[^,]+')) + 1
はregexp_replace
を使用して区切り文字以外のもの(この場合はコンマ)を消去し、length +1
を使用して要素(エラー)の数を調べます。select level from dual connect by level <= (...)
は、階層クエリを使用して、1からエラーの総数まで、一致数が増加する列を作成します。
プレビュー:
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))
は、Oracle型のキャストをいくつか行います。cast(multiset(.....)) as sys.OdciNumberList
は、複数のコレクション(元のデータセットの各行に1つのコレクション)を単一の数字のコレクションOdciNumberListに変換します。table()
関数はコレクションを結果セットに変換します。結合なしのFROM
は、データセットとマルチセットの間に交差結合を作成します。結果として、4つの一致があるデータセット内の行は4回繰り返されます( "column_value"という名前の列に番号が増えていきます)。
プレビュー:
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))
は、column_value
のnth_appearance/ocurrenceパラメーターとしてregexp_substr
を使用します。t.name, t.project
)を追加することができます。Oracleドキュメントへの参照
正規表現は素晴らしいことです:)
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
以下の2つの間には大きな違いがあります。
行を制限しない場合は、CONNECT BY句を使用すると、複数行が生成されます希望する出力が得られません。
正規表現とは別に、他のいくつかの方法があります。
設定
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>
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>
MODEL句を使用する:
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>
同じ例をいくつか挙げます。
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
/
また、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
PIPELINED表関数を使用して別のアプローチを提案したいと思います。文字列を分割するための独自のカスタム関数を提供している点を除けば、XMLTABLEの手法と多少似ています。
-- 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));
結果:
NAME PROJECT ERROR
---------- ---------- --------------------
108 test Err1
108 test Err2
108 test Err3
109 test2 Err1
この種のアプローチの問題点は、オプティマイザが表関数の基数を知らないことが多く、推測しなければならないことです。これは実行計画に有害になる可能性があるため、オプティマイザの実行統計を提供するようにこのソリューションを拡張することができます。
上記のクエリでEXPLAIN PLANを実行すると、このオプティマイザの見積もりを確認できます。
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 |
----------------------------------------------------------------------------------------------
コレクションには3つの値しかありませんが、オプティマイザはそれに対して8168行を見積もりました(デフォルト値)。これは最初は無関係に思えるかもしれませんが、オプティマイザが次善の計画を決定するのに十分かもしれません。
解決策は、オプティマイザ拡張機能を使用してコレクションの統計を提供することです。
-- 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;
実行計画をテストします。
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 |
----------------------------------------------------------------------------------------------
あなたが見ることができるように上記の計画の基数はもう8196推測された値ではありません。文字列リテラルの代わりに列を関数に渡しているため、まだ正しくありません。
この特定のケースでは、関数コードを少し調整してより正確に見積もる必要がありますが、ここでは全体的な概念について説明します。
この回答で使用したstr2tbl関数は、もともとTom Kyteによって開発されました。 https://asktom.Oracle.com/pls/asktom/f?p = 100:11:0 :::: P11_QUESTION_ID:110612348061
統計とオブジェクト型の関連付けの概念は、この記事を読んでさらに詳しく調べることができます。 http://www.Oracle-developer.net/display.php?id=427
ここで説明している手法は10g以降で動作します。
私は私が接続し、機能を正規表現する最善の方法だと思う
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は、Oracle 11iまで追加されませんでした。これは、Artのソリューションから採用されたOracle 10gのソリューションです。
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;
これはXMLTABLEを使った別の実装で、さまざまなデータ型にキャストできます。
select
xmltab.txt
from xmltable(
'for $text in tokenize("a,b,c", ",") return $text'
columns
txt varchar2(4000) path '.'
) xmltab
;
...または区切り文字列がテーブルの1つ以上の行に格納されている場合
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
;
Oracle 12c以降では、JSON_TABLE
とJSON_ARRAY
を使用できます。
CREATE TABLE tab(Name, Project, Error) AS
SELECT 108,'test' ,'Err1, Err2, Err3' FROM dual UNION
SELECT 109,'test2','Err1' FROM dual;
そしてクエリ:
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;
出力:
┌──────┬─────────┬──────────────────┬──────┐
│ 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 │
└──────┴─────────┴──────────────────┴──────┘
connect byまたはregexpを使用しない場合:
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;
別の方法を追加したいのですが。これは再帰的なクエリを使用します。これは他の回答では見たことがありません。 11gR2以降、Oracleでサポートされています。
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
それは分割文字で非常に柔軟です。 INSTR
の呼び出しで変更するだけです。
私は同じ問題を抱えていました、そしてxmltableは私を助けました:
SELECT ID、トリム(COLUMN_VALUE)テキストFROM t、xmltable(( '' '|| REPLACE(text、'、 '、' "、" ')||' "'))