2012-07-12 12 views
9

Chcę wybrać konkatenację kilku pól, ale z separatorem między nimi. Separator powinien być tam, tylko jeśli oba argumenty nie są puste.Oracle: Concat with delimiter, ale tylko wtedy, gdy oba operandy NIE są NULL

Tak więc dla rekordu z a='foo', b=NULL, c='bar', chcę uzyskać wynik abc='foo;bar' (nie 'foo;;bar').

Chciałbym mieć funkcję taką jak concat_sep(a, b, ';'), która dodaje tylko ";" pośrednie, jeśli zarówno a, jak i b nie są zerowe.

Oczywiście, można użyć nvl2 tak:

select 
    a, b, c, 
    substr(abc, 1, length(abc) - 1) as abc 
from 
    (select 
    a, b, c, 
    nvl2(a, a || ';', '') || nvl2(b, b || ';', '') || nvl2(c, c || ';', '') as abc 
    from 
    Table1) 

Ale jak widać, ten kod staje się lepki szybko, zwłaszcza, gdy masz więcej niż 3 kolumny i dałeś im nazwy sensowne zamiast a, b i c. ;-)

Nie mogłem znaleźć krótszego, łatwiejszego ani bardziej czytelnego sposobu, ale pomyślałem, że zapytam tutaj, zanim całkowicie zrezygnuję (lub tracę czas na pisanie takiej funkcji).

+0

wydaje się bardzo specyficznej logiki y chcesz: dlaczego pisanie własnej funkcji byłoby stratą czasu? – tbone

+0

Byłoby, gdyby okazało się, że już istnieje. :) – GolezTrol

+0

bez listy 11g wygląda na to, że będziesz musiał napisać własną. I patrząc na twoje komentarze, wydaje się, że napisałeś swój własny, więc jestem zdezorientowany, czy szukasz jakiejś funkcjonalności, której nie zapewnia twoja własna funkcja? Może przykład użycia, aby zobaczyć, jak zamierzasz użyć tego (mogę wymyślić kilka podejść) – tbone

Odpowiedz

6

Wiem, że używasz 10g, więc to nie zadziała. Ale dla kompletności, LISTAGG() obsługuje wartości "poprawnie". Do tego trzeba by zaktualizować do 11g2, choć:

-- Some sample data, roughly equivalent to yours 
with t as (
    select 'foo' as x from dual union all 
    select null  from dual union all 
    select 'bar'  from dual 
) 
-- Use the listagg aggregate function to join all values 
select listagg(x, ';') within group (order by rownum) 
from t; 

lub nieco bardziej zwięzłe, jeśli chcesz do listy kolumn z tabeli:

-- I use SYS.ORA_MINING_VARCHAR2_NT as a TABLE TYPE. Use your own, if you prefer 
select listagg(column_value, ';') within group (order by rownum) 
from table(ORA_MINING_VARCHAR2_NT('foo', null, 'bar')); 

lub przeciwko rzeczywistej tabeli:

select listagg(column_value, ';') 
     within group (order by rownum) 
from Table1 
cross join table(ORA_MINING_VARCHAR2_NT(Table1.a, Table1.b, Table1.c)) 
group by Table1.id; 

teraz nie jestem pewien, czy to jest tak dużo lepsze (bardziej czytelny) niż oryginalny przykład :-)

+0

Czuje się trochę obrzydliwie przekształcanie kolumn w tabelę, a następnie agregowanie. Chciałem to wypróbować, przynajmniej jako przykład uczenia się. :) Szkoda, że ​​ORA_MINING_VARCHAR2_NT również nie jest dostępny w 10g, szkoda, ponieważ napisałem już zamiennik LISTAGG na 10g: http://stackoverflow.com/a/7885793/511529 – GolezTrol

+0

Tak, icky jest po prostu początek. Był kiedyś nazywany 'DMSYS.ORA_MINING_VARCHAR2_NT'. [Ta odpowiedź] (http: // stackoverflow.com/a/8786893/521799) pokazuje, jak znaleźć inny typ tabeli/typu "SYS", który może odpowiadać twoim potrzebom. –

+2

Również nie działa. Najwyraźniej nie mam jeszcze funkcji eksploracji danych w mojej bazie danych. Będę o tym pamiętać, ale mam wrażenie, że nie powinienem używać tego rozwiązania w kodzie produkcyjnym. ;-) – GolezTrol

1

AFAIK, nie ma krótkiego sposobu na zrobienie tego.

W przeszłości, mam uciekają się do

SELECT a 
||  DECODE(b 
     ,  NULL, NULL 
     ,  ';' || b) 
||  DECODE(c 
     ,  NULL, NULL 
     ,  ';' || c) 
||  DECODE(d 
     ,  NULL, NULL 
     ,  ';' || d) 
... 
FROM table1 

ale to nie lepsze niż np.

+0

Rzeczywiście, to prawie to samo. Mimo to dziękuję. – GolezTrol