Ваш «идеальный» результат - простое объединение:
select p.id, p.dt, p.value, p.type, m.id as satisfied_by
from purchases p
join makes m on m.needed_for = p.id;
Возможно, вы захотите сделать это left join
в случае отсутствия совпадений, если это возможно в ваших данных.
Быстрое демо с вашими данными:
-- CTEs for sample data
with purchases (id, dt, value, type, satisfied_by) as (
select 'SALE100', date '2019-01-01', -5, 'OUT', null from dual
union all select 'SALE201', date '2019-01-09', -10, 'OUT', null from dual
union all select 'SALE203', date '2019-02-22', -1, 'OUT', null from dual
union all select 'SALE205', date '2019-03-14', -1, 'OUT', null from dual
),
makes (id, dt, value, needed_for) as (
select 'MAKE300', date '2018-12-24', 5, 'SALE100' from dual
union all select 'MAKE301', date '2019-01-03', 3, 'SALE201' from dual
union all select 'MAKE399', date '2019-01-05', 5, 'SALE201' from dual
union all select 'MAKE401', date '2019-01-07', 3, 'SALE201' from dual
union all select 'MAKE401', date '2019-01-07', 3, 'SALE203' from dual
union all select 'MAKE912', date '2019-02-01', 1, 'SALE205' from dual
)
-- actual query
select p.id, p.dt, p.value, p.type, m.id as satisfied_by
from purchases p
left join makes m on m.needed_for = p.id;
ID DT VALUE TYP SATISFIED_BY
------- ---------- ---------- --- ------------------------------
SALE100 2019-01-01 -5 OUT MAKE300
SALE201 2019-01-09 -10 OUT MAKE301
SALE201 2019-01-09 -10 OUT MAKE399
SALE201 2019-01-09 -10 OUT MAKE401
SALE203 2019-02-22 -1 OUT MAKE401
SALE205 2019-03-14 -1 OUT MAKE912
Версия listagg
также довольно проста:
select p.id, p.dt, p.value, p.type,
listagg(m.id, ', ') within group (order by m.id) as satisfied_by
from purchases p
left join makes m on m.needed_for = p.id
group by p.id, p.dt, p.value, p.type;
ID DT VALUE TYP SATISFIED_BY
------- ---------- ---------- --- ------------------------------
SALE100 2019-01-01 -5 OUT MAKE300
SALE201 2019-01-09 -10 OUT MAKE301, MAKE399, MAKE401
SALE203 2019-02-22 -1 OUT MAKE401
SALE205 2019-03-14 -1 OUT MAKE912
Из вашего фрагмента кода не совсем понятно, что вы делаете неправильно, но похоже, что вы правильно коррелируете свои подзапросы; но тогда они вам на самом деле не нужны ... И если вы уже правильно коррелируете подзапрос версии listagg
, то у вас может быть слишком много совпадений в ваших реальных данных; это либо так, либо подзапрос возвращает больше данных, чем следовало бы, и агрегирование всего этого нарушает ограничение размера.
«Отсутствующая» часть подзапроса заключается в том, что я использую CASE WHEN TYPE = 'OUT' THEN
, так что ничего особенного, но это ограничит количество записей, которые у меня есть
Вы можете включить это в условие соединения:
from purchases p
left join makes m on (p.type = 'OUT' and m.needed_for = p.id)
Вы все еще можете использовать подзапрос для подхода listagg
:
select p.id, p.dt, p.value, p.type,
(
select listagg(m.id, ', ') within group (order by m.id)
from makes m
where m.needed_for = p.id
-- and p.type = 'OUT'
) as satisfied_by
from purchases p;
, что на самом деле может быть тем, что вы делаете - не совсем понятно, эквивалентно ли это условие показанному вами purchased.id = needed_for.id
. Если вы по-прежнему получаете ORA-01489 от этого, то вы получите и версию, не относящуюся к подзапросу, и у вас слишком много совпадений, чтобы вместить агрегированный список в 4000 байтов. И если они оба работают, то я не уверен, в чем будет преимущество наличия подзапроса - в лучшем случае оптимизатор Oracle может сделать их эквивалентными, но более вероятно, что производительность будет хуже. Вы должны проверить свою реальную среду и данные, чтобы быть уверенным.
Подзапрос без listagg не будет работать, с или без in()
(который просто добавляет другой уровень подзапроса без реального эффекта):
select p.id, p.dt, p.value, p.type,
(
select m.id
from makes m
where m.needed_for = p.id
-- and p.type = 'OUT'
) as satisfied_by
from purchases p;
ORA-01427: single-row subquery returns more than one row
... потому что вы знаете и ожидаете, что вы получите несколько строк из этого подзапроса, по крайней мере, для некоторых покупок. С вашими примерами данных это действительно работает, если вы исключаете SALE201, но это бесполезно. Вы пытаетесь втиснуть несколько значений в один скалярный результат, который не сработает, и именно поэтому вам нужно было в первую очередь взглянуть на listagg.
Помимо варианта xmlagg, продемонстрированного @Tejash, вы также можете получить объединенные значения в виде коллекции, например ::1010
select p.id, p.dt, p.value, p.type,
cast(multiset(
select m.id
from makes m
where m.needed_for = p.id
-- and p.type = 'OUT'
) as sys.odcivarchar2list) as satisfied_by
from purchases p;
ID DT VALUE TYP SATISFIED_BY
------- ---------- ---------- --- --------------------------------------------------
SALE100 2019-01-01 -5 OUT ODCIVARCHAR2LIST('MAKE300')
SALE201 2019-01-09 -10 OUT ODCIVARCHAR2LIST('MAKE301', 'MAKE399', 'MAKE401')
SALE203 2019-02-22 -1 OUT ODCIVARCHAR2LIST('MAKE401')
SALE205 2019-03-14 -1 OUT ODCIVARCHAR2LIST('MAKE912')
... или как коллекция табличного типа, определенная в вашей схеме. С этим может быть еще сложнее работать, и даже дальше от вашего «идеального» результата. Это немного зависит от того, что будет использовать ваш набор результатов и как.