Как найти несколько пар в одной таблице оракула - PullRequest
2 голосов
/ 10 апреля 2019

У меня проблема с поиском совпадений в одной таблице Oracle, и я надеюсь, что вы мне поможете.

У меня есть одна таблица счетов, содержащая данные бронирования, которые выглядят следующим образом:

ID  GROUP  Bill-Number  Value    Partner-Number
 1    1      111         10,90
 2    1      751         40,28
 3    1      438        125,60 
 4    1      659        -10,90        987 
 5    1      387       -165,88        755 
 6    1      774       -100,10 
 7    1      664        -80,12 
 8    1      259        180,22        999
 9    2      774       -200,10 
10    2      664        -80,12 
11    2      259        280,22        777

Как видите, у нас есть несколько счетов, которые содержат расходы.Через некоторое время приходит счетчик, который суммирует предыдущие расходы.Сумма счетов и связанных счетчиков создает значение 0.

Пример: value of (id 2 + id 3 = id 5*-1) или в числах: 40,28 + 125,60 + (-165,88) = 0

Счетчики содержат "Партнер-Number».Мне нужно добавить эту информацию в соответствующие счета.

Решение должно выглядеть следующим образом:

ID  GROUP  Bill-Number  Value    Partner-Number
 1    1      111         10,90        987
 2    1      751         40,28        755
 3    1      438        125,60        755
 4    1      659        -10,90        987 
 5    1      387       -165,88        755 
 6    1      774       -100,10        999
 7    1      664        -80,12        999
 8    1      259        180,22        999
 9    2      774       -200,10        777
10    2      664        -80,12        777
11    2      259        280,22        777

Я должен сопоставлять счета только внутри группы.(Идентификатор - мой основной ключ). Пока группа содержит один счет-фактуру с отношением 1: 1 к счету, это выполнимо для меня.

Но как мне найти совпадения, как в группе 1где отношение 1: N?(группа содержит несколько встречных счетов)

Надеюсь, вы мне поможете - заранее спасибо:)

Ответы [ 2 ]

1 голос
/ 12 апреля 2019

Следующий код SQL был протестирован с Oracle 12c и 18c соответственно. Идеи / шаги:

{1} Разделить исходную таблицу на таблицу MINUSES и PLUSES, содержащую только положительные числа, что сэкономит нам несколько вызовов функций позже.

{2} Создайте 2 представления, которые найдут комбинации плюсов, которые соответствуют определенному минусу (и наоборот).

{3} Вывести список всех компонентов в виде «разделенных запятыми» в таблице с именем ALLCOMPONENTS.

{4} Таблица GAPFILLERS: разверните идентификаторы (разделенные запятыми) всех компонентов, получив таким образом все необходимые значения, чтобы заполнить пробелы в исходной таблице.

{5} СЛЕДУЕТ ПРИСОЕДИНИТЬСЯ к оригинальной таблице к GAPFILLERS.

Исходная таблица / данные

create table bills ( id primary key, bgroup, bnumber, bvalue, partner )
as
select 1, 1, 111, 10.90, null from dual union all
select 2, 1, 751, 40.28, null from dual union all
select 3, 1, 438, 125.60, null from dual union all 
select 4, 1, 659, -10.90, 987 from dual union all
select 5, 1, 387, -165.88, 755 from dual union all
select 6, 1, 774, -100.10, null from dual union all 
select 7, 1, 664, -80.12, null from dual union all 
select 8, 1,   259, 180.22, 999 from dual union all
select 9, 2,   774, -200.10, null from dual union all 
select 10, 2,   664, -80.12, null from dual union all 
select 11, 2,   259, 280.22, 777 from dual ;

{1} разбить таблицу на таблицу ПЛЮС и МИНУС

-- MINUSes
create table minuses as
select id
, bgroup       as mgroup
, bnumber      as mnumber
, bvalue * -1  as mvalue
, partner      as mpartner 
from bills where bvalue < 0 ;

-- PLUSes
create table pluses as
select id
, bgroup  as pgroup
, bnumber as pnumber
, bvalue  as pvalue
, partner as ppartner  
from bills where bvalue >= 0 ;

{2} Просмотр: найти компоненты PLUSvalues ​​

-- used here: "recursive subquery factoring" 
-- and LATERAL join (needs Oracle 12c or later)
create or replace view splitpluses
as
with recursiveclause ( nextid, mgroup, tvalue, componentid )
as (
  select                     -- anchor member
    id            as nextid
  , mgroup        as mgroup
  , mvalue        as tvalue  -- total value 
  , to_char( id ) as componentid
  from minuses
  union all
  select                     -- recursive member
    M.id
  , R.mgroup
  , R.tvalue + M.mvalue
  , R.componentid || ',' || to_char( M.id )
  from recursiveclause R
    join minuses M
      on M.id > R.nextid and M.mgroup = R.mgroup -- only look at values in the same group
)
--
select
  mgroup
, tvalue      as plusvalue
, componentid as minusids
, ppartner
from 
  recursiveclause R
, lateral ( select ppartner from pluses P where R.tvalue = P.pvalue ) -- fetch the partner id
where 
  tvalue in ( select pvalue from pluses where ppartner is not null ) -- get all relevant pvalues that must be broken down into components
  and ppartner is not null -- do this for all pluses that have a partner id
;

{2b} Просмотр: найти компоненты значений MINUS

create or replace view splitminuses
as
with recursiveclause ( nextid, pgroup, tvalue, componentid )
as (
  select                     -- anchor member
    id            as nextid
  , pgroup        as pgroup
  , pvalue        as tvalue  -- total value 
  , to_char( id ) as componentid
  from pluses
  union all
  select                     -- recursive member
    P.id
  , R.pgroup
  , R.tvalue + P.pvalue
  , R.componentid || ',' || to_char( P.id )
  from recursiveclause R
    join pluses P
      on P.id > R.nextid and P.pgroup = R.pgroup
)
--
select
  pgroup
, tvalue      as minusvalue
, componentid as plusids
, mpartner
from 
  recursiveclause R
, lateral ( select mpartner from minuses M where R.tvalue = M.mvalue )
where 
  tvalue in ( select mvalue from minuses where mpartner is not null )
  and mpartner is not null
;

Представления дают нам следующие наборы результатов:

SQL> select * from splitpluses;
MGROUP  PLUSVALUE  MINUSIDS  PPARTNER  
1       180.22     6,7       999       
2       280.22     9,10      777    

SQL> select * from splitminuses ;
PGROUP  MINUSVALUE  PLUSIDS  MPARTNER  
1       10.9        1        987       
1       165.88      2,3      755 

{3} Таблица ALLCOMPONENTS: список всех «компонентов»

create table allcomponents ( type_, group_, value_, cids_, partner_ )
as
select 'components of PLUS' as type_, M.* from splitminuses M
union all
select 'components of MINUS', P.* from splitpluses P
;

SQL> select * from allcomponents ;
TYPE_                GROUP_  VALUE_  CIDS_  PARTNER_  
components of PLUS   1       10.9    1      987       
components of PLUS   1       165.88  2,3    755       
components of MINUS  1       180.22  6,7    999       
components of MINUS  2       280.22  9,10   777 

{4} Таблица GAPFILLERS: получена из ALLCOMPONENTS, содержит все значения, необходимые для заполнения «пробелов» в исходной таблице.

-- One row for each CSV (comma-separated value) of ALLCOMPONENTS
create table gapfillers
as
select unique type_, group_, value_
, trim( regexp_substr( cids_, '[^,]+', 1, level ) ) cids_
, partner_
from (
  select type_, group_, value_, cids_, partner_
  from allcomponents
) AC 
connect by instr( cids_, ',', 1, level - 1 ) > 0
order by group_, partner_ ;

SQL> select * from gapfillers ;
TYPE_                GROUP_  VALUE_  CIDS_  PARTNER_  
components of PLUS   1       165.88  2      755       
components of PLUS   1       165.88  3      755       
components of PLUS   1       10.9    1      987       
components of MINUS  1       180.22  6      999       
components of MINUS  1       180.22  7      999       
components of MINUS  2       280.22  10     777       
components of MINUS  2       280.22  9      777       

7 rows selected.

{5} ФИЛЬТР ЛЕВНОГО СОЕДИНЕНИЯ

select
  B.id, bgroup, bnumber, bvalue
, case 
    when B.partner is null then G.partner_
    else B.partner
  end as partner
from bills B
  left join gapfillers G on B.id = G.cids_ 
order by 1 ; 

-- result
ID  BGROUP  BNUMBER  BVALUE   PARTNER  
1   1       111      10.9     987      
2   1       751      40.28    755      
3   1       438      125.6    755      
4   1       659      -10.9    987      
5   1       387      -165.88  755      
6   1       774      -100.1   999      
7   1       664      -80.12   999      
8   1       259      180.22   999      
9   2       774      -200.1   777      
10  2       664      -80.12   777      
11  2       259      280.22   777      


11 rows selected. 

DBFIDDLE здесь .

1 голос
/ 11 апреля 2019

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

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

Я показываю запросы для первогодва шага, вы должны получить представление о том, как действовать - скорее всего, с использованием динамического SQL в цикле.

Первый шаг - тривиальное самостоятельное присоединение к таблице и ограничение GROUP и value.Создается таблица результатов, которая в дальнейшем используется для ограничения уже сопоставленных строк.

create table tab_match as 
-- 1 row match
select b.ID, b.GROUP_ID, b.BILL_NUMBER, b.VALUE, a.partner_number from tab a
join tab b 
on a.group_id = b.group_id and /* same group */
-1 * a.value = b.value /* oposite value */
where a.partner_number is not NULL  /* consider group row only */

На втором шаге вы повторяете то же самое, добавляя только одно объединение (мы исследуем две суббилла) с дополнительным ограничением наобщее значение -1 * a.value = (b.value + c.value) Также мы подавляем все partner_number s и bills, уже назначенные.Результат вставляется во временную таблицу.

insert into tab_match (ID, GROUP_ID, BILL_NUMBER, VALUE, PARTNER_NUMBER)
select b.ID, b.GROUP_ID, b.BILL_NUMBER, b.VALUE, a.partner_number partner_number_match from tab a
join tab b 
on a.group_id = b.group_id and /* same group */
sign(a.value) * sign(b.value) < 0 and  /* values must have oposite signs */
abs(a.value) > abs(b.value) /* the partial value is lower than the sum */
join tab c /* join to 2nd table */
on a.group_id = c.group_id and 
sign(a.value) * sign(c.value) < 0 and 
abs(a.value) > abs(c.value) and
-1 * a.value = (b.value + c.value)
where a.partner_number is not NULL and /* consider open group row only */
a.partner_number  not in (select partner_number from tab_match) and
a.id not in (select id from tab_match) /* ignore matched rows */
;

Необходимо выполнить обработку 3,4 и т. Д. Строк, пока не будут назначены все partner_number s и bills.

Добавитьследующее соединение

join tab d  
on a.group_id = d.group_id and 
sign(a.value) * sign(d.value) < 0 and 
abs(a.value) > abs(d.value)

и настройка предиката общей суммы на каждом шаге

-1 * a.value = (b.value + c.value + d.value)

Удачи;)

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