Sql server SQL Server:应用一对一匹配
我使用的是SQL Server 2008 R2 我有一套付款: account_number transaction_amount transaction_date 0001 $250.00 01/01/2020 0001 $250.00 01/02/2020 0001 $500.00 02/01/2020 0002 $100.00 01/05/2020 0003 $150.00 02/05/2020Sql server SQL Server:应用一对一匹配,sql-server,Sql Server,我使用的是SQL Server 2008 R2 我有一套付款: account_number transaction_amount transaction_date 0001 $250.00 01/01/2020 0001 $250.00 01/02/2020 0001 $500.00 02/01/2020 0002
我知道你不应该使用游标,我相信有更好的解决方案,但我找到的唯一样本取决于精确匹配,而不是“小于”,我想不出一种方法来阻止他们将同一笔交易匹配到两个不同的退款,或者反之亦然 因为效率已经是您提到的一个问题,所以我决定使用双游标重新实现匹配算法,以最小化I/O成本 本质上是这样一个事实:该算法只需对每个数据集进行一次遍历(忽略排序和索引构建的成本)。为了实现这个目标,显式跟踪两个数据集的当前行对我来说似乎是不可避免的。在“内部循环”部分的注释中解释了这两个游标如何相互作用 免责声明:代码已在SQL Server 2017上测试,数据集略有修改。重点是展示双光标策略。根据您的实际情况,可能需要进行一些修改 测试数据集
use [testdb];
if OBJECT_ID('testdb..disburse') is not null
drop table testdb..disburse;
create table disburse (
account_number varchar(4),
transaction_amount money,
transaction_date date,
refund_date date,
refund_sequence int,
refund_amount money
);
insert into disburse (account_number, transaction_amount, transaction_date)
values ('0001', 250.00, '01/01/2020'),
('0001', 250.00, '01/02/2020'),
('0001', 500.00, '02/01/2020'),
('0002', 100.00, '01/05/2020'),
('0003', 150.00, '02/05/2020');
--select * from disburse;
if OBJECT_ID('testdb..refund') is not null
drop table testdb..refund;
create table refund (
account_number varchar(4),
transaction_amount money,
transaction_date date,
sequence_number int,
);
insert into refund (account_number, transaction_amount, transaction_date, sequence_number)
values ('0001', 250.00, '01/17/2020', 1),
('0001', 250.00, '01/17/2020', 2),
('0001', 700.00, '02/21/2020', 1),
('0003', 150.00, '02/21/2020', 1);
--select * from refund;
解决方案
/* cursor variables */
-- cursor 1: disburse
declare @d_an varchar(4),
@d_ta money,
@d_td date;
-- need a index to make the cursor updatable
create index #idx on disburse(account_number, transaction_date desc);
declare cur_d CURSOR local
for select account_number, transaction_amount, transaction_date
from disburse
order by account_number, transaction_date desc
for update of refund_date, refund_sequence, refund_amount;
open cur_d;
-- cursor 2: refund
declare @r_an varchar(4),
@r_ta money,
@r_td date,
@r_sn int;
declare cur_r CURSOR local
for select account_number, transaction_amount, transaction_date, sequence_number
from refund
order by account_number, transaction_date desc, sequence_number desc;
open cur_r;
-- state vairables
declare @flag int = 0; -- 0: normal, 1: break inner loop, 2: break all loops
/* main program */
-- read the first disburse record (ignoring emptiness check)
fetch next from cur_d into @d_an, @d_ta, @d_td;
-- outer loop: for each refund record
while 1=1 BEGIN
fetch next from cur_r into @r_an, @r_ta, @r_td, @r_sn;
-- termination check (no more refunds)
if @@FETCH_STATUS <> 0
break;
-- inner loop: find the disburse record
while 1=1 begin
/* the main control logic of cursor interaction */
-- 1) current disburse account > refund account -> read next refund record
if @d_an > @r_an
break;
-- 2) current disburse account < refund account -> read next disburse record
else if @d_an < @r_an
fetch next from cur_d into @d_an, @d_ta, @d_td;
-- 3) same account
else if @d_an = @r_an begin
-- save the result only when disburse record is earlier than refund record
if @d_td < @r_td begin
update disburse
set refund_date = @r_td,
refund_sequence = @r_sn,
refund_amount = @r_ta
where current of cur_d;
set @flag = 1; -- terminate the inner loop later on
end
-- proceed to next disburse record in any case
fetch next from cur_d into @d_an, @d_ta, @d_td;
end
-- termination checks
-- disburse records exhausted -> quit both loops
if @@FETCH_STATUS <> 0 begin
set @flag = 2;
break;
end
-- go to next refund record if this refund record has found its disburse record
if @flag = 1 begin
set @flag = 0; -- reset flag
break;
end
end
-- disburse records exhausted
if @flag = 2
break;
END
-- cleanup
close cur_d;
deallocate cur_d;
close cur_r;
deallocate cur_r;
我会以一种迭代的方式来做这件事——编写一个更新,根据id查找第一个unqiue数量并应用它,然后再次运行它,直到什么都没有剩下。我不知道你的表有多大,但你可能需要准确的索引。 account_number transaction_amount transaction_date sequence_number assigned 0001 $250.00 01/17/2020 1 01/02/2020 0001 $250.00 01/17/2020 2 01/01/2020 0001 $700.00 02/21/2020 1 NULL 0003 $150.00 02/21/2020 1 02/05/2020
IF OBJECT_ID('tempdb.dbo.#disb', 'U') IS NOT NULL
DROP TABLE #disb;
create table #disb (
account_number char(4)
,transaction_amount money
,transaction_date datetime
,refund_date datetime null
,refund_sequence int null
,refund_amount money null
)
insert into #disb (account_number, transaction_amount, transaction_date)
values
('0001',250.00,'01/01/2020'),
('0001',250.00,'01/02/2020'),
('0001',500.00,'02/01/2020'),
('0002',100.00,'01/05/2020'),
('0003',150.00,'02/05/2020')
IF OBJECT_ID('tempdb.dbo.#refund', 'U') IS NOT NULL
DROP TABLE #refund;
create table #refund (
account_number char(4)
,transaction_amount money
,transaction_date datetime
,sequence_number int
,assigned varchar(1) null
)
insert into #refund (account_number, transaction_amount, transaction_date, sequence_number)
values
('0001',250.00,'01/17/2020',1),
('0001',250.00,'01/17/2020',2),
('0001',700.00,'02/21/2020',1),
('0003',150.00,'02/21/2020',1)
DECLARE @account_number char(10), @refund_date datetime, @refund_seq int
DECLARE MY_CURSOR CURSOR
LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR
SELECT DISTINCT r.account_number, r.transaction_date, r.sequence_number
FROM #REFUND r
INNER JOIN #disb d on r.account_number = d.account_number and r.transaction_date > d.transaction_date and r.transaction_amount = d.transaction_amount
ORDER BY r.account_number, r.transaction_date, r.sequence_number
OPEN MY_CURSOR
FETCH NEXT FROM MY_CURSOR INTO @account_number, @refund_date, @refund_seq
WHILE @@FETCH_STATUS = 0
BEGIN
update i
set i.refund_date = r.transaction_date, i.refund_sequence = r.sequence_number, i.refund_amount = r.transaction_amount
from
#refund r
outer apply (
select top 1 d.*
from #disb d
where
r.account_number = d.account_number
and r.transaction_amount = d.transaction_amount
and d.refund_date is null
order by
d.transaction_date desc
) i
IF @@ROWCOUNT = 1
update #refund
set assigned = 'Y'
where account_number = @account_number
and transaction_date = @refund_date
and sequence_number = @refund_seq
FETCH NEXT FROM MY_CURSOR INTO @account_number, @refund_date, @refund_seq
END
CLOSE MY_CURSOR
DEALLOCATE MY_CURSOR
select * from #disb
select * from #refund
use [testdb];
if OBJECT_ID('testdb..disburse') is not null
drop table testdb..disburse;
create table disburse (
account_number varchar(4),
transaction_amount money,
transaction_date date,
refund_date date,
refund_sequence int,
refund_amount money
);
insert into disburse (account_number, transaction_amount, transaction_date)
values ('0001', 250.00, '01/01/2020'),
('0001', 250.00, '01/02/2020'),
('0001', 500.00, '02/01/2020'),
('0002', 100.00, '01/05/2020'),
('0003', 150.00, '02/05/2020');
--select * from disburse;
if OBJECT_ID('testdb..refund') is not null
drop table testdb..refund;
create table refund (
account_number varchar(4),
transaction_amount money,
transaction_date date,
sequence_number int,
);
insert into refund (account_number, transaction_amount, transaction_date, sequence_number)
values ('0001', 250.00, '01/17/2020', 1),
('0001', 250.00, '01/17/2020', 2),
('0001', 700.00, '02/21/2020', 1),
('0003', 150.00, '02/21/2020', 1);
--select * from refund;
/* cursor variables */
-- cursor 1: disburse
declare @d_an varchar(4),
@d_ta money,
@d_td date;
-- need a index to make the cursor updatable
create index #idx on disburse(account_number, transaction_date desc);
declare cur_d CURSOR local
for select account_number, transaction_amount, transaction_date
from disburse
order by account_number, transaction_date desc
for update of refund_date, refund_sequence, refund_amount;
open cur_d;
-- cursor 2: refund
declare @r_an varchar(4),
@r_ta money,
@r_td date,
@r_sn int;
declare cur_r CURSOR local
for select account_number, transaction_amount, transaction_date, sequence_number
from refund
order by account_number, transaction_date desc, sequence_number desc;
open cur_r;
-- state vairables
declare @flag int = 0; -- 0: normal, 1: break inner loop, 2: break all loops
/* main program */
-- read the first disburse record (ignoring emptiness check)
fetch next from cur_d into @d_an, @d_ta, @d_td;
-- outer loop: for each refund record
while 1=1 BEGIN
fetch next from cur_r into @r_an, @r_ta, @r_td, @r_sn;
-- termination check (no more refunds)
if @@FETCH_STATUS <> 0
break;
-- inner loop: find the disburse record
while 1=1 begin
/* the main control logic of cursor interaction */
-- 1) current disburse account > refund account -> read next refund record
if @d_an > @r_an
break;
-- 2) current disburse account < refund account -> read next disburse record
else if @d_an < @r_an
fetch next from cur_d into @d_an, @d_ta, @d_td;
-- 3) same account
else if @d_an = @r_an begin
-- save the result only when disburse record is earlier than refund record
if @d_td < @r_td begin
update disburse
set refund_date = @r_td,
refund_sequence = @r_sn,
refund_amount = @r_ta
where current of cur_d;
set @flag = 1; -- terminate the inner loop later on
end
-- proceed to next disburse record in any case
fetch next from cur_d into @d_an, @d_ta, @d_td;
end
-- termination checks
-- disburse records exhausted -> quit both loops
if @@FETCH_STATUS <> 0 begin
set @flag = 2;
break;
end
-- go to next refund record if this refund record has found its disburse record
if @flag = 1 begin
set @flag = 0; -- reset flag
break;
end
end
-- disburse records exhausted
if @flag = 2
break;
END
-- cleanup
close cur_d;
deallocate cur_d;
close cur_r;
deallocate cur_r;
select * from disburse;
| account_number | transaction_amount | transaction_date | refund_date | refund_sequence | refund_amount |
|----------------|--------------------|------------------|-------------|-----------------|---------------|
| 0001 | 250.0000 | 2020-01-01 | 2020-01-17 | 1 | 250.0000 |
| 0001 | 250.0000 | 2020-01-02 | 2020-01-17 | 2 | 250.0000 |
| 0001 | 500.0000 | 2020-02-01 | 2020-02-21 | 1 | 700.0000 |
| 0002 | 100.0000 | 2020-01-05 | NULL | NULL | NULL |
| 0003 | 150.0000 | 2020-02-05 | 2020-02-21 | 1 | 150.0000 |