2012-07-12 24 views
9

Quiero seleccionar una concatenación de un par de campos, pero con un separador entre ellos. El separador solo debe estar allí si ambos operandos no son nulos. Para obtener un registro con a='foo', b=NULL, c='bar', quiero obtener el resultado abc='foo;bar' (no).Oracle: Concat con delimitador, pero solo si ambos operandos NO SON NULOS

Me gustaría tener una función como concat_sep(a, b, ';') que solo agrega el ';' entremedio si tanto a como b no son nulos.

Por supuesto, puedo usar NVL2 así:

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) 

Pero como se puede ver, este código se convierte en cloggy pronto, sobre todo cuando se tiene más de 3 columnas y se les ha dado nombres sensibles vez de a, by c. ;-)

No pude encontrar una manera más corta, más fácil o más fácil de leer, pero pensé en preguntar aquí antes de renunciar por completo (o perder el tiempo escribiendo esa función yo mismo).

+0

parece como muy específica y lógica o quieres: ¿por qué escribir tu propia función sería una pérdida de tiempo? – tbone

+0

Sería si ya hubiera uno. :) – GolezTrol

+0

sin 11g listagg parece que tendrá que escribir el suyo. Y al mirar tus comentarios, parece que tu escribiste el tuyo, así que estoy confundido, ¿estás buscando alguna funcionalidad que tu propia función no brinde? Tal vez un ejemplo de caso de uso para ver cómo planeas usar esto (puedo pensar en algunos enfoques) – tbone

Respuesta

6

Sé que está usando 10g, por lo que no funcionará. Pero para completar, LISTAGG() maneja los valores NULL "correctamente". Para que usted tendría que actualizar a 11g2, sin embargo:

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

O un poco más sucinta, si desea una lista de columnas de una tabla:

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

o en contra de una tabla real:

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; 

ahora no estoy seguro si esto es mucho mejor (más legible) que su original ejemplo :-)

+0

Se siente un poco asqueroso transformar columnas en una tabla y luego agregar eso. Quería probarlo, al menos como un ejemplo de aprendizaje. :) Lástima que ORA_MINING_VARCHAR2_NT también parece no estar disponible en 10g, lo cual es una lástima porque, ya escribí un reemplazo LISTAGG para 10g: http://stackoverflow.com/a/7885793/511529 – GolezTrol

+0

Sí, es asqueroso el principio. Solía ​​llamarse 'DMSYS.ORA_MINING_VARCHAR2_NT'. [Esta respuesta] (http: // stackoverflow.com/a/8786893/521799) muestra cómo puede encontrar otro tipo 'tabla/varray 'SYS' que pueda adaptarse a sus necesidades –

+2

Tampoco funciona. Aparentemente aún no tengo las funciones de minería de datos en mi base de datos. Lo tendré en cuenta, pero tengo la sensación de que no debería usar esta solución en el código de producción de todos modos. ;-) – GolezTrol

1

AFAIK, no hay una forma concisa de hacer esto.

En el pasado, he recurrido a

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

pero eso no es mejor que su ejemplo.

+0

De hecho, eso es más o menos lo mismo. Gracias de todos modos. – GolezTrol

Cuestiones relacionadas