Oracle: Concat с разделителем, но только если оба операнда NOT NULL

Я хочу выбрать конкатенацию нескольких полей, но с разделителем между ними. Разделитель должен присутствовать только в том случае, если оба операнда не являются нулевыми.

Итак, для записи с a='foo', b=NULL, c='bar', я хочу получить результат abc='foo;bar' (not 'foo;;bar').

Я хотел бы иметь такую ​​функцию, как concat_sep(a, b, ';'), которая добавляет только ';' inbetween, если оба и и b не являются нулевыми.

Конечно, я могу использовать nvl2 следующим образом:

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)

Но, как вы можете видеть, этот код скоро становится засоренным, особенно когда вы получили более трех столбцов, и вы дали им разумные имена вместо a, b и c.; -)

Я не мог найти более короткий, легкий или более читаемый способ, но я думал, что попрошу здесь, прежде чем полностью отказаться (или тратить время на создание такой функции).

Ответ 1

Я знаю, что вы используете 10g, так что это не сработает. Но для полноты LISTAGG() обрабатывает значения NULL "правильно". Для этого вам нужно обновить до 11g2, хотя:

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

Или немного более кратким, если вы хотите перечислить столбцы из таблицы:

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

Или против фактической таблицы:

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;

Теперь я не уверен, что это намного лучше (более читаемо), чем ваш оригинальный пример: -)

Ответ 2

AFAIK, нет лаконичного способа сделать это.

В прошлом я прибегал к

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

но это не лучше вашего примера.

Ответ 3

select trim(';' from REGEXP_REPLACE (a || ';' || b || ';' || c , ';+' , ';')) abc 
from Table1