Reputation: 1
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
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