Funkcja agregująca COLLECT – część pierwsza

W tym wpisie przyjrzymy się funkcji agregującej COLLECT. Funkcja ta przyjmuje jako argument kolumnę, zwracając w wyniku zagnieżdżoną tabelę dla każdej grupy, po której odbywa się grupowanie. Tabela ta zawiera wiersze złożone z wartości kolumny, która została podana jako argument. Wiersze zawarte w zagnieżdżonej tabeli, przynależą do grupy, dla której wywołana została funkcja (rys. 1.)  

Pełny opis i składnia funkcji dostępna jest w dokumentacji:  
https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/COLLECT.html#GUID-A0A74602-2A97-449B-A3EC-847D38D3DA90)

Przykładowy przypadek użycia 1różne poziomy agregacji w jednym zapytaniu. 

Funkcję COLLECT można użyć w celu zwrócenia w jednym zapytaniu wyników o różnych poziomach agregacji 

Na początku spójrzmy na przykład bez wykorzystania funkcji COLLECT. Chcemy obliczyć średnią pensję dla departamentu wg dwóch algorytmów: 

  • V1 – średnia pensja dla departamentu 
  • V2 – średnia pensja dla departamentu wyliczana ze średnich pensji dla poszczególnych stanowisk w ramach departamentu, np. jeśli w departamencie jest jeden kierownik, czterech sprzedawców i dwie osoby odpowiedzialne za administracje, to średnia V2 będzie średnią wyliczoną ze średniej pensji na stanowisku kierownika, średniej pensji na stanowisku sprzedawcy, średniej pensji na stanowisku administracyjnym.
select d.department_name
     ,to_char(avg(e.salary),'999G999D00') as dept_salary_avg_v1 
     ,(select to_char(avg(dept_job_salary_avg),'999G999D00')
                from (
             select d_i.department_id
                  ,e_i.job_id
                  ,avg(e_i.salary) as dept_job_salary_avg
                                 from hr.employees e_i 
                                  join hr.departments d_i on d_i.department_id = e_i.department_id
                                where d_i.department_id = d.department_id
                               group by d_i.department_id,e_i.job_id
             ) 
                 group by department_id 
       ) as dept_salary_avg_v2
from hr.employees e 
join hr.departments d on d.department_id = e.department_id
group by d.department_id, d.department_name
order by 1;

Jak możemy zrobić to przy użyciu funkcji COLLECT? W tym celu utwórzmy dwa typy bazodanowe, które są potrzebne w kolejnym zapytaniu: 

create or replace type job_salary as object (
job_title varchar2(35)
,salary number
)
/
create or replace type job_salary_tab as table of job_salary;
/

Następnie możemy skorzystać z funkcji COLLECT: 

select department_name
     ,to_char(dept_salary_avg_v1,'999G999D00') as dept_salary_avg_v1
     ,(select to_char(avg(avg(salary)),'999G999D00') 
                from table(dept_job_sal) group by job_title) as dept_salary_avg_v2
from (
             select d.department_name
          ,avg(salary) as dept_salary_avg_v1
          ,cast(collect(job_salary(job_title,salary)) as job_salary_tab) as dept_job_sal 
              from hr.employees e 
              join hr.jobs j on j.job_id = e.job_id
              join hr.departments d on d.department_id = e.department_id
              group by d.department_name
      )
order by 1;

Podobne rezultaty można oczywiście osiągnąć na różne sposoby. Niemniej funkcja COLLECT może być bardzo użyteczna np. gdy mamy duże skomplikowane zapytanie, w którym trudno szybko zmienić poziom agregacji. Dzięki wykorzystaniu funkcji COLLECT możemy zrobić to w stosunkowo prosty sposób, nie ingerujący mocno w główne zapytanie. 

 Przykładowy przypadek użycia 2: agregacja danych znakowych. 

Ciekawym przypadkiem użycia jest agregacja danych znakowych z wykorzystaniem typów i funkcji z Oracle Application Express (APEX). Tym sposobem uzyskujemy efekt działania funkcji LISTAGG wraz z możliwością skorzystania z opcji DISTINCT. Funkcja LISTAGG nie pozwala na użycie w niej DISTINCT. 

select d.department_name
     ,listagg(distinct j.job_title,', ') 
         within group (order by j.job_title) as job_titles_1 
     ,apex_string.join(cast(collect(distinct j.job_title) as apex_t_varchar2),', ') as job_titles_2
from hr.employees e 
join hr.jobs j on j.job_id = e.job_id
join hr.departments d on d.department_id = e.department_id
group by d.department_name;

Autorzy:
Tomasz Strawski
Rafał Stryjek

 

About Rafal Stryjek

My LinkedIn profile. Passionate about data. Data Warehousing Expert. Snowflake Data SuperHero & Oracle ACE Associate. International conferences Speaker: Snowflake Build Summit-BUILD.local 2021, Oracle Open World, DOAG, UKOUG, APEX World,.. Visit dataconsulting.pl website!
This entry was posted in Oracle, Oracle APEX, Oracle SQL, Oracle SQL Zaawansowany and tagged , . Bookmark the permalink.