Подзапрос Возвращает более 1 строки, даже используя оператор IN - Oracle SQL - PullRequest
0 голосов
/ 20 июня 2019

Я работаю с двумя таблицами, первой, purchases, ниже (обратите внимание, это вырезка из таблицы purchases:

|    ID   |    Date   | Value | Type | Satisfied By |
|:-------:|:---------:|:-----:|:----:|:------------:|
| SALE100 |  1/1/2019 |   -5  |  OUT |              |
| SALE201 |  1/9/2019 |  -10  |  OUT |              |
| SALE203 | 2/22/2019 |   -1  |  OUT |              |
| SALE205 | 3/14/2019 |   -1  |  OUT |              |

Я пытаюсь определить, какая MAKEтовары из другой таблицы, makes, удовлетворяют этим продажам.

|    ID   |    Date    | Value | Needed For |
|:-------:|:----------:|:-----:|:----------:|
| MAKE300 | 12/24/2018 |   5   |   SALE100  |
| MAKE301 |  1/3/2019  |   3   |   SALE201  |
| MAKE399 |  1/5/2019  |   5   |   SALE201  |
| MAKE401 |  1/7/2019  |   3   |   SALE201  |
| MAKE401 |  1/7/2019  |   3   |   SALE203  |
| MAKE912 |  2/1/2019  |   1   |   SALE205  |

Я пытаюсь написать запрос, который позволит мне определить, какой ID или IDs из таблицы makes удовлетворяетмои продажи.

Мои конечные результаты будут выглядеть так, как если бы они были LISTAGG:

|    ID   |    Date   | Value | Type |        Satisfied By       |
|:-------:|:---------:|:-----:|:----:|:-------------------------:|
| SALE100 |  1/1/2019 |   -5  |  OUT |          MAKE300          |
| SALE201 |  1/9/2019 |  -10  |  OUT | MAKE301, MAKE399, MAKE401 |
| SALE203 | 2/22/2019 |   -1  |  OUT |          MAKE401          |
| SALE205 | 3/14/2019 |   -1  |  OUT |          MAKE912          |

Однако при написании следующей строки кода:

(SELECT LISTAGG(makes.id, ', ') WITHIN GROUP (ORDER BY NULL) FROM makes WHERE purchased.id = needed_for.id) ELSE NULL END AS Satisfied_By

приводит к ошибке:

ORA-01489: результат объединения строк слишком длинный 01489. 00000 - «результат объединения строк слишком длинный»

Я также пробовал следующий запрос для получения таких результатов (что идеально):

|    ID   |    Date   | Value | Type | Satisfied By |
|:-------:|:---------:|:-----:|:----:|:------------:|
| SALE100 |  1/1/2019 |   -5  |  OUT |    MAKE300   |
| SALE201 |  1/9/2019 |  -10  |  OUT |    MAKE301   |
| SALE201 |  1/9/2019 |  -10  |  OUT |    MAKE399   |
| SALE201 |  1/9/2019 |  -10  |  OUT |    MAKE401   |
| SALE203 | 2/22/2019 |   -1  |  OUT |    MAKE401   |
| SALE205 | 3/14/2019 |   -1  |  OUT |    MAKE912   |

CASE WHEN Type = 'OUT' THEN
(SELECT 
makes.id

FROM
makes
WHERE
makes.id IN (

  SELECT
    makes.id

  FROM
    makes

  WHERE
    sales.id = purchases.id

)) ELSE NULL END AS Satisfied_By

, что дает

ORA-01427: однострочный возврат подзапросаболее одной строки 01427. 00000 - «однострочный подзапрос возвращает более одной строки»

I чМы нашли много примеров этой ошибки в переполнении стека, где я принял метод IN и из этого источника, но я все еще получаю ошибку.Любая помощь приветствуется.

Ответы [ 2 ]

3 голосов
/ 20 июня 2019

Ваш «идеальный» результат - простое объединение:

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')                       

... или как коллекция табличного типа, определенная в вашей схеме. С этим может быть еще сложнее работать, и даже дальше от вашего «идеального» результата. Это немного зависит от того, что будет использовать ваш набор результатов и как.

2 голосов
/ 21 июня 2019

Ваш первый запрос вернул следующую ошибку:

ORA-01489: результат конкатенации строк слишком длинный 01489. 00000 - «результат конкатенации строк слишком длинный»

Поскольку конкатенация в столбце "Satisfied_By" становится длинной более 4000 символов.Вам нужно использовать XMLAGG для более безопасной стороны при объединении столбца VARCHAR.

Можно выполнить следующий запрос:

-- DATA PREPARATION
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,
    RTRIM(XMLAGG(XMLELEMENT(E, M.ID, ',').EXTRACT('//text()')
        ORDER BY
            M.ID
    ).GETCLOBVAL(), ',') AS SATISFIED_BY
FROM
    PURCHASES P
    LEFT JOIN MAKES M ON P.ID = M.NEEDED_FOR
GROUP BY
    P.ID,
    P.DT,
    P.VALUE,
    P.TYPE;

Демонстрация DB Fiddle

Ура !!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...