AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / dba / 问题 / 18089
Accepted
Scott Chamberlain
Scott Chamberlain
Asked: 2012-05-18 09:56:54 +0800 CST2012-05-18 09:56:54 +0800 CST 2012-05-18 09:56:54 +0800 CST

对数据集执行“负传播”

  • 772

我有一种情况,代表未结余额和贷方的表都包含在一个表中。我需要做的是将所有未清信用额(最好是从最早开始的顺序,但不是必需的)应用到所有未清余额(从最早开始的顺序)。

例如(负余额代表贷方)

Account_ID  DateOfEntry  Balance  
----------  -----------  -------  
1           1/1/2012     10.00    
1           1/2/2012     -15.00
2           1/1/2012     -15.00
2           1/2/2012     10.00
3           1/1/2012     10.00
3           1/2/2012     1.00
3           1/3/2012     -5.00
4           1/1/2012     5.00
4           1/2/2012     5.00
4           1/3/2012     -7.00
5           1/1/2012     10.00
5           1/2/2012     -5.00
5           1/3/2012     -5.00

会变成:

Account_ID  DateOfEntry  Balance  
----------  -----------  -------    
1           1/2/2012     -5.00
2           1/1/2012     -5.00
3           1/1/2012     5.00
3           1/2/2012     1.00
4           1/2/2012     2.00

这是发生的事情的细目

  • 帐户 1 和帐户 2 留下信用(表明付款顺序和信用相对于彼此无关紧要)
  • 帐户 3 留下了两个余额(表明信用首先应用于最旧的余额)
  • 帐户 4 在 2012 年 1 月 2 日还剩一笔余额(表明如果第一个余额不满足信用额度,信用额度将应用于下一个最旧的余额)
  • 账户 5 消失了,因为贷方与余额完全匹配。

这是我的真实表中相关列的架构

CREATE TABLE [dbo].[IDAT_AR_BALANCES](
    [cvtGUID] [uniqueidentifier] ROWGUIDCOL  NOT NULL,
    [CLIENT_ID] [varchar](11) NOT NULL,
    [AGING_DATE] [datetime] NOT NULL,
    [AMOUNT] [money] NOT NULL,
 CONSTRAINT [PK_IDAT_ARBALANCES] PRIMARY KEY CLUSTERED ([cvtGUID] ASC)
)

目前我用一个游标循环所有可用的学分来做到这一点。

--Remove AR that totals to 0.
DELETE FROM IDAT_AR_BALANCES 
WHERE client_id IN ( 
SELECT client_id 
FROM IDAT_AR_BALANCES 
GROUP BY client_id 
HAVING SUM(amount) = 0)

--Spred the credits on to existing balances.
select * into #balances from [IDAT_AR_BALANCES] where amount > 0
select * into #credits from [IDAT_AR_BALANCES] where amount < 0

declare credit_cursor cursor for select [CLIENT_ID], amount, cvtGUID from #credits 

open credit_cursor 

declare @client_id varchar(11) 
declare @credit money 
declare @balance money 
declare @cvtGuidBalance uniqueidentifier 
declare @cvtGuidCredit uniqueidentifier 

fetch next from credit_cursor into @client_id, @credit, @cvtGuidCredit 
while @@fetch_status = 0 
begin 
  --While balances exist for the current client_ID and there are still credits to be applied, loop.
  while(@credit < 0 and (select count(*) from #balances where @client_id = CLIENT_ID and amount <> 0) > 0) 
  begin 
    --Find the oldest oustanding balance.
    select top 1  @balance = amount, @cvtGuidBalance = cvtGuid 
    from #balances 
    where @client_id = CLIENT_ID and amount <> 0 
    order by AGING_DATE 

    -- merge the balance and the credit
    set @credit = @balance + @credit 

    --If the credit is now postive save the leftover in the currently selected balance and set the credit to 0
    if(@credit > 0) 
    begin 
      update #balances set amount = @credit where cvtGuid = @cvtGuidBalance 
      set @credit = 0 
    end
    else -- Credit is larger than the balance, 0 out the balance and continue processesing
      update #balances set amount = 0 where cvtGuid = @cvtGuidBalance 

  end  -- end of while loop

  --There are no more balances to apply the credit to, save it back to the list.
  update #credits set amount = @credit where cvtGuid = @cvtGuidCredit 

  --Get the next credit.
  fetch next from credit_cursor into @client_id, @credit, @cvtGuidCredit 
end 
close credit_cursor 
deallocate credit_cursor

--Delete any balances and credits that where 0'ed out durning the spred negitive.
delete #balances where AMOUNT = 0
delete #credits where AMOUNT = 0

truncate table [IDAT_AR_BALANCES]
insert [IDAT_AR_BALANCES] select * from #balances
insert [IDAT_AR_BALANCES] select * from #credits
drop table #balances
drop table #credits

我确信有更好的方法可以做到这一点,所以我可以在没有光标的情况下做到这一点并从中获得更好的性能,但我很难弄清楚如何在不使用光标。

sql-server sql-server-2005
  • 2 2 个回答
  • 138 Views

2 个回答

  • Voted
  1. Best Answer
    Aaron Bertrand
    2012-05-18T10:24:18+08:002012-05-18T10:24:18+08:00

    通常对于诸如运行总计之类的操作,使用游标实际上比其他一些方法更有效。不要害怕使用游标,除非您看到明显的性能问题。

    在 SQL Server 2012 中,有新的窗口函数使它变得更好,但显然你不能使用它们。有一种古怪的更新方法“有效”,但该语法不受官方支持,并且在 SQL Server 2012 之后可能无法正常工作——它可能有一天会成为非法语法,并且永远无法保证排序的工作方式。典型的基于集合的方法不能很好地扩展——使用游标通常会更好,因为您只需扫描每一行一次,而使用基于集合的解决方案,扫描会非线性增长。我希望我能向您展示我在这方面所做的工作,但揭示这一切的博客文章的发布日期仍然未知。

    至少在声明游标时使用:

    DECLARE ... CURSOR LOCAL STATIC READ_ONLY FORWARD_ONLY
    

    这也可能是有用的阅读:

    • 要改掉的坏习惯:认为 while 循环不是游标
    • 1
  2. Scott Chamberlain
    2012-05-18T12:47:57+08:002012-05-18T12:47:57+08:00

    多亏了 Aaron 的建议,我没有尝试让整个事物都基于集合,而是尝试在集合最适用的地方做集合,然后对其余部分使用游标。

    我还能够改变必须只从最旧的信用中提取的要求,现在我可以将每个客户的信用汇总到一个桶中并与之对应。

    现在快多了,这是更新版本

    --indexes to speed up first two queires
    IF not EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'[dbo].[IDAT_AR_BALANCES]') AND name = N'IX_IDATARBALANCES_CLIENTID_GUID')
        CREATE NONCLUSTERED INDEX IX_IDATARBALANCES_CLIENTID_GUID
        ON [dbo].[IDAT_AR_BALANCES] ([CLIENT_ID])
        INCLUDE ([cvtGUID])
    
    IF not EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'[dbo].[IDAT_AR_BALANCES]') AND name = N'IX_IDATARBALANCES_AMOUNT_ID_DATE')   
        CREATE NONCLUSTERED INDEX [IX_IDATARBALANCES_AMOUNT_ID_DATE]
        ON [dbo].[IDAT_AR_BALANCES] ([AMOUNT])
        INCLUDE ([CLIENT_ID],[AGING_DATE])
    
    --Remove AR that totals to 0.
    DELETE FROM IDAT_AR_BALANCES 
    WHERE client_id IN ( 
    SELECT client_id 
    FROM IDAT_AR_BALANCES 
    GROUP BY client_id 
    HAVING SUM(amount) = 0)
    
    --find all instances that credit > balance
    SELECT newid() as cvtGUID, client_id, max(AGING_DATE) as AGING_DATE, sum(AMOUNT) as amount
    into #creditLarger
    FROM IDAT_AR_BALANCES 
    GROUP BY client_id 
    HAVING SUM(amount) < 0
    
    --remove all of the creditLargerEntries
    delete IDAT_AR_BALANCES where client_id in (select client_id from #creditLarger)
    
    --Build a list of remaining balances and summed credits
    select * into #balances from [IDAT_AR_BALANCES] where amount > 0
    
    SELECT newid() as cvtGUID, client_id, max(AGING_DATE) as AGING_DATE, sum(AMOUNT) as amount
    into #credits 
    FROM [IDAT_AR_BALANCES] 
    where amount < 0 
    GROUP BY client_id
    
    --Index to make the update faster
    CREATE NONCLUSTERED INDEX BALANCE_INDEX ON #balances ([CLIENT_ID],[AMOUNT])
    
    --Begin loop of processing credits
    set nocount on
    declare credit_cursor cursor LOCAL STATIC READ_ONLY FORWARD_ONLY for select top 10 [CLIENT_ID], amount, cvtGUID from #credits 
    open credit_cursor 
    
    declare @client_id varchar(11) 
    declare @credit money 
    declare @balance money 
    declare @cvtGuidBalance uniqueidentifier 
    declare @cvtGuidCredit uniqueidentifier 
    
    fetch next from credit_cursor into @client_id, @credit, @cvtGuidCredit 
    while @@fetch_status = 0 
    begin 
      --While balances exist for the current client_ID and there are still credits to be applied, loop.
      while(@credit < 0 and exists(select * from #balances where @client_id = CLIENT_ID and amount > 0)) 
      begin 
        --Find the oldest oustanding balance.
        select top 1  @balance = amount, @cvtGuidBalance = cvtGuid 
        from #balances 
        where @client_id = CLIENT_ID and amount <> 0 
        order by AGING_DATE 
    
        -- merge the balance and the credit
        set @credit = @balance + @credit 
    
        if(@credit > 0) 
        begin 
          --If the credit is now postive save the leftover in the currently selected balance and set the credit to 0
          update #balances set amount = @credit where cvtGuid = @cvtGuidBalance  
          set @credit = 0 
        end
        else
          -- Credit is larger than the balance, 0 out the balance and continue processesing
          update #balances set amount = 0 where cvtGuid = @cvtGuidBalance  
    
      end -- end of while loop
    
      --There are no more balances to apply the credit to, save it back to the list.
      update #credits set amount = @credit where cvtGuid = @cvtGuidCredit 
    
      --Get the next credit.
      fetch next from credit_cursor into @client_id, @credit, @cvtGuidCredit 
    end 
    close credit_cursor 
    deallocate credit_cursor
    set nocount off
    
    truncate table [IDAT_AR_BALANCES]
    
    insert into [IDAT_AR_BALANCES] 
        select * from #balances
        union select * from #credits
        union select * from #creditLarger
    
    --Delete any balances and credits that where 0'ed out durning the spred negitive.
    delete [IDAT_AR_BALANCES] where amount = 0
    
    drop table #balances
    drop table #credits
    drop table #creditLarger
    
    • 0

相关问题

  • 我需要为每种类型的查询使用单独的索引,还是一个多列索引可以工作?

  • 什么时候应该使用唯一约束而不是唯一索引?

  • 死锁的主要原因是什么,可以预防吗?

  • 如何确定是否需要或需要索引

  • 从 SQL Server 2008 降级到 2005

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    如何查看 Oracle 中的数据库列表?

    • 8 个回答
  • Marko Smith

    mysql innodb_buffer_pool_size 应该有多大?

    • 4 个回答
  • Marko Smith

    列出指定表的所有列

    • 5 个回答
  • Marko Smith

    从 .frm 和 .ibd 文件恢复表?

    • 10 个回答
  • Marko Smith

    如何在不修改我自己的 tnsnames.ora 的情况下使用 sqlplus 连接到位于另一台主机上的 Oracle 数据库

    • 4 个回答
  • Marko Smith

    你如何mysqldump特定的表?

    • 4 个回答
  • Marko Smith

    如何选择每组的第一行?

    • 6 个回答
  • Marko Smith

    使用 psql 列出数据库权限

    • 10 个回答
  • Marko Smith

    如何从 PostgreSQL 中的选择查询中将值插入表中?

    • 4 个回答
  • Marko Smith

    如何使用 psql 列出所有数据库和表?

    • 7 个回答
  • Martin Hope
    Mike Walsh 为什么事务日志不断增长或空间不足? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland 列出指定表的所有列 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney MySQL 能否合理地对数十亿行执行查询? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx 如何监控大型 .sql 文件的导入进度? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison 你如何mysqldump特定的表? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    pedrosanta 使用 psql 列出数据库权限 2011-08-04 11:01:21 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 对 SQL 查询进行计时? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas 如何从 PostgreSQL 中的选择查询中将值插入表中? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 列出所有数据库和表? 2011-02-18 00:45:49 +0800 CST
  • Martin Hope
    bernd_k 什么时候应该使用唯一约束而不是唯一索引? 2011-01-05 02:32:27 +0800 CST

热门标签

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve