Utility of List Aggregation Function
28 Jun 2020
The list aggregation function in SQL is a very handy function for consolidating data from multiple rows in data based on a grouping variable. Be sure to try out this function using Oracle’s live SQL editor SQL Worksheet tool.
With Oracle PL/SQL you there is a built-in function for list aggregation with details found in the reference documentation.
This entry will show an example generating a CSV list of each of the salaries based on Job IDs. For this example we will use Oracle’s built in HR schema Employee table.
Syntax
LISTAGG(COLUMN_NAME, ‘.,delimiter’) WITHIN GROUP(ORDER BY GROUPING_VAR1, GROUPUING_VAR2,..)
Followed eventually by a grouping clause:
GROUP BY GROUPING_VAR1, GROUPUING_VAR2,..
Steps
1. Explore the data
  SELECT FIRST_NAME, LAST_NAME, JOB_ID, SALARY FROM Employees;
  | FIRST_NAME | LAST_NAME | JOB_ID | SALARY | 
|---|---|---|---|
| Steven | King | AD_PRES | 24000 | 
| Neena | Kochhar | AD_VP | 17000 | 
| Lex | De Haan | AD_VP | 17000 | 
| Alexander | Hunold | IT_PROG | 9000 | 
| Bruce | Ernst | IT_PROG | 6000 | 
| David | Austin | IT_PROG | 4800 | 
| Valli | Pataballa | IT_PROG | 4800 | 
| ... | ... | ... | ... | 
2. Run a query to aggregate the salaries grouped by the job ID. The delimiter used can vary but commas or semi-colons are very readable. When joining text spaces can also be used. .
  SELECT
     JOB_ID,
     LISTAGG(SALARY, ',') WITHIN GROUP (ORDER BY JOB_ID) "Salaries"
  FROM HR.EMPLOYEES
  GROUP BY JOB_ID;
  | JOB_ID | Salaries | 
|---|---|
| AC_ACCOUNT | 8300 | 
| AC_MGR | 12008 | 
| AD_ASST | 4400 | 
| AD_PRES | 24000 | 
| AD_VP | 17000,17000 | 
| FI_ACCOUNT | 6900,7700,7800,8200,9000 | 
| FI_MGR | 12008 | 
| HR_REP | 6500 | 
| IT_PROG | 4200,4800,4800,6000,9000 | 
| MK_MAN | 13000 | 
| MK_REP | 6000 | 
| PR_REP | 10000 | 
| PU_CLERK | 2500,2600,2800,2900,3100 | 
| PU_MAN | 11000 | 
| SA_MAN | 10500,11000,12000,13500,14000 | 
| SA_REP | 10000,10000,10000,10500,11000,11500,6100,6200,6200,6400,6800,7000,7000,7000,7200,7300,7400,7500,7500,8000,8000,8400,8600,8800,9000,9000,9500,9500,9500,9600 | 
| SH_CLERK | 2500,2500,2600,2600,2800,2800,2900,3000,3000,3100,3100,3200,3200,3400,3600,3800,3900,4000,4100,4200 | 
| ST_CLERK | 2100,2200,2200,2400,2400,2500,2500,2500,2600,2700,2700,2800,2900,3100,3200,3200,3300,3300,3500,3600 | 
| ST_MAN | 5800,6500,7900,8000,8200 | 
3. Sometimes it is also useful to only select only two or three of the items being consolidated. To do this use the PARTITION function combined with a WHERE filter. The below query limits the selection to only 3 salaries per Job ID.
SELECT * FROM (
  SELECT
    ROW_NUMBER() OVER(PARTITION BY JOB_ID ORDER BY JOB_ID) "NUM",
    E.*,
  FROM HR.EMPLOYEES E
  )
WHERE NUM <= 3 ;| NUM | EMPLOYEE_ID | FIRST_NAME | LAST_NAME | PHONE_NUMBER | HIRE_DATE | JOB_ID | SALARY | COMMISSION_PCT | MANAGER_ID | DEPARTMENT_ID | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 206 | William | Gietz | WGIETZ | 515.123.8181 | 07-JUN-02 | AC_ACCOUNT | 8300 | - | 205 | 110 | 
| 1 | 205 | Shelley | Higgins | SHIGGINS | 515.123.8080 | 07-JUN-02 | AC_MGR | 12008 | - | 101 | 110 | 
| 1 | 200 | Jennifer | Whalen | JWHALEN | 515.123.4444 | 17-SEP-03 | AD_ASST | 4400 | - | 101 | 10 | 
| 1 | 100 | Steven | King | SKING | 515.123.4567 | 17-JUN-03 | AD_PRES | 24000 | - | - | 90 | 
| 1 | 101 | Neena | Kochhar | NKOCHHAR | 515.123.4568 | 21-SEP-05 | AD_VP | 17000 | - | 100 | 90 | 
| 2 | 102 | Lex | De Haan | LDEHAAN | 515.123.4569 | 13-JAN-01 | AD_VP | 17000 | - | 100 | 90 | 
| 1 | 109 | Daniel | Faviet | DFAVIET | 515.124.4169 | 16-AUG-02 | FI_ACCOUNT | 9000 | - | 108 | 100 | 
| 2 | 110 | John | Chen | JCHEN | 515.124.4269 | 28-SEP-05 | FI_ACCOUNT | 8200 | - | 108 | 100 | 
| 3 | 111 | Ismael | Sciarra | ISCIARRA | 515.124.4369 | 30-SEP-05 | FI_ACCOUNT | 7700 | - | 108 | 100 | 
| 1 | 108 | Nancy | Greenberg | NGREENBE | 515.124.4569 | 17-AUG-02 | FI_MGR | 12008 | - | 101 | 100 | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | 
4. Lastly combine steps 2 and 3 to consolidate the query and limit the results.
SELECT
   JOB_ID,
   LISTAGG(SALARY, ',') WITHIN GROUP (ORDER BY JOB_ID) "Salaries"
FROM (
SELECT * FROM (
    SELECT
      E.*,
      ROW_NUMBER() OVER(PARTITION BY JOB_ID ORDER BY JOB_ID) "NUM"
    FROM HR.EMPLOYEES E
    )
  WHERE NUM <= 3
)
GROUP BY JOB_ID;| JOB_ID | Salaries | 
|---|---|
| AC_ACCOUNT | 8300 | 
| AC_MGR | 12008 | 
| AD_ASST | 4400 | 
| AD_PRES | 24000 | 
| AD_VP | 17000,17000 | 
| FI_ACCOUNT | 6900,7800,9000 | 
| FI_MGR | 12008 | 
| HR_REP | 6500 | 
| IT_PROG | 4800,6000,9000 | 
| MK_MAN | 13000 | 
| MK_REP | 6000 | 
| PR_REP | 10000 | 
| PU_CLERK | 2500,2600,3100 | 
| PU_MAN | 11000 | 
| SA_MAN | 10500,11000,14000 | 
| SA_REP | 10000,6200,7000 | 
| SH_CLERK | 2600,2600,3200 | 
| ST_CLERK | 2500,2600,3200 | 
| ST_MAN | 7900,8000,8200 |