Zapytanie SQL do łączenia wartości kolumn z wielu wierszy w Oracle

169

Czy byłoby możliwe skonstruowanie kodu SQL w celu konkatenacji wartości kolumn z wielu wierszy?

Oto przykład:

Tabela A

PID
ZA
b
do

Tabela B.

PID SEQ Desc

A 1 mieć
A 2 miło
3 dni.
B 1 Dobra robota.
C 1 Tak
C 2 możemy 
C 3 zrobić 
C 4 ta praca!

Wynik SQL powinien być -

PID Desc
Miłego dnia.
B Dobra robota.
C Tak, możemy wykonać tę pracę!

Więc w zasadzie kolumna Opis dla tabeli wyjściowej jest konkatenacją wartości SEQ z tabeli B?

Jakaś pomoc dotycząca SQL?

jagamot
źródło
Zobacz na przykład: halisway.blogspot.com/2006/08/…
Andomar
Proszę spojrzeć na to rozwiązanie . To ci się przyda.
Jineesh Uvantavida,

Odpowiedzi:

237

Istnieje kilka sposobów w zależności od posiadanej wersji - zapoznaj się z dokumentacją Oracle dotyczącą technik agregacji ciągów . Bardzo często używa się LISTAGG:

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

Następnie dołącz do, Aaby wybrać, pidsco chcesz.

Uwaga: po wyjęciu z pudełka LISTAGGdziała poprawnie tylko z VARCHAR2kolumnami.

Lou Franco
źródło
2
użycie wm_concat () dla Oracle 10g łączy tekst w porządku rosnącym według numeru sekwencyjnego rozdzielonego przecinkami, czy możemy zrobić malejąco rozdzielony czymś innym?
jagamot
19

Istnieje również XMLAGGfunkcja, która działa w wersjach wcześniejszych niż 11.2. Ponieważ WM_CONCATjest nieudokumentowany i nie jest obsługiwany przez Oracle , zaleca się, aby nie używać go w systemie produkcyjnym.

Za pomocą XMLAGGmożesz wykonać następujące czynności:

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

Co to robi

  • umieść wartości enamekolumny (połączone przecinkiem) z employee_namestabeli w elemencie xml (z tagiem E)
  • wyodrębnij tekst tego
  • zagreguj xml (połącz go)
  • nazwij wynikową kolumnę „Wynik”
Piotr
źródło
XMLAGG działa na Oracle 12.2. Ponadto XLMAGG pozwala łączyć bardzo długie struny, których LISTAGG może nie mieć ze względu na ich końcową długość.
Marco
13

Z klauzulą ​​modelu 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.

Pisałem o tym tutaj . A jeśli klikniesz link do wątku OTN, znajdziesz więcej, w tym porównanie wydajności.

Rob van Wijk
źródło
8

Jak sugeruje większość odpowiedzi, LISTAGGjest to opcja oczywista. Jednak jednym irytującym aspektem LISTAGGjest to, że jeśli całkowita długość łączonego ciągu przekracza 4000 znaków (limit dla VARCHAR2SQL), generowany jest poniższy błąd, który jest trudny do zarządzania w wersjach Oracle do 12.1

ORA-01489: wynik konkatenacji ciągów znaków jest zbyt długi

Nową funkcją dodaną w 12cR2 jest ON OVERFLOWklauzula LISTAGG. Zapytanie zawierające tę klauzulę wyglądałoby następująco:

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

Powyższe ograniczy wynik do 4000 znaków, ale nie spowoduje zgłoszenia ORA-01489błędu.

Oto niektóre z dodatkowych opcji ON OVERFLOWklauzuli:

  • ON OVERFLOW TRUNCATE 'Contd..' : Wyświetli się 'Contd..'na końcu ciągu (domyślnie ...)
  • ON OVERFLOW TRUNCATE '' : Spowoduje to wyświetlenie 4000 znaków bez żadnego łańcucha kończącego.
  • ON OVERFLOW TRUNCATE WITH COUNT: Wyświetli całkowitą liczbę znaków na końcu po znakach kończących. Np .: - ' ...(5512)'
  • ON OVERFLOW ERROR: Jeśli spodziewasz LISTAGGsię, że ORA-01489błąd zakończy się niepowodzeniem (co i tak jest domyślne).
Kaushik Nayak
źródło
6

Dla tych, którzy muszą rozwiązać ten problem za pomocą Oracle 9i (lub wcześniejszej), prawdopodobnie będziesz musiał użyć SYS_CONNECT_BY_PATH, ponieważ LISTAGG nie jest dostępny.

Aby odpowiedzieć na OP, następujące zapytanie wyświetli PID z tabeli A i połączy wszystkie kolumny DESC z tabeli 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;

Mogą również istnieć przypadki, w których wszystkie klucze i wartości są zawarte w jednej tabeli. Poniższego zapytania można użyć, gdy nie ma tabeli A i istnieje tylko 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;

Wszystkie wartości można dowolnie zmienić. Poszczególne konkatenowane opisy można zmienić w klauzuli PARTITION BY, a listę PID można zmienić w ostatecznej klauzuli ORDER BY.


Alternatywnie: może się zdarzyć, że będziesz chciał połączyć wszystkie wartości z całej tabeli w jeden wiersz.

Kluczową ideą jest tutaj użycie sztucznej wartości dla grupy opisów, które mają być łączone.

W poniższym zapytaniu zostanie użyty stały ciąg „1”, ale każda wartość będzie działać:

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;

Poszczególne połączone opisy można zmienić w klauzuli PARTITION BY.

Kilka innych odpowiedzi na tej stronie również wspomniało o tym niezwykle pomocnym źródle: https://oracle-base.com/articles/misc/string-aggregation-techniques

JonathanDavidArndt
źródło
3
  1. LISTAGG zapewnia najlepszą wydajność, jeśli sortowanie jest koniecznością (00: 00: 05.85)

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

  2. COLLECT zapewnia najlepszą wydajność, jeśli sortowanie nie jest potrzebne (00: 00: 02.90):

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

  3. ZBIERZ przy zamawianiu jest nieco wolniejszy (00: 00: 07.08):

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

Wszystkie inne techniki były wolniejsze.

Misho
źródło
1
Dobrze byłoby rozwinąć swoją odpowiedź.
Jon Surrell
John, nie chciałem powtarzać tego artykułu, ale w skrócie są to wyniki: 1. LISTAGG zapewnia najlepszą wydajność, jeśli sortowanie jest koniecznością (00: 00: 05.85) 2. COLLECT zapewnia najlepszą wydajność, jeśli sortowanie nie jest potrzebne (00: 00: 02.90): SELECT pid, TO_STRING (CAST (COLLECT (Desc) AS varchar2_ntt)) AS Vals FROM B GROUP BY pid; 3. COLLECT z porządkowaniem jest nieco wolniejszy (00: 00: 07.08): SELECT pid, TO_STRING (CAST (COLLECT (Desc ORDER BY Desc) AS varchar2_ntt)) AS Vals FROM B GROUP BY pid; Wszystkie inne techniki były wolniejsze.
Misho
1
Możesz po prostu zmodyfikować swoją odpowiedź, aby zawierała odpowiednie informacje.
Jon Surrell
Byłem za późno w edycji i dlatego dodałem go ponownie. Przepraszam, jestem tutaj nowy i dopiero zaczynam to rozumieć.
Misho
1

Przed uruchomieniem zapytania wybierającego uruchom to:

SET SERVEROUT ON SIZE 6000

SELECT XMLAGG(XMLELEMENT(E,SUPLR_SUPLR_ID||',')).EXTRACT('//text()') "SUPPLIER" 
FROM SUPPLIERS;
user2865810
źródło
-1

Wypróbuj ten kod:

 SELECT XMLAGG(XMLELEMENT(E,fieldname||',')).EXTRACT('//text()') "FieldNames"
    FROM FIELD_MASTER
    WHERE FIELD_ID > 10 AND FIELD_AREA != 'NEBRASKA';
Krishnakumar MD Amain Infotech
źródło
-3

W zaznacz, gdzie chcesz umieścić konkatenację, wywołaj funkcję SQL.

Na przykład:

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

Następnie dla funkcji 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

Składnia nagłówka funkcji może być nieprawidłowa, ale zasada działa.

user5473005
źródło
To jest nieprawidłowe dla Oracle
a_horse_with_no_name