Consulta SQL para concatenar valores de coluna de várias linhas no Oracle


169

Seria possível construir SQL para concatenar valores de colunas de várias linhas?

A seguir, um exemplo:

Quadro A

PID
UMA
B
C

Quadro B

PQ SEQ Desc

A 1 Have
A 2 um bom
Um dia 3.
B 1 Bom trabalho.
C 1 sim
C 2 podemos 
C 3 do 
C 4 esse trabalho!

A saída do SQL deve ser -

PID Desc
Tenha um bom dia.
B Bom trabalho.
C Sim, podemos fazer este trabalho!

Então, basicamente, a coluna Desc para a tabela de saída é uma concatenação dos valores SEQ da Tabela B?

Alguma ajuda com o SQL?



Por favor, olhe para esta solução . Será útil para você.
Jineesh Uvantavida

Respostas:


237

Existem algumas maneiras, dependendo da versão que você possui - consulte a documentação do Oracle sobre técnicas de agregação de strings . Um muito comum é usar LISTAGG:

SELECT pid, LISTAGG(Desc, ' ') WITHIN GROUP (ORDER BY seq) AS description
FROM B GROUP BY pid;

Em seguida, junte-se a Apara escolher o pidsque deseja.

Nota: Fora da caixa, LISTAGGfunciona apenas corretamente com VARCHAR2colunas.


2
usando wm_concat () para Oracle 10g concatena o texto na ordem crescente do número de sequência delimitado por vírgulas.
jagamot

19

Há também uma XMLAGGfunção que funciona em versões anteriores à 11.2. Como não WM_CONCATé documentado e não é suportado pela Oracle , é recomendável não usá-lo no sistema de produção.

Com XMLAGGvocê pode fazer o seguinte:

SELECT XMLAGG(XMLELEMENT(E,ename||',')).EXTRACT('//text()') "Result" 
FROM employee_names

O que isso faz é

  • coloque os valores da enamecoluna (concatenados com vírgula) da employee_namestabela em um elemento xml (com a tag E)
  • extrair o texto deste
  • agregar o xml (concatená-lo)
  • chame a coluna resultante "Resultado"

O XMLAGG funciona no Oracle 12.2. Além disso, o XLMAGG permite concatanar seqüências de caracteres muito longas que o LISTAGG pode não possuir devido ao seu comprimento final.
Marco

13

Com a cláusula de modelo SQL:

SQL> select pid
  2       , ltrim(sentence) sentence
  3    from ( select pid
  4                , seq
  5                , sentence
  6             from b
  7            model
  8                  partition by (pid)
  9                  dimension by (seq)
 10                  measures (descr,cast(null as varchar2(100)) as sentence)
 11                  ( sentence[any] order by seq desc
 12                    = descr[cv()] || ' ' || sentence[cv()+1]
 13                  )
 14         )
 15   where seq = 1
 16  /

P SENTENCE
- ---------------------------------------------------------------------------
A Have a nice day
B Nice Work.
C Yes we can do this work!

3 rows selected.

Eu escrevi sobre isso aqui . E se você seguir o link para o thread OTN, encontrará mais alguns, incluindo uma comparação de desempenho.



8

Como a maioria das respostas sugere, LISTAGGé a opção óbvia. No entanto, um aspecto irritante LISTAGGé que, se o comprimento total da string concatenada exceder 4000 caracteres (limite para VARCHAR2SQL), será gerado o erro abaixo, que é difícil de gerenciar nas versões Oracle até 12.1

ORA-01489: o resultado da concatenação de cadeias é muito longo

Um novo recurso adicionado ao 12cR2 é a ON OVERFLOWcláusula de LISTAGG. A consulta incluindo esta cláusula seria semelhante a:

SELECT pid, LISTAGG(Desc, ' ' on overflow truncate) WITHIN GROUP (ORDER BY seq) AS desc
FROM B GROUP BY pid;

O acima irá restringir a saída a 4000 caracteres, mas não emitirá o ORA-01489erro.

Estas são algumas das opções adicionais da ON OVERFLOWcláusula:

  • ON OVERFLOW TRUNCATE 'Contd..' : Isso será exibido 'Contd..'no final da string (o padrão é ...)
  • ON OVERFLOW TRUNCATE '' : Isso exibirá os 4000 caracteres sem nenhuma sequência final.
  • ON OVERFLOW TRUNCATE WITH COUNT: Isso exibirá o número total de caracteres no final após os caracteres finais. Por exemplo: - ' ...(5512)'
  • ON OVERFLOW ERROR: Se você espera que a LISTAGGfalha com o ORA-01489erro (que é o padrão de qualquer maneira).

6

Para aqueles que precisam resolver esse problema usando o Oracle 9i (ou anterior), provavelmente será necessário usar o SYS_CONNECT_BY_PATH, pois o LISTAGG não está disponível.

Para responder ao OP, a seguinte consulta exibirá o PID da Tabela A e concatenará todas as colunas DESC da Tabela B:

SELECT pid, SUBSTR (MAX (SYS_CONNECT_BY_PATH (description, ', ')), 3) all_descriptions
FROM (
       SELECT ROW_NUMBER () OVER (PARTITION BY pid ORDER BY pid, seq) rnum, pid, description
       FROM (
              SELECT a.pid, seq, description
              FROM table_a a, table_b b
              WHERE a.pid = b.pid(+)
             )
      )
START WITH rnum = 1
CONNECT BY PRIOR rnum = rnum - 1 AND PRIOR pid = pid
GROUP BY pid
ORDER BY pid;

Também pode haver casos em que chaves e valores estão todos contidos em uma tabela. A consulta a seguir pode ser usada onde não há Tabela A e existe apenas a Tabela B:

SELECT pid, SUBSTR (MAX (SYS_CONNECT_BY_PATH (description, ', ')), 3) all_descriptions
FROM (
       SELECT ROW_NUMBER () OVER (PARTITION BY pid ORDER BY pid, seq) rnum, pid, description
       FROM (
              SELECT pid, seq, description
              FROM table_b
             )
      )
START WITH rnum = 1
CONNECT BY PRIOR rnum = rnum - 1 AND PRIOR pid = pid
GROUP BY pid
ORDER BY pid;

Todos os valores podem ser reordenados conforme desejado. Descrições concatenadas individuais podem ser reordenadas na cláusula PARTITION BY e a lista de PIDs pode ser reordenada na cláusula ORDER BY final.


Como alternativa: pode haver momentos em que você deseja concatenar todos os valores de uma tabela inteira em uma linha.

A idéia principal aqui é usar um valor artificial para o grupo de descrições a serem concatenadas.

Na consulta a seguir, a cadeia constante '1' é usada, mas qualquer valor funcionará:

SELECT SUBSTR (MAX (SYS_CONNECT_BY_PATH (description, ', ')), 3) all_descriptions
FROM (
       SELECT ROW_NUMBER () OVER (PARTITION BY unique_id ORDER BY pid, seq) rnum, description
       FROM (
              SELECT '1' unique_id, b.pid, b.seq, b.description
              FROM table_b b
             )
      )
START WITH rnum = 1
CONNECT BY PRIOR rnum = rnum - 1;

Descrições concatenadas individuais podem ser reordenadas na cláusula PARTITION BY.

Várias outras respostas nesta página também mencionaram essa referência extremamente útil: https://oracle-base.com/articles/misc/string-aggregation-techniques


3
  1. O LISTAGG oferece o melhor desempenho se a classificação for obrigatória (00: 00: 05.85)

    SELECT pid, LISTAGG(Desc, ' ') WITHIN GROUP (ORDER BY seq) AS description FROM B GROUP BY pid;

  2. COLLECT oferece o melhor desempenho se a classificação não for necessária (00: 00: 02.90):

    SELECT pid, TO_STRING(CAST(COLLECT(Desc) AS varchar2_ntt)) AS Vals FROM B GROUP BY pid;

  3. COLECIONAR com a encomenda é um pouco mais lento (00: 00: 07.08):

    SELECT pid, TO_STRING(CAST(COLLECT(Desc ORDER BY Desc) AS varchar2_ntt)) AS Vals FROM B GROUP BY pid;

Todas as outras técnicas foram mais lentas.


1
Seria útil elaborar sua resposta.
22615 Jon Junrell

John, eu não queria repetir a partir do artigo, mas, em resumo, estes são os resultados: 1. O LISTAGG oferece o melhor desempenho se a classificação for obrigatória (00: 00: 05.85) 2. COLLECT oferece o melhor desempenho se a classificação não for necessário (00: 00: 02.90): SELECT pid, TO_STRING (CAST (COLLECT (Desc) AS varchar2_ntt)) AS Vals FROM B GROUP BY pid; 3. COLLECT com a ordenação é um pouco mais lento (00: 00: 07.08): SELECT pid, TO_STRING (CAST (COLLECT (DESC ORDER BY Desc) AS varchar2_ntt)) AS Vals FROM B GROUP BY pid; Todas as outras técnicas foram mais lentas.
Misho

1
Você pode apenas editar sua resposta para incluir informações relevantes.
Jon Surrell

Eu estava atrasado na edição e foi por isso que o adicionei novamente. Desculpe, eu sou novo aqui e estou começando a pegar o jeito.
Misho

1

Antes de executar uma consulta de seleção, execute o seguinte:

SET SERVEROUT ON SIZE 6000

SELECT XMLAGG(XMLELEMENT(E,SUPLR_SUPLR_ID||',')).EXTRACT('//text()') "SUPPLIER" 
FROM SUPPLIERS;

-1

Tente este código:

 SELECT XMLAGG(XMLELEMENT(E,fieldname||',')).EXTRACT('//text()') "FieldNames"
    FROM FIELD_MASTER
    WHERE FIELD_ID > 10 AND FIELD_AREA != 'NEBRASKA';

-3

Na seleção onde deseja sua concatenação, chame uma função SQL.

Por exemplo:

select PID, dbo.MyConcat(PID)
   from TableA;

Em seguida, para a função SQL:

Function MyConcat(@PID varchar(10))
returns varchar(1000)
as
begin

declare @x varchar(1000);

select @x = isnull(@x +',', @x, @x +',') + Desc
  from TableB
    where PID = @PID;

return @x;

end

A sintaxe do cabeçalho da função pode estar errada, mas o princípio funciona.


Isso é inválido para Oracle
a_horse_with_no_name 11/11
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.