Мой запрос возвращает много (тысячи) строк. Столбец l имеет определенное значение для очень небольшого количества строк (до 10). Для каждой такой строки я хочу вывести агрегированные значения, разделенные запятыми, очень короткого (до 5 символов) столбца varchar v по всем этим строкам. Для строк, не имеющих специального значения l, я хочу просто вывести значение v для этой строки.

Синтезированный пример той же проблемы: из первых 10000 целых чисел я хочу вывести 1,2,3,4,5,6,7,8,9 для каждого однозначного числа; этот номер для многозначного числа. (Да, глупый пример, но реальный случай имеет смысл.)

with x (v,l) as (
  select to_char(level), length(to_char(level)) from dual connect by level <= 10000
)
select case l
         when 1 then listagg(v,',') within group (order by v) over (partition by l)
         else v
       end
from x
order by 1;

Проблема в том, что функция listagg не работает при ошибке ORA-01489: result of string concatenation is too long.

Мне известно о пределе 4000 символов для функции listagg, а также об обходном пути на основе xmlagg. Я просто не получаю предела, которого достаточно для данных, которые я хочу объединить, хотя его недостаточно для всех данных. В приведенном выше примере раздел из 9 однозначных чисел умещается в 4000 символов, а раздел из 9000 четырехзначных чисел - нет. Я ожидал, что выражение case предотвратит выполнение окна для несвязанных строк, но по какой-то причине кажется, что механизм db оценивает окно для всех строк. (Также обратите внимание, что предложение order by приводит к быстрому выполнению запроса - без него некоторые строки возвращаются до сбоя.)

Не могли бы вы объяснить причину такого поведения? Я подозреваю, что вычисление окна логически выполняется перед предложением select, но без каких-либо доказательств. Воспроизводится на Oracle 11g, 18c и 19 (liveql).

0
Tomáš Záluský 10 Фев 2021 в 00:37

2 ответа

Лучший ответ

Что ж, вы используете SQL, который не является процедурным, поэтому вы не можете ожидать , что некоторые части пути кода не будут выполнены только потому, что они не используются. (Таким образом, исправление ошибки, предложенное другими способами, не принесет успеха).

В любом случае вы можете проделать часто используемый трюк, основанный на том факте, что listagg игнорирует значения null.

Итак, эта формулировка отлично работает:

with x (v,l) as (
  select to_char(level), length(to_char(level)) from dual connect by level <= 10000
)
select   nvl(listagg(case when l = 1 then v end,',') within group (order by v) over (partition by l),v) lst
from x
order by 1;

Давая

LST
------------------
1,2,3,4,5,6,7,8,9
1,2,3,4,5,6,7,8,9
..
10
100
1000
10000

Объяснение проблемы можно найти в плане выполнения (с указанием только соответствующей части)

----------------------------------------------------------------------------------------
| Id  | Operation                       | Name | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                |      |     1 |    35 |     4  (50)| 00:00:01 |
|   1 |  SORT ORDER BY                  |      |     1 |    35 |     4  (50)| 00:00:01 |
|   2 |   WINDOW SORT                   |      |     1 |    35 |     4  (50)| 00:00:01 |
|   3 |    VIEW                         |      |     1 |    35 |     2   (0)| 00:00:01 |
|*  4 |     CONNECT BY WITHOUT FILTERING|      |       |       |            |          |
|   5 |      FAST DUAL                  |      |     1 |       |     2   (0)| 00:00:01 |
----------------------------------------------------------------------------------------
...
Column Projection Information (identified by operation id):
-----------------------------------------------------------
 
   1 - (#keys=1) CASE "L" WHEN 1 THEN LISTAGG("V",',') WITHIN GROUP ( ORDER BY 
       "V") OVER ( PARTITION BY "L") ELSE "V" END [4000]
   2 - (#keys=2) "L"[NUMBER,22], "V"[VARCHAR2,40], LISTAGG("V",',') WITHIN 
       GROUP ( ORDER BY "V") OVER ( PARTITION BY "L")[4000]
   3 - "V"[VARCHAR2,40], "L"[NUMBER,22]
   4 - LEVEL[4]

Таким образом, в строке 2 вычисляется listagg (для всех строк) только для фильтрации в строке 1.

1
Marmite Bomber 9 Фев 2021 в 23:44

Странно, что вы получаете сообщение об ограничении 4000 символов, даже если результат не превышает 4000 символов. Возможно, вы могли бы сообщить об этом как об ошибке в службу поддержки Oracle.

Другой обходной путь - использовать логику ON OVERFLOW функции LISTAGG, если вы используете Oracle 12.2 или выше. Использование LISTAGG (v, ',' ON OVERFLOW TRUNCATE) в запросе позволяет выполнить запрос без ошибок и не усекает никаких значений (по крайней мере, в примере).

1
EJ Egyed 9 Фев 2021 в 22:09
66127548