SQL-запрос для удовлетворения нескольких потребностей частичными количествами с помощью оконных функций Oracle SQL - PullRequest
3 голосов
/ 09 мая 2019

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

Я бы хотел удовлетворить эти потребности, упорядоченные к необходимой дате и доступной датеиспользуя только один запрос (без процедуры, без временных таблиц).

В качестве технической базы можно предположить, что у вас есть две таблицы:

  • NEED_TABLE, в которой перечислены несколько потребностей, требуемая дата и количества.
  • FILL_TABLE, в котором перечислены несколько «заливок», доступные даты и количества.

В моем примере есть две потребности:

  • Необходимость A:нам нужно партно 123 в кол-во 4 на дату 01/02/2019
  • нужен Б: нам нужно партно 123 в кол-во 2 на дату 06/02/2019

И у нас есть два«заполняет» в разных количествах:

  • Заполнить X: у нас есть партно 123 в кол-во 2 в заказе на покупку, доступно 01/01/2019
  • Заполнить Y: у нас парно 123в кол-во 4 в заказе на поставку, доступно 06/01/2019

Результат должен быть:

  • Мне нужно partno 123 с номером 4 в 1/2/2019 («Нужна A»), который заполняется заказом на покупку в количестве 2 («Заполнить X»)) и другим заказом на покупку в кол-во 2 («Заполнить Y» - частично).
  • Мне нужно partno 123 с кол-во 2 от 02.06.2009 («Need B»), который заполняется покупкойпорядок в кол-во 2 («Заполнить Y» - частично).

SQL-запрос:

with
    NEED_TABLE
    as
        (select 'A' NEED_ID, 123 partno, to_date('01/02/2019', 'MM/DD/YYYY') DATE_NEEDED, 4 NEED_QTY from dual
         union all
         select 'B' NEED_ID, 123 partno, to_date('06/02/2019', 'MM/DD/YYYY') DATE_NEEDED, 2 NEED_QTY from dual),
    FILL_TABLE
    as
        (select 'X' FILL_ID, 123 partno, to_date('01/01/2019', 'MM/DD/YYYY') DATE_AVAILABLE, 2 FILL_QTY from dual
         union all
         select 'Y' FILL_ID, 123 partno, to_date('06/01/2019', 'MM/DD/YYYY') DATE_AVAILABLE, 4 FILL_QTY from dual)
select   NEED_TABLE.NEED_ID
       , NEED_TABLE.PARTNO
       , NEED_TABLE.DATE_NEEDED
       , NEED_TABLE.NEED_QTY
       , FILL_TABLE.FILL_ID
       , FILL_TABLE.DATE_AVAILABLE
       , FILL_TABLE.FILL_QTY
       /* all following is wrong/incomplete */
       , lag(need_QTY - fill_QTY, 1, need_QTY)
             over(
                 partition by NEED_ID
                 order by DATE_NEEDED, DATE_AVAILABLE) REAL_NEED_QTY
       , greatest(
               lag(need_QTY - fill_QTY, 1, need_QTY)
                   over(
                       partition by NEED_ID
                       order by DATE_NEEDED, DATE_AVAILABLE)
             - FILL_QTY
           , 0) LEFT_NEED_QTY
       , abs(
             least(
                   lag(need_QTY - fill_QTY, 1, need_QTY)
                       over(
                           partition by NEED_ID
                           order by DATE_NEEDED, DATE_AVAILABLE)
                 - FILL_QTY
               , 0)) LEFT_FILL_QTY
    from NEED_TABLE, FILL_TABLE
order by DATE_NEEDED, DATE_AVAILABLE;

Если вы проверите результат этого запроса, все будет хорошо дляпервый NEED_ID "A".Но так как он продолжается с NEED_ID «B», он не помнит, что FILL_IDs X и Y уже были уменьшены, а для заполнения требуется «A».

Я ожидаю такой результат:

NEED_ID A is filled by FILL_ID X qty 2

NEED_ID A is filled by FILL_ID Y qty 2

(NEED_ID A is filled by FILL_ID X qty 0)

NEED_ID B is filled by FILL_ID Y qty 2

NEED_TABLE:

| NEED_ID | PARTNO | DATE_NEEDED | NEED_QTY |
|---------|--------|-------------|----------|
| A       | 123    | 01/02/2019  | 4        |
| B       | 123    | 06/02/2019  | 2        |

FILL_TABLE:

| FILL_ID | PARTNO | DATE_AVAILABLE | FILL_QTY |
|---------|--------|----------------|----------|
| X       | 123    | 01/01/2019     | 2        |
| Y       | 123    | 06/01/2019     | 4        |

Ожидаемый результат запроса:

| NEED_ID | PARTNO | DATE_NEEDED | NEED_QTY | FILL_ID | DATE_AVAILABLE | FILL_QTY | ***REAL_FILL*** | "WHY?"                                                              |
|---------|--------|-------------|----------|---------|----------------|----------|-----------------|---------------------------------------------------------------------|
| A       | 123    | 01/02/2019  | 4        | X       | 01/01/2019     | 2        | 2               | A needs 4, gets partially filled by X by 2                          |
| A       | 123    | 01/02/2019  | 4        | Y       | 06/01/2019     | 4        | 2               | A still needs 2, gets completely filled by Y by 2                   |
| B       | 123    | 06/02/2019  | 2        | X       | 01/01/2019     | 2        | 0               | B needs 2, can't get filled by X, because A already used that qty   |
| B       | 123    | 06/02/2019  | 2        | Y       | 06/01/2019     | 4        | 2               | B still needs 2, gets completely filled by remaining qty of Y, by 2 |

Любая помощь очень ценится - спасибо!

Ответы [ 2 ]

3 голосов
/ 10 мая 2019

Вот моя попытка:

with 
  need(rn, nid, nq) as (
    select 1, 'A', 4 from dual union all 
    select 2, 'B', 2 from dual ),
  fill(rf, fid, fq) as (
    select 1, 'X', 2 from dual union all 
    select 2, 'Y', 4 from dual ),
  u as (
    select rn, nid, -nq nq, null rf, null fid, null fq from need
    union all select null, null, null, rf, fid, fq from fill),
  c (crn, cnid, cnq, crf, cfid, cfq, rest, amt) as (
    select rn, nid, nq, 0, fid, fq, nq, 0 from u where rn = 1
    union all 
    select nvl(rn, crn), nvl(nid, cnid), nvl(nq, cnq), 
           nvl(rf, crf), nvl(fid, cfid), nvl(fq, cfq), rest + nvl(nq, fq), 
           least(abs(rest), abs(nvl(nq, fq)))
      from c 
      join u on rest >= 0 and rn = crn + 1 
             or rest <  0 and rf = crf + 1 )
select cnid, cfid, amt from c where amt <> 0

Я упростила данные, но partno может быть легко добавлена ​​в соединениях, а partition by правильных номеров строк и дат важны только для упорядочивания строк.Если они имеют больше значения, их можно добавить сейчас, но давайте начнем с чего-то более понятного.

Как это работает.need и fill являются нашими источниками данных.u - это объединение этих таблиц с потребность и заполнение данных в отдельных столбцах.Это объединение необходимо для того, чтобы следующий запрос работал.

c - это рекурсивный CTE, который начинается с первого fill .Это наш якорь.На следующем шаге я добавляю (в соединении) fill или нужную строку в зависимости от того, что мы получили в предыдущем rest.Если покой ниже нуля, это означает, что мы должны искать следующую fill строку.Если оно больше, значит, мы получили излишки от заливок , и мы можем искать следующую потребность .На каждом шаге подсчитывается сумма транзакции, которая равна более низкому значению от предыдущего остатка и текущего присоединенного заполнения / потребности.

Наконец, я беру суммы и обе стороны транзакции.Проверено на некоторых примерах.

демо

2 голосов
/ 10 мая 2019

Возможно, следующие идеи помогут: {1} написать 2 представления, которые разбивают значения NEED_QTY и FILL_QTY на «атомы», то есть наименьшие доступные единицы измерения. (Если вам нужен только один массивный запрос, вы можете включить код представления в окончательный запрос позже). {2} СОЕДИНИТЕ 2 вида {3}, используйте GROUP BY ROLLUP для получения полезного вывода.

Просмотры

-- NEED: required items, broken down into "atoms" ie smallest available quantities
create or replace view nvw
as
select
  need_id as nid
, partno
, need_qty as nqty
, date_needed as ndate
, single_item
, row_number() over ( order by need_id, partno, date_needed ) row_
from need_table N cross apply (
    select 1 as single_item from dual connect by level <= N.need_qty
) SN
;

-- FILL: available items, broken down into "atoms" ie smallest available quantities
create or replace view fvw
as
select
  fill_id as fid
, partno
, fill_qty as fqty
, date_available as fdate
, single_item
, row_number() over ( order by fill_id, partno, date_available ) row_
from fill_table F cross apply (
    select 1 as single_item from dual connect by level <= F.fill_qty
) SF
;

ПРИСОЕДИНИТЬСЯ к просмотрам

select *
from (
  select fid, partno, fqty, single_item, row_ from fvw
) FV join (
  select nid, partno, nqty, single_item, row_ from nvw
) NV on FV.row_ = NV.row_ ;

-- result
FID  PARTNO  FQTY  SINGLE_ITEM  ROW_  NID  PARTNO  NQTY  SINGLE_ITEM  ROW_  
X    123     2     1            1     A    123     4     1            1     
X    123     2     1            2     A    123     4     1            2     
Y    123     4     1            3     A    123     4     1            3     
Y    123     4     1            4     A    123     4     1            4     
Y    123     4     1            5     B    123     2     1            5     
Y    123     4     1            6     B    123     2     1            6     

6 rows selected. 

GROUP BY ROLLUP

-- dates omitted for clarity
select  NV.nid, FV.fid, sum( FV.single_item ) real_fill
from (
  select fid, partno, fqty, single_item, row_ from fvw
) FV join (
  select nid, partno, nqty, single_item, row_ from nvw
) NV on FV.row_ = NV.row_
group by rollup ( NV.nid, FV.fid );

-- result
NID   FID   REAL_FILL  
A     X     2          
A     Y     2          
A     NULL  4          
B     Y     2          
B     NULL  2          
NULL  NULL  6

Представления / запросы были написаны с центральной проблемой, то есть "заполняют множественные потребности частичными количествами". Вы можете добавить больше имен столбцов в предложение GROUP BY (например, даты). Протестировано с Oracle 12c и 18c. DBfiddle здесь .

Эту концепцию также можно использовать для сценария, описанного в вашем комментарии (2 пакета по 10 должны быть заполнены элементами, которые хранятся в 4 корзинах по 5 элементов в каждой) - см. DBfiddle .

...