sharona
sharona

Reputation: 1

How can i set a maxiumum value using list agg

i have read the other questions and answers and they do not help with my issue. i am asking if there is a way to set a limit on the number of results returned in listagg.

I am using this query

HR--Any baby with a HR<80 AS

SELECT fm.y_inpatient_dat, h.pat_id, h.pat_enc_csn_id, 
LISTAGG(meas_value, '; ') WITHIN GROUP (ORDER BY fm.recorded_time) 
abnormal_HR_values
from
ip_flwsht_meas fm
join pat_enc_hsp h on fm.y_inpatient_dat = h.inpatient_data_id
where fm.flo_meas_id in ('8' ) and (to_number(MEAS_VALUE) <80)
AND fm.recorded_time between (select start_date from dd) AND (select end_date from dd)
group by fm.y_inpatient_dat,h.pat_id, h.pat_enc_csn_id)

and I get the following error:

ORA-01489: result of string concatenation is too long

I have researched online how to set a size limit, but I can't seem to make it work. Can someone please advise how to set a limit so it does not exceed the 50 characters.

Upvotes: 0

Views: 400

Answers (1)

Matthew McPeak
Matthew McPeak

Reputation: 17944

In Oracle 12.2, you can use ON OVERFLOW ERROR in the LISTAGG, like:

LISTAGG(meas_value, '; ' ON OVERFLOW ERROR) WITHIN GROUP (ORDER BY fm.recorded_time) 

Then you can surround that with a SUBSTR() to get the first 50 characters.

Pre 12.2, you need to restructure the query to limit the number of rows that get seen by the LISTAGG. Here is an example of that that uses DBA_OBJECTS (so people without your tables can run it). It will only the 1st three values for each object type.

SELECT object_type, 
       listagg(object_name, ', ') within group ( order by object_name) first_three
FROM (
    SELECT object_type, 
           object_name, 
           row_number() over ( partition by object_type order by object_name ) ord
    FROM   dba_objects
    WHERE  owner = 'SYS'
    )
WHERE ord <= 3
GROUP BY object_type
ORDER BY object_type;

The idea is to number the row that you want to aggregate and then only aggregate the first X of them, where "X" is small enough not to overflow the max length on VARCHAR2. "X" will depend on your data.

Or, if you don't want the truncation at 50 characters to happen mid-values and/or you don't know how many values are safe to allow, you can replace the ord expression with a running_length expression to keep a running count of the length and cap it off before it gets to your limit (of 50 chars). That expression would be a SUM(length()) OVER (...). Like this:

SELECT object_type, 
       listagg(object_name, ', ') within group ( order by object_name) first_50_char,
FROM (
    SELECT object_type, 
           object_name, 
           sum(length(object_name || ', ')) 
              over ( partition by object_type order by object_name ) running_len
    FROM   dba_objects
    WHERE  owner = 'SYS'
    )
WHERE running_len <= 50+2  -- +2 because the last one won't have a trailing delimiter
GROUP BY object_type
ORDER BY object_type;

With your query, all that put together would look like this:

SELECT  y_inpatient_dat,
        pat_id,
        pat_enc_csn_id,
        LISTAGG(meas_value, '; ') WITHIN GROUP ( ORDER BY fm.recorded_time ) abnormal_HR_values
FROM (
    SELECT fm.y_inpatient_dat, 
           h.pat_id, 
           h.pat_enc_csn_id, 
           meas_value,
           fm.recorded_time,
           SUM(length(meas_value || '; ') OVER ( ORDER BY fm.recorded_time ) running_len
    FROM   ip_flwsht_meas fm
    INNER JOIN pat_enc_hsp h on fm.y_inpatient_dat = h.inpatient_data_id
    WHERE  fm.flo_meas_id in ('8' ) and (to_number(MEAS_VALUE) <80)
    AND    fm.recorded_time BETWEEN 
             (SELECT start_date FROM dd) AND (SELECT end_date FROM dd)
)
WHERE running_len <= 50+2
GROUP BY fm.y_inpatient_dat,h.pat_id, h.pat_enc_csn_id;

Upvotes: 2

Related Questions