Tables in databases are very simple - they consist of columns and rows. However, sometimes the data from the tables must be presented in a slightly more sophisticated form. And here the numerous functions available to us come to the rescue.
This time we would like to present data from many rows - as a list with comma-separated values.
For this example, I will use the default HR schema and the EMPLOYEES table in Oracle Database 18c Express Edition Release 18.0.0.0.0 - Production.
In the employees table, we have employees and departments data.
SELECT department_id, last_name
FROM employees
where department_id in (20, 30)
ORDER BY department_id;
DEPARTMENT_ID LAST_NAME
------------- -------------------------
20 Hartstein
20 Fay
30 Raphaely
30 Khoo
30 Baida
30 Tobias
30 Himuro
30 Colmenares
8 rows selected
We would like to convert this report a little so that we have a department id and a column with all the employees of this department in one line.
We will use the LISTAGG function.
SELECT department_id, LISTAGG(last_name, ',' ) WITHIN GROUP (ORDER BY employee_id)
FROM employees
where department_id in (20, 30)
GROUP BY department_id
ORDER BY department_id;
DEPARTMENT_ID LIST
------------- ----------------------------------------------------------------------
20 Hartstein,Fay
30 Raphaely,Khoo,Baida,Tobias,Himuro,Colmenares
And what if we would like to have information at the level of a single employee, e.g. his employee_id and at the same time information about all employees of a given department? We can do this using the LISTAGG function together with analytical functions.
SELECT department_id, employee_id, LISTAGG(last_name, ',' ) WITHIN GROUP (ORDER BY employee_id) over( partition by department_id) listFROM employees
ORDER BY department_id;where department_id in (20, 30)DEPARTMENT_ID EMPLOYEE_ID LIST------------- ----------- ----------------------------------------------------------------------20 202 Hartstein,Fay20 201 Hartstein,Fay 30 114 Raphaely,Khoo,Baida,Tobias,Himuro,Colmenares30 117 Raphaely,Khoo,Baida,Tobias,Himuro,Colmenares30 115 Raphaely,Khoo,Baida,Tobias,Himuro,Colmenares 30 116 Raphaely,Khoo,Baida,Tobias,Himuro,Colmenares 30 118 Raphaely,Khoo,Baida,Tobias,Himuro,Colmenares30 119 Raphaely,Khoo,Baida,Tobias,Himuro,Colmenares8 rows selected.
We should keep in mind that with a large amount of data converted to an Oracle list, it may return an error.
SELECT department_id dep_id, LISTAGG(last_name, ',' ) WITHIN GROUP (ORDER BY employee_id) list
FROM employees,(SELECT level FROM dual CONNECT BY level <= 100)where department_id in (20, 30, 80)ORA-01489: napis otrzymany w wyniku konkatenacji jest zbyt długiGROUP BY department_id ORDER BY department_id;01489. 00000 - "result of string concatenation is too long"*Cause: String concatenation result is more than the maximum size.*Action: Make sure that the result is less than the maximum size.
Up to version 12, not much could be done about it. Fortunately, in newer Oracle databases we already have an influence on the system behavior in such a situation.
For example, we can do nothing, let Oracle report a bug. To make it obvious that this is the intended action, we can add the ON OVERFLOW ERROR clause. This is the default behavior.
SELECT department_id dep_id, LISTAGG(last_name, ',' on overflow error ) WITHIN GROUP (ORDER BY employee_id)FROM employees,
(SELECT level FROM dual CONNECT BY level <= 100)where department_id in (20, 30) GROUP BY department_idORA-01489: napis otrzymany w wyniku konkatenacji jest zbyt długiORDER BY department_id;01489. 00000 - "result of string concatenation is too long"*Cause: String concatenation result is more than the maximum size.*Action: Make sure that the result is less than the maximum size.
We can also cut the too-long string, add a tag that the text was cut off, and even count the number of cut characters.
ON OVERFLOW TRUNCATE char WITH COUNT - with the count is the default option
char - the character at the end
SELECT department_id dep_id, LISTAGG(last_name, ',' on overflow truncate '(...)' with count) WITHIN GROUP (ORDER BY employee_id) listFROM employees,
(SELECT level FROM dual CONNECT BY level <= 100)where department_id in (20, 30, 80) GROUP BY department_idDEP_ID LIST---------- ----------------------------------------------------------------------Fay,Fay,Fay,Fay,Fay,Fay,Fay,Fay,Fay20 Hartstein,Hartstein,Hartstein,Hartstein,Hartstein,(..)Fay,Fay,Fay,Fay,30 Raphaely,Raphaely,Raphaely,Raphaely,Raphaely,Rapha(..)lmenares,Colmena res,Colmenares,Colmenares,(...)(49)80 Russell,Russell,Russell,Russell,Russell,Russell,Ru(..)Zlotkey,Zlotkey,Zlotkey,Zlotkey,Zlotkey,(...)(2967)
If we do not want to show the number of truncated characters, we change the clause to WITHOUT COUNT
SELECT department_id dep_id, LISTAGG(last_name, ',' on overflow truncate '(...)' without count) WITHIN GROUP (ORDER BY employee_id) listFROM employees,
(SELECT level FROM dual CONNECT BY level <= 100)where department_id in (20, 30, 80) GROUP BY department_idDEP_ID LIST---------- ----------------------------------------------------------------------Fay,Fay,Fay,Fay,Fay,Fay,Fay,Fay,Fay20 Hartstein,Hartstein,Hartstein,Hartstein,Hartstein,(..)Fay,Fay,Fay,Fay,30 Raphaely,Raphaely,Raphaely,Raphaely,Raphaely,Rapha(..)s,Colmenares,Col menares,Colmenares,Colmenares,(...)otkey,Zlotkey,Zlotkey,Zlotkey,(...)80 Russell,Russell,Russell,Russell,Russell,Russell,Ru(..)otkey,Zlotkey,Zl
You can also use the LISTAGG function to retrieve data from another table.
SELECT department_name,
(select listagg(last_name, ',') WITHIN GROUP (ORDER BY employee_id) from employees where department_id = dept.department_id) listfrom departments deptwhere department_id in (20, 30);DEPARTMENT_NAME LIST------------------------------ ----------------------------------------------------------------------Purchasing Raphaely,Khoo,Baida,Tobias,Himuro,ColmenaresMarketing Hartstein,Fay
Nevertheless, it is a very inefficient construction. The query to the employees table will be executed as many times as there are rows.
Is LISTAGG useful in practice? Perhaps this is not the most used function, but I must admit that it came in handy several times. Also definitely worth remembering about it.
Komentarze
Prześlij komentarz