【问题标题】:How can I spread a value across multiple rows without a cursor?如何在没有游标的情况下将值分布在多行中?
【发布时间】:2012-08-31 11:49:22
【问题描述】:

有没有一种简单的方法可以将一个值分散到多行?

例如,我的表包含

类型 已开票 已付 电流 收费 100 0 100 收费 100 0 100 收费 100 0 100 付款 0 250 0 付款 0 25 0

数据是以这种方式导入的,但我需要根据同时导入的支付交易来填充CurrentPaid 列。

是否有一种简单的方法可以编写查询来确定每条记录的Current 列的余额?

例如,250 将 100 应用于前两条记录,50 应用于接下来的两条记录,而 25 将应用于最后一条,因此在我的表中更新 Current 余额后的最终结果应该是:

类型 已开票 已付 电流 收费 100 100 0 收费 100 100 0 收费 100 75 25 付款 0 250 0 付款 0 25 0

理想情况下,我希望使用单个查询来完成此操作,而不是使用游标单独处理每个项目。我一直在尝试通过使用 Row_Number() 函数并加入两个子查询来做到这一点,但我知道我在这里遗漏了一些东西

这是我的第一次尝试,结果得到了当前余额的总和

;with cte(invoiced, paid, current)
as (
    select invoiced, paid, current
        , row_number() over (order by datecreated)
    from mytable
)

select t1.invoiced, t1.paid, sum(t2.invoiced - t2.paid) as [current]
from cte as t1
join cte as t2 on t1.number = t2.number and t2.rownum <= t1.rownum
group by t1.uid, t1.number, t1.rownum
order by t1.rownum

结果:

已开票的当前 100 0 100 100 0 200 100 0 300 0 250 50 0 25 25

我确信有办法做到这一点,但现在我的大脑似乎在罢工并且拒绝提出解决方案。

【问题讨论】:

  • 我可能是错的,但是 afaik,你在考虑列,SQL 适用于行。也许提供数据集底层的数据结构可能会有所帮助。
  • 嗨 - “子选择”和“加入”:很好。你肯定在正确的轨道上。 “Row_number()”:不好。问:有没有定义ordercolumn?例如,“invoice#”或“po_date”?
  • @greg 我的基础数据集几乎是那里的第一个示例数据集,还有一些额外的列,例如 DateCreatedAccountIdTransactionType 和一个 Id 字段。我的目标是根据任何Paid 交易,用该交易的当前余额填充Current 字段。
  • 给定的集合是否按 DateCreated DESC 排序?
  • @Rachel - 请同时查看此链接:它可能会有所帮助:geekswithblogs.net/Rhames/archive/2008/10/28/…

标签: sql tsql sql-server-2005


【解决方案1】:

我想我找到了解决办法

首先,我不需要将付费交易链接到发票交易,所以我只需要所有付款的总和

select accountid, sum(paid)
from mytable
where type = 'Payment'
group by accountid

然后我需要将此值应用于每条记录,直到运行总额大于支付总额。

为此,我修改了我的运行总计查询,使其仅对费用求和,而不是对费用和付款进行求和

;with cte(id, accountid, invoiced, paid, current)
as (
    select id, accountid, invoiced, paid, current
        , row_number() over (order by datecreated)
    from mytable
    where type = 'Charge'
)

select t1.id, t1.accountid, t1.invoiced, sum(t2.invoiced) as [runningTotalOfCharges]
from cte as t1
join cte as t2 on t1.number = t2.number and t2.rownum <= t1.rownum
group by t1.id, t1.accountid, t1.invoiced

并将其加入到支付查询中,所以现在我有一堆行,其中包含总支付金额、直到该记录的总费用以及当前记录的费用金额。

从那里,我只需要一个CASE 语句来确定费用是全额支付、部分支付还是根本没有支付,并使用一些数学计算出PaidCurrent 记录

select charged.Id, charged.AccountId, charged.Invoiced
    -- Use Case statements to determine if this payment is fully paid, partially paid, 
    -- or not paid at all, then determine Current and Paid based on that
    , case when totalpaid - runningtotal >= 0 then invoiced 
        when invoiced > abs(totalpaid - runningtotal) then invoiced + totalpaid - runningtotal
        else 0 end as [Paid]
    , case when totalpaid - runningtotal >= 0 then 0 
        when invoiced > abs(totalpaid - runningtotal) then abs(totalpaid - runningtotal)
        else invoiced end as [Current]
from 
(
    -- Running total query from above
    select t1.id, t1.accountid, t1.invoiced, sum(t2.invoiced) as [runningtotal]
    from cte as t1
    join cte as t2 on t1.number = t2.number and t2.rownum <= t1.rownum
    group by t1.id, t1.accountid, t1.invoiced
) as charged

inner join (
    -- Total Paid query from above
    select accountid, sum(paid) as totalpaid
    from mytable
    where type = 'Payment'
    group by accountid
) as paid on charged.number = paid.number

最终结果就是我想要的。只需通过Id 列将其加入实际数据表,并更新PaidCurrent 值:)

Id AccountId Invoiced 已付 当前 1 1 100 100 0 2 1 100 100 0 3 1 100 75 25

【讨论】:

    【解决方案2】:

    您可以计算已付费列和当前列的运行总计和总和,然后执行一些数学运算以获取当前列值。我试了一下,但在计算最终值时陷入了困境。这不是一个简单的方法,但它是一个方法。

    DECLARE @myTable TABLE
    (
        [TranId] INT,
        [Type] VARCHAR(10),
        [Invoiced] INT,
        [Paid] INT,
        [Current] INT,
        [SumPaid] INT,
        [SumCurrent] INT,
        [RunningPaid] INT,
        [RunningCurrent] INT
    )
    
    INSERT INTO @myTable SELECT 1, 'Charge', 100, 0, 100, null, null, null, null
    INSERT INTO @myTable SELECT 2, 'Charge', 100, 0, 100, null, null, null, null
    INSERT INTO @myTable SELECT 3, 'Charge', 100, 0, 100, null, null, null, null
    INSERT INTO @myTable SELECT 4, 'Paid', 0, 250, 0, null, null, null, null
    INSERT INTO @myTable SELECT 5, 'Paid', 0, 25, 0, null, null, null, null
    
    
    UPDATE @myTable SET SumPaid = (SELECT SUM([Paid]) FROM @myTable)
    UPDATE @myTable SET SumCurrent = (SELECT SUM([Current]) FROM @myTable)
    UPDATE @myTable
    SET 
        [RunningPaid] = full_running_total_set.[RunningPaid], 
        [RunningCurrent] = full_running_total_set.[RunningCurrent]
    FROM @myTable
    INNER JOIN
    (
        SELECT
            TranId1,
            SUM([Paid]) AS [RunningPaid],
            SUM([Current]) AS [RunningCurrent]
        FROM
        (
            SELECT
                set2.[Paid],
                set2.[Current],
                set1.[TranId] AS [TranId1],
                set2.[TranId] AS [TranId2]
            FROM @myTable set1
            INNER JOIN @myTable set2
                ON set1.[TranId] >= set2.[TranId]
        )running_total_set
        GROUP BY [TranId1]
    )full_running_total_set
    ON [TranId] = full_running_total_set.[TranId1]
    
    SELECT * FROM @myTable
    

    【讨论】:

    • 谢谢,实际上我最终做了与此非常相似的事情并将其发布为我自己的答案:) 我最终使用 CASE 声明来确定费用是否已全额支付,部分支付,或者根本不支付,如果部分支付了费用,只需要一点数学。
    猜你喜欢
    • 1970-01-01
    • 2020-08-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-16
    • 2021-12-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多