Oracle SQL / PLSQL: иерархический запрос для разделения и суммирования значений на основе валюты - PullRequest
1 голос
/ 12 марта 2019

Если повезет, я могу описать свое требование, не путая себя ...

У меня есть многоуровневый набор данных, где пользователи могут отмечать, какие записи суммировать в разные итоги.

Правила ввода данных:

  1. Если проверен столбец «Базовый блок» (True / False) (т. Е. True), то «Add for Option #» и «Remove for Option #» должны быть NULL.
  2. Если установлено значение «Добавить для варианта №» или «Удалить для варианта №», то столбец «Базовый блок» должен быть ЛОЖЬ.
  3. ЕСЛИ «Добавить для опции #» тогда установлено; список опций (буквы AZ, разделенные точками с запятой) не может содержать букву, которая находится в той же структуре на другом уровне (например, для последовательности с номером = 300, «Добавить для опции #» не может включать «B» или « D. То же правило применяется к столбцу «Удалить для опции #».
  4. Если задано «Добавить для варианта №» или «Удалить для варианта №», то одна и та же строка не может содержать, например, одну и ту же опцию (то есть букву); для последовательности № = 300 «Добавить для опции #» не может включать «B» или «D».

Столбец «Стоимость закупки / Curr» заполняется только для самого низкого уровня для каждой структуры - эти значения уже умножены на Кол-во.

Мне нужна функция PLSQL, которая будет: - В зависимости от параметра (is_base_ / option_) найдите все строки, в которых столбец «Базовый блок» = is_base_ ИЛИ (столбец «Добавить для варианта №» или столбец «Удалить для варианта №» содержит option_) , Например:

select sequence_no,
       part_no,
       component_part,
       base_unit
       add_for_option,
       remove_for_option
from prod_conf_cost_struct
where (base_unit = is_base_ and base_unit = 'True' 
    or (add_for_option = option_ or remove_for_option = option_));
  • Переберите все строки, возвращенные запросом выше, и суммируйте PER CURRENCY итоговую сумму столбца 'Cost Cost / Curr' для каждого уровня в структуре. Основная проблема, с которой я сталкиваюсь, состоит в том, что, получив общее количество для строки уровня 2, я не могу умножить его на «Кол-во» на этом уровне, прежде чем продолжить структуру.

    • При расчете итогового значения для опции значение «Удалить для опции #» должно быть вычтено из значения «Добавить для опции #».

Вот моя НОВАЯ попытка:

function calc_cost(
  model_no_ number, 
  revision_ number, 
  sequence_no_ in number, 
  currency_ in varchar2
) return number is
  qty_ number := 0;
  cost_ number := 0;
begin

  select nvl(new_qty, qty), purch_cost 
    into qty_, cost_ 
  from prod_conf_cost_struct_clv
  where model_no = model_no_
    and revision = revision_
    and sequence_no = sequence_no_
    and (purch_curr = currency_ or purch_curr is null);

  if cost_ is null then 
    select sum(calc_cost(model_no, revision, sequence_no, purch_curr)) into cost_ 
    from prod_conf_cost_struct_clv 
    where model_no = model_no_
      and revision = revision_
      and (purch_curr = currency_ or purch_curr is null)
      and part_no in (
        select component_part
        from prod_conf_cost_struct_clv
        where model_no = model_no_
          and revision = revision_
          and sequence_no = sequence_no_);
  end if;
  return qty_ * cost_;
exception when no_data_found then 
  return 0;
end calc_cost;

Помимо столбца «Номер последовательности» есть «Номер модели» и «Редакция».

MODEL_NO - REVISION - SEQUENCE_NO составит составной ключ.

select level, sys_connect_by_path(sequence_no, '->') path, 
     calc_cost(model_no, revision, sequence_no, 'GBP') total_gbp,
     calc_cost(model_no, revision, sequence_no, 'EUR') total_eur,
     calc_cost(model_no, revision, sequence_no, 'USD') total_usd
     calc_cost(model_no, revision, sequence_no, '???') total_other
from prod_conf_cost_struct_clv
where model_no = 35
  and revision = 2
connect by prior component_part = part_no
  and prior model_no = 30
  and prior revision = 2
start with sequence_no = 22500
order by sequence_no

Мне нужно, чтобы итоговые суммы по каждой валюте суммировались следующим образом (когда столбец База / Опция пуст, он будет суммироваться, когда BASE_UNIT = True):

expected result

1 Ответ

1 голос
/ 12 марта 2019

Боюсь, что вы все запутали :)

Хотя ваши требования частично трудно понять, я думаю, что есть одна вещь, которую я бы сделал, если бы мне пришлось решать такую ​​задачу.Я написал бы рекурсивную функцию, вычисляющую стоимость от любой части дерева до листьев.

Вот демонстрация данных, аналогичных вашим:

select prod.*, level, sys_connect_by_path(seq, '->') path, 
       calc_cost(comp) total
  from prod connect by prior comp = part
  start with base = 1;


   SEQ PART COMP        QTY       COST CURR       BASE AFO RFO   LEVEL PATH             TOTAL
------ ---- ---- ---------- ---------- ---- ---------- --- --- ------- ----------- ----------
     1 A    A1            5                          1               1 ->1                850
     2 A1   A11           3                          0 B             2 ->1->2             114
     3 A11  A111          4          2 EUR           0     B;D       3 ->1->2->3            8
     4 A11  A112          2         15 EUR           0               3 ->1->2->4           30
     5 A1   A12           8          7 EUR           0               2 ->1->5              56
    11 B    B1            5                          1               1 ->11               870
    12 B1   B11           3                          0               2 ->11->12           174
    13 B11  B111          4         12 GBP           0               3 ->11->12->13        48
    14 B11  B112          2          5 GBP           0               3 ->11->12->14        10

Столбец total содержит затраты на компоненты, например, для B1 это 5 * (3 * (4 * 12 + 2 * 5)), что составляет 870.

Данные о функциях и примерах приведены здесь:

create or replace function calc_cost(i_comp in varchar2) return number is
  v_qty number := 0;
  v_cost number := 0;
begin
  select qty, cost into v_qty, v_cost from prod where comp = i_comp;
  if v_cost is null then 
    select sum(calc_cost(comp)) into v_cost from prod where part = i_comp;
  end if;
  return v_qty * v_cost;
exception when no_data_found then 
  return 0;
end;

Данные:

create table prod(seq, part, comp, qty, cost, curr, base, afo, rfo) as (
    select  1, 'A',   'A1',   5, null, null,  1, null, null  from dual union all
    select  2, 'A1',  'A11',  3, null, null,  0, 'B',  null  from dual union all
    select  3, 'A11', 'A111', 4,    2, 'EUR', 0, null, 'B;D' from dual union all
    select  4, 'A11', 'A112', 2,   15, 'EUR', 0, null, null  from dual union all
    select  5, 'A1',  'A12',  8,    7, 'EUR', 0, null, null  from dual union all
    select 11, 'B',   'B1',   5, null, null,  1, null, null  from dual union all
    select 12, 'B1',  'B11',  3, null, null,  0, null, null  from dual union all
    select 13, 'B11', 'B111', 4,   12, 'GBP', 0, null, null  from dual union all
    select 14, 'B11', 'B112', 2,    5, 'GBP', 0, null, null  from dual );

Вы не указали, могут ли разные валюты быть одинаковыми part/ component и, если да, то как будет выводиться, как.В любом случае вы можете найти эти валюты и сделать расчеты для каждой валюты независимо.Вам нужно добавить второй параметр в функцию и написать что-то вроде where part = i_comp and curr = i_curr or curr is null.

Также для ask_for_option / remove_for_option вы, вероятно, можете справиться с ними в case when.

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

Но я надеюсь, что эта функция может помочь вам решить проблему.Я предположил, что если cost не равно нулю, то мы в листе, иначе функция рекурсивно ищет подкомпоненты.


Редактировать:

Допустим,что seq = 14 было в евро, а не в фунтах стерлингов

update prod set curr = 'EUR' where seq = 14;

Как я уже сказал, точное решение зависит от того, какой результат вам нужен.Если вы знаете все возможные валюты, тогда вы можете изменить функцию для обработки валюты и отображать расходы, как показано здесь:

create or replace function calc_cost(i_comp in varchar2, i_curr in varchar2) 
  return number is

  v_qty number := 0;
  v_cost number := 0;
begin

  select qty, cost into v_qty, v_cost from prod 
    where comp = i_comp and (curr = i_curr or curr is null);

  if v_cost is null then 
    select sum(calc_cost(comp, i_curr)) into v_cost 
      from prod where part = i_comp and (curr = i_curr or curr is null);
  end if;
  return v_qty * nvl(v_cost, 0);
exception when no_data_found then 
  return 0;
end;

select seq, part, comp, qty, cost, curr, 
       calc_cost(comp, 'EUR') eur, calc_cost(comp, 'GBP') gbp
  from prod 
  connect by part = prior comp
  start with part = 'B';

   SEQ PART COMP        QTY       COST CURR        EUR        GBP
 ----- ---- ---- ---------- ---------- ---- ---------- ----------
    11 B    B1            5                        150        720
    12 B1   B11           3                         30        144
    13 B11  B111          4         12 GBP           0         48
    14 B11  B112          2          5 EUR          10          0      

Part B стоит 150 EUR и 720 GBP.

Вы можете найти все различные валюты в интересующей части данных, объединить их со своей таблицей и вызвать функцию таким образом.Результатом будет то, что для каждого seq вы получите столько строк, сколько разных валют.Затем вы можете использовать listagg() и представлять значения как 150 EUR; 720 GBP в одной ячейке.

Вы также можете создать объект некоторого типа и изменить функцию, чтобы она возвращала таблицу кортежей (currency, cost_in_this_currency).Вопрос в том, как вы хотите показать данные.Или вы можете конвертировать значения в единую валюту, но для этого вам нужна ежедневная таблица коэффициентов.

...