如何在一个Oracle表中查找多个对

时间:2019-04-10 05:34:27

标签: sql database oracle

在一个Oracle-Table中查找匹配项时遇到问题,希望您能对我有所帮助。

我有一个帐单表,其中包含如下预订数据:

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

还价单中包含“合作伙伴编号”。我需要将此信息添加到关联的帐单中。

解决方案应如下所示:

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

我只需要在一组内匹配账单。 (ID是我的主键) 只要该组包含一个与票据具有1:1关系的反票据,对我来说就是可行的。

但是我如何找到像第1组那样的比例为1:N的比赛? (该组包含多个反制)

希望您能帮到我-谢谢您:)

2 个答案:

答案 0 :(得分:1)

SQL是解决
强力问题的理想选择(唯一的问题是,对于大数据,查询将永远

这里是一种可能的分步方法,在第一步中只考虑一个反付帐,在第二步中只考虑两个反付帐,等等。

我正在显示前两个步骤的查询,您应该了解如何进行操作-最有可能在循环中使用动态SQL。

第一步是简单的自联接,联接表并约束GROUPvalue。将创建一个结果表,该表将进一步用于限制已经匹配的行。

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_numberbills。结果插入到临时表中。

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_numberbills

添加下一个联接

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)

祝你好运;)

答案 1 :(得分:1)

以下SQL代码已分别与Oracle 12c和18c进行了测试。想法/步骤:

{1}将原始表拆分为MINUSES和PLUSES表,其中仅包含正数,从而为我们节省了一些函数调用。

{2}创建2个视图,这些视图将找到适合特定负号的正负号的组合(反之亦然)。

{3}在“ ALLCOMPONENTS”表中以“逗号分隔”的形式列出所有组件。

{4}表GAPFILLERS:扩展所有组件的ID(以逗号分隔),从而获得所有必要的值以填补原始表中的空白。

{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}将表拆分为PLUS和MINUS表

-- 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}视图:查找MINUSvalues的分量

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}最终的LEFT JOIN

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 here