Dzielenie łańcucha na wiele wierszy w Oracle

104

Wiem, że do pewnego stopnia odpowiedziano na to za pomocą PHP i MYSQL, ale zastanawiałem się, czy ktoś mógłby mnie nauczyć najprostszego podejścia do dzielenia ciągu (rozdzielanego przecinkami) na wiele wierszy w Oracle 10g (najlepiej) i 11g.

Tabela przedstawia się następująco:

Name | Project | Error 
108    test      Err1, Err2, Err3
109    test2     Err1

Chcę utworzyć:

Name | Project | Error
108    Test      Err1
108    Test      Err2 
108    Test      Err3 
109    Test2     Err1

Widziałem kilka potencjalnych rozwiązań dotyczących stosu, jednak dotyczyły one tylko jednej kolumny (będącej ciągiem rozdzielanym przecinkami). Każda pomoc byłaby bardzo mile widziana.

Marshalllaw
źródło
2
Na przykładach z wykorzystaniem REGEXP, XMLTABLEoraz MODELklauzuli zobaczyć Podział rozdzielany przecinkami ciągi w tabeli przy użyciu Oracle SQL
Lalit Kumar B

Odpowiedzi:

121

To może być ulepszony sposób (również w przypadku wyrażenia regularnego i łączenia przez):

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

EDYCJA : Oto proste (jak w przypadku „nie dogłębnego”) wyjaśnienie zapytania.

  1. length (regexp_replace(t.error, '[^,]+')) + 1używa regexp_replacedo wymazania wszystkiego, co nie jest ogranicznikiem (w tym przypadku przecinka) i length +1do obliczenia liczby elementów (błędów).
  2. select level from dual connect by level <= (...)Wykorzystuje hierarchicznego zapytania w celu utworzenia kolumny coraz odnalezione od 1 do całkowitej liczby błędów.

    Zapowiedź:

    select level, length (regexp_replace('Err1, Err2, Err3', '[^,]+'))  + 1 as max 
    from dual connect by level <= length (regexp_replace('Err1, Err2, Err3', '[^,]+'))  + 1
  3. table(cast(multiset(.....) as sys.OdciNumberList)) wykonuje niektóre odlewania typów wyroczni.
    • W cast(multiset(.....)) as sys.OdciNumberListtransformat kilka zbiorów (jeden zbiór dla każdego rzędu w pierwotnym zestawie danych) w jeden zbiór cyfr, OdciNumberList.
    • table()Funkcja przekształca zbiór do wynikowego.
  4. FROMbez sprzężenia tworzy sprzężenie krzyżowe między zbiorem danych a zestawem wielozbiorowym. W rezultacie wiersz w zestawie danych z 4 dopasowaniami powtórzy się 4 razy (z rosnącą liczbą w kolumnie o nazwie „wartość_kolumny”).

    Zapowiedź:

    select * from 
    temp t,
    table(cast(multiset(select level from dual connect by  level <= length (regexp_replace(t.error, '[^,]+'))  + 1) as sys.OdciNumberList)) levels
  5. trim(regexp_substr(t.error, '[^,]+', 1, levels.column_value))używa column_valuejako parametru nth_appearance / ocurrence dla regexp_substr.
  6. Możesz dodać kilka innych kolumn ze swojego zestawu danych ( t.name, t.projectna przykład), aby ułatwić wizualizację.

Niektóre odniesienia do dokumentów Oracle:

Nefreo
źródło
7
Strzec się! Wyrażenie regularne formatu '[^,]+'do analizowania ciągów nie zwraca prawidłowego elementu, jeśli na liście znajduje się element pusty. Zobacz tutaj, aby uzyskać więcej informacji: stackoverflow.com/questions/31464275/…
Gary_W
13
od 11g można użyć regexp_count(t.error, ',')zamiast length (regexp_replace(t.error, '[^,]+')), co może przynieść kolejną poprawę wydajności
Štefan Oravec
1
485 sekund przy „normalnym” trybie CONNECT BY. W ten sposób 0,296 sekundy. Rządzisz! Teraz wszystko, co muszę zrobić, to zrozumieć, jak to działa. :-)
Bob Jarvis - Przywróć Monikę
@BobJarvis dodał zmianę, aby wyjaśnić, co robi. Mile widziane poprawki ortograficzne / gramatyczne.
Nefreo
„Zaakceptowana odpowiedź ma słabe wyniki” - jaka jest akceptowana odpowiedź w tym temacie? Użyj linków, aby odnieść się do innego postu.
0xdb
28

wyrażenia regularne to cudowna rzecz :)

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
Andrey Khmelev
źródło
1
cześć, czy możesz mi wyjaśnić, dlaczego powyższe zapytanie daje zduplikowane wiersze, jeśli nie użyłem odrębnego słowa kluczowego w zapytaniu
Jagadeesh G
2
To zapytanie jest bezużyteczne z powodu @JagadeeshG, szczególnie w przypadku dużych tabel.
Michael-O
3
Niezwykle powolny, poniżej jest lepsza odpowiedź
MoreCoffee
Powodem tej powolności jest to, że każda kombinacja Names jest połączona, co można zobaczyć, jeśli usuniesz distinct. Niestety dodanie and Name = prior Namedo connect byklauzuli powoduje ORA-01436: CONNECT BY loop in user data.
mik
Możesz uniknąć ORA-01436błędu, dodając AND name = PRIOR name(lub jakikolwiek inny klucz podstawowy) i AND PRIOR SYS_GUID() IS NOT NULL
David Faber
28

Istnieje ogromna różnica między tymi dwoma:

  • dzielenie pojedynczego rozdzielanego ciągu
  • dzielenie ciągów rozdzielanych na wiele wierszy w tabeli.

Jeśli nie ograniczysz wierszy, klauzula CONNECT BY wygeneruje wiele wierszy i nie da pożądanego wyniku.

Oprócz wyrażeń regularnych używa się kilku innych alternatyw:

  • XMLTable
  • Klauzula MODEL

Ustawiać

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>

Korzystanie z 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>

Korzystanie z klauzuli 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>
Lalit Kumar B.
źródło
1
Czy możesz wyjaśnić więcej, dlaczego musi być, ('"' || REPLACE(text, ',', '","') || '"')a nawiasów nie można usunąć? Dokumenty Oracle ([ docs.oracle.com/database/121/SQLRF/functions268.htm ) nie są dla mnie jasne. Czy to jest XQuery_string?
Betlista
@Betlista jest to wyrażenie XQuery.
Lalit Kumar B,
Rozwiązanie XMLTABLE z jakiegoś powodu nieustannie nie wyświetla ostatniego wpisu dla wierszy o różnej długości. Na przykład. wiersz1: 3 słowa; wiersz2: 2 słowa, wiersz3: 1 słowo; row4: 2 words, row5: 1 word - nie wyświetla ostatniego słowa. Kolejność wierszy nie ma znaczenia.
Gnudiff
7

Kilka innych przykładów tego samego:

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
/

Można również użyć 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

Sztuka
źródło
Należy pamiętać, że comma_to_table()działa tylko z tokenami, które pasują do konwencji nazewnictwa obiektów bazy danych Oracle. Rzuci się na strunę, jak '123,456,789'na przykład.
APC
7

Chciałbym zaproponować inne podejście przy użyciu funkcji tabeli PIPELINED. Jest to trochę podobne do techniki XMLTABLE, z tym wyjątkiem, że udostępniasz własną niestandardową funkcję do dzielenia ciągu znaków:

-- 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));

Wyniki:

      NAME PROJECT    ERROR
---------- ---------- --------------------
       108 test       Err1
       108 test       Err2
       108 test       Err3
       109 test2      Err1

Problem z tego typu podejściem polega na tym, że często optymalizator nie zna liczności funkcji tabeli i będzie musiał zgadnąć. Może to być potencjalnie szkodliwe dla planów wykonania, więc to rozwiązanie można rozszerzyć, aby zapewnić statystyki wykonania dla optymalizatora.

Możesz zobaczyć to oszacowanie optymalizatora, uruchamiając EXPLAIN PLAN dla powyższego zapytania:

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 |
----------------------------------------------------------------------------------------------

Mimo że kolekcja ma tylko 3 wartości, optymalizator oszacował dla niej 8168 wierszy (wartość domyślna). Na początku może się to wydawać nieistotne, ale optymalizatorowi może wystarczyć podjęcie decyzji o planie nieoptymalnym.

Rozwiązaniem jest użycie rozszerzeń optymalizatora w celu zapewnienia statystyk dla kolekcji:

-- 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;

Testowanie powstałego planu wykonania:

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 |
----------------------------------------------------------------------------------------------

Jak widać, liczność na powyższym planie nie jest już domyślną wartością 8196. Wciąż nie jest to poprawne, ponieważ przekazujemy do funkcji kolumnę zamiast literału ciągu.

Pewne poprawki w kodzie funkcji byłyby konieczne, aby dać dokładniejsze oszacowanie w tym konkretnym przypadku, ale myślę, że ogólna koncepcja jest tutaj dość dobrze wyjaśniona.

Funkcja str2tbl użyta w tej odpowiedzi została pierwotnie opracowana przez Toma Kyte'a: https://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:110612348061

Koncepcję kojarzenia statystyk z typami obiektów można dokładniej zbadać, czytając ten artykuł: http://www.oracle-developer.net/display.php?id=427

Opisana tutaj technika działa w 10g +.

Daniela Petruzalek
źródło
4

REGEXP_COUNT nie został dodany do Oracle 11i. Oto rozwiązanie Oracle 10g, przejęte z rozwiązania Art.

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;
dureta
źródło
Jak mogę dodać filtr do tego, powiedzmy, że chcę filtrować tylko z nazwą = '108'. Próbowałem dodać gdzie po klauzuli from, ale skończyło się na duplikatach.
DRTauli
4

Począwszy od Oracle 12c możesz użyć JSON_TABLEi JSON_ARRAY:

CREATE TABLE tab(Name, Project, Error) AS
SELECT 108,'test' ,'Err1, Err2, Err3' FROM dual UNION 
SELECT 109,'test2','Err1'             FROM dual;

I zapytanie:

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;

Wynik:

┌──────┬─────────┬──────────────────┬──────┐
 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 
└──────┴─────────┴──────────────────┴──────┘

db <> fiddle demo

Łukasz Szozda
źródło
1
Przyznaję, że to sprytna sztuczka, ale szczerze mówiąc, zdziwiłaby mnie, gdybym natknęła się na nią w bazie kodu.
APC
@APC To jest tylko pokaz tego, co jest możliwe dzięki SQL. Gdybym miał użyć takiego kodu w swojej bazie to na pewno owinąłbym go w funkcję lub zostawił rozszerzony komentarz :)
Łukasz Szozda
Oczywiście. Tyle, że ten wątek jest jednym z bardziej popularnych hitów w zakresie tokenizacji stringów w Oracle, więc myślę, że powinniśmy uwzględnić zastrzeżenia dotyczące bardziej egzotycznych rozwiązań, aby chronić niewinnych przed sobą :)
APC
3

Oto alternatywna implementacja wykorzystująca XMLTABLE, która umożliwia rzutowanie na różne typy danych:

select 
  xmltab.txt
from xmltable(
  'for $text in tokenize("a,b,c", ",") return $text'
  columns 
    txt varchar2(4000) path '.'
) xmltab
;

... lub jeśli ciągi rozdzielane są przechowywane w jednym lub kilku wierszach tabeli:

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
;
silentsurfer
źródło
Myślę, że to rozwiązanie działa dla Oracle 11.2.0.3 i nowszych wersji.
APC
2

Chciałbym dodać inną metodę. Ten używa zapytań rekurencyjnych, czego nie widziałem w innych odpowiedziach. Jest obsługiwany przez Oracle od 11gR2.

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

Jest dość elastyczny dzięki charakterowi rozszczepiania. Po prostu zmień to w INSTRrozmowach.

Thomas Tschernich
źródło
2

Bez używania connect by lub 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;
Ilya Kharlamov
źródło
1

Miałem ten sam problem i xmltable mi pomogło:

SELECT id, trim (COLUMN_VALUE) tekst FROM t, xmltable (('"' || REPLACE (tekst, ',', '", "') || '"'))

Volkov Maxim
źródło
0

W Oracle 11g i nowszych można użyć rekursywnego zapytania podrzędnego i prostych funkcji łańcuchowych (które mogą być szybsze niż wyrażenia regularne i skorelowane hierarchiczne zapytania podrzędne):

Konfiguracja Oracle :

CREATE TABLE table_name ( name, project, error ) as
 select 108, 'test',  'Err1, Err2, Err3' from dual union all
 select 109, 'test2', 'Err1'             from dual;

Zapytanie :

WITH table_name_error_bounds ( name, project, error, start_pos, end_pos ) AS (
  SELECT name,
         project,
         error,
         1,
         INSTR( error, ', ', 1 )
  FROM   table_name
UNION ALL
  SELECT name,
         project,
         error,
         end_pos + 2,
         INSTR( error, ', ', end_pos + 2 )
  FROM   table_name_error_bounds
  WHERE  end_pos > 0
)
SELECT name,
       project,
       CASE end_pos
       WHEN 0
       THEN SUBSTR( error, start_pos )
       ELSE SUBSTR( error, start_pos, end_pos - start_pos )
       END AS error
FROM   table_name_error_bounds

Wyjście :

NAZWA | PROJEKT | BŁĄD
---: | : ------ | : ----
 108 | test | Err1
 109 | test2 | Err1
 108 | test | Err2
 108 | test | Err3

db <> skrzypce tutaj

MT0
źródło
-1

Użyłem funkcji DBMS_UTILITY.comma_to _table, która faktycznie działa z kodem w następujący sposób

declare
l_tablen  BINARY_INTEGER;
l_tab     DBMS_UTILITY.uncl_array;
cursor cur is select * from qwer;
rec cur%rowtype;
begin
open cur;
loop
fetch cur into rec;
exit when cur%notfound;
DBMS_UTILITY.comma_to_table (
     list   => rec.val,
     tablen => l_tablen,
     tab    => l_tab);
FOR i IN 1 .. l_tablen LOOP
    DBMS_OUTPUT.put_line(i || ' : ' || l_tab(i));
END LOOP;
end loop;
close cur;
end; 

użyłem własnych nazw tabel i kolumn

Smart003
źródło
5
Należy pamiętać, że comma_to_table()działa tylko z tokenami, które pasują do konwencji nazewnictwa obiektów bazy danych Oracle. Rzuci się na strunę, jak '123,456,789'na przykład.
APC
czy możemy zaimplementować za pomocą tabel tymczasowych?
Smart003
1
Umm, biorąc pod uwagę wszystkie inne działające rozwiązania, dlaczego mielibyśmy chcieć używać tabel tymczasowych, które wiążą się z ogromnym narzutem materializacji danych?
APC