Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/sql-server/23.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Sql server SQL Server:应用一对一匹配_Sql Server - Fatal编程技术网

Sql server SQL Server:应用一对一匹配

Sql 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

我使用的是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/2020
我知道你不应该使用游标,我相信有更好的解决方案,但我找到的唯一样本取决于精确匹配,而不是“小于”,我想不出一种方法来阻止他们将同一笔交易匹配到两个不同的退款,或者反之亦然

因为效率已经是您提到的一个问题,所以我决定使用双游标重新实现匹配算法,以最小化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      |