Introduction
Data consumers, business analysts, managers, auditors, rarely want to sift through dozens of rows to find a concise summary. They prefer one-line summaries that read like natural language: “User 123 listened to Blues, Jazz, and Rock” or “Order 456 includes Widget A, Widget B, and Widget C.” Oracle LISTAGG delivers exactly that in SQL, and when combined with PL/SQL, it becomes a powerful tool to transform raw query output into human-readable strings. In this article, we’ll explore how to craft readable output using LISTAGG, handle edge cases, integrate it into PL/SQL procedures, and follow best practices so your reports read like well-written prose rather than machine dumps.
1. Crafting Clear Output with Proper Delimiters
While a comma and space (‘, ‘) is the most common delimiter, thoughtful choices can improve clarity:
- Oxford comma style: When listing multiple items, add an “and” before the last element.
- Custom separators: Use semicolons (‘; ‘) if values themselves contain commas (e.g., “Paris, TX”).
Example: Building an Oxford comma list in PL/SQL
plsql
CopyEdit
DECLARE
v_raw_list VARCHAR2(4000);
v_items SYS.ODCIVARCHAR2LIST; — PL/SQL collection type
v_readable VARCHAR2(4000);
BEGIN
SELECT LISTAGG(tag_name, ‘, ‘)
WITHIN GROUP (ORDER BY tag_name)
INTO v_raw_list
FROM article_tags
WHERE article_id = 100;
v_items := SYS.ODCIVARCHAR2LIST();
FOR i IN 1 .. REGEXP_COUNT(v_raw_list, ‘, ‘) + 1 LOOP
v_items.EXTEND;
v_items(i) := REGEXP_SUBSTR(v_raw_list, ‘[^,]+’, 1, i);
END LOOP;
IF v_items.COUNT = 1 THEN
v_readable := v_items(1);
ELSIF v_items.COUNT = 2 THEN
v_readable := v_items(1) || ‘ and ‘ || v_items(2);
ELSE
v_readable := SUBSTR(v_raw_list, 1, INSTR(v_raw_list, ‘,’, -1) – 1)
|| ‘, and ‘
|| v_items(v_items.COUNT);
END IF;
DBMS_OUTPUT.PUT_LINE(‘Tags: ‘ || v_readable);
END;
This turns “Alpha, Beta, Gamma” into “Alpha, Beta, and Gamma,” making output more natural.
2. Handling Singular vs. Plural Labels
Human-readable output also adapts labels based on count:
plsql
CopyEdit
DECLARE
v_list VARCHAR2(4000);
v_count NUMBER;
v_label VARCHAR2(20);
BEGIN
SELECT COUNT(*),
LISTAGG(error_code, ‘, ‘)
WITHIN GROUP (ORDER BY error_code)
INTO v_count, v_list
FROM process_errors
WHERE run_id = 789;
v_label := CASE WHEN v_count = 1 THEN ‘error’ ELSE ‘errors’ END;
DBMS_OUTPUT.PUT_LINE(v_count || ‘ ‘ || v_label || ‘: ‘ || v_list);
END;
This outputs “1 error: X123” or “3 errors: X123, Y456, Z789” as appropriate.
3. Embedding LISTAGG in Stored Functions
To reuse logic, encapsulate it in a PL/SQL function:
plsql
CopyEdit
CREATE OR REPLACE FUNCTION get_user_genres(p_user_id IN NUMBER)
RETURN VARCHAR2 IS
v_genres VARCHAR2(4000);
BEGIN
SELECT LISTAGG(genre, ‘, ‘)
WITHIN GROUP (ORDER BY genre)
INTO v_genres
FROM user_listens
WHERE user_id = p_user_id;
RETURN v_genres;
END;
Clients can then:
sql
CopyEdit
SELECT user_id,
get_user_genres(user_id) AS genres
FROM users;
4. Combining LISTAGG with Other Text Functions
Often, you need to enrich the aggregated list with context:
plsql
CopyEdit
DECLARE
v_genres VARCHAR2(4000);
v_msg VARCHAR2(4050);
BEGIN
v_genres := get_user_genres(42);
IF v_genres IS NULL THEN
v_msg := ‘User 42 has not listened to any genres yet.’;
ELSE
v_msg := ‘User 42 has listened to: ‘ || v_genres || ‘.’;
END IF;
DBMS_OUTPUT.PUT_LINE(v_msg);
END;
This provides clear, sentence-like feedback.
5. Avoiding Common Pitfalls
- Null handling: LISTAGG ignores NULLs, but concatenation with NULL strings can produce extra separators. Always trim or NVL results.
- Length limits: Remember the 4000/32767 limit and use ON OVERFLOW TRUNCATE or CLOB casts.
- Ordering: Use ORDER BY within LISTAGG to ensure predictable output.
Conclusion
By harnessing Oracle LISTAGG within PL/SQL, you can transform dry, row-based query results into polished, human-readable summaries that feel like natural language. Whether you’re creating one-line audit logs, user-friendly dashboards, or automated reports, combining LISTAGG with PL/SQL functions, control-flow logic, and text manipulation yields output that speaks directly to business users. Follow best practices, choose appropriate delimiters, handle singular vs. plural, manage empty or overflow cases, and encapsulate logic in reusable functions, to make your database deliver not just data, but clear, readable stories.