Как я могу распределить значение по нескольким строкам без курсора?

Есть ли простой способ распределить значение по нескольким строкам?

Например, моя таблица содержит

Type      Invoiced    Paid    Current
Charge    100         0       100
Charge    100         0       100
Charge    100         0       100
Payment   0         250       0
Payment   0          25       0

Данные импортируются таким образом, но мне нужно заполнить столбцы Current и Paid тем, чем они должны быть для этой транзакции, на основе платежных транзакций, которые также были импортированы.

Есть ли простой способ написать запрос для определения баланса столбца Current для каждой записи?

Например, 250 применит 100 к первым двум записям и 50 к следующим двум, а 25 будет применено к последней, поэтому конечный результат после обновления баланса Current в моей таблице должен быть:

Type      Invoiced    Paid    Current
Charge    100         100     0
Charge    100         100     0
Charge    100          75     25
Payment   0           250     0
Payment   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

Результат:

Invoiced    Paid    Current
100         0       100
100         0       200
100         0       300
0         250       50
0          25       25

Я уверен, что есть способ сделать это, но прямо сейчас мой мозг, кажется, бастует и отказывается найти решение.


person Rachel    schedule 06.09.2012    source источник
comment
Я могу ошибаться, но на самом деле вы думаете о столбцах, а SQL применяется к строкам. Возможно, может помочь предоставление структуры данных, лежащей в основе вашего набора данных.   -  person greg    schedule 06.09.2012
comment
Привет - выберите и присоединитесь: Хорошо. Вы определенно на правильном пути. Row_number(): Плохо. В: Существует ли какой-либо столбец, определяющий порядок? Например, номер счета-фактуры или po_date?   -  person paulsm4    schedule 06.09.2012
comment
@greg Мой базовый набор данных — это в значительной степени первый пример набора данных, а также несколько дополнительных столбцов, таких как DateCreated, AccountId, TransactionType и поле идентификатора. Моя цель — заполнить поле Current любым текущим балансом этой транзакции на основе любых Paid транзакций.   -  person Rachel    schedule 06.09.2012
comment
Является ли данный набор упорядоченным по DateCreated DESC?   -  person greg    schedule 06.09.2012
comment
@greg Да, но он также упорядочен еще по нескольким вещам из-за того, что время не включено в даты. Мой фактический бит Row_Number выглядит примерно так: row_number() over (partition by AccountId order by DateCreated, Invoiced desc). Я просто упростил это ради этого вопроса, так как не думал, что это имеет значение.   -  person Rachel    schedule 06.09.2012
comment
@Rachel - пожалуйста, посмотрите также эту ссылку: это может помочь: geekswithblogs.net/Rhames/archive/2008/10/28/   -  person paulsm4    schedule 06.09.2012
comment
Можете ли вы исправить свой запрос с первой попытки? Я думаю, что это может быть несоответствие из разных итераций. (например, в CTE нет счета-фактуры)   -  person Zeph    schedule 06.09.2012
comment
@Zeph Конечно, извините за то, что имена столбцов не совсем совпадают :) На самом деле у меня может быть решение, но я все еще проверяю числа прямо сейчас, прежде чем я смогу сказать, будет это работать или нет.   -  person Rachel    schedule 06.09.2012


Ответы (2)


кажется я нашел решение

Во-первых, мне не нужно связывать платные транзакции с транзакциями по счетам, поэтому мне нужна только сумма всех платежей.

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, чтобы определить, была ли оплата полностью оплачена, частично оплачена или не оплачена вообще, и использовать немного математики, чтобы выяснить записи Paid и Current.

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 и обновить значения Paid и Current :)

Id    AccountId    Invoiced    Paid    Current
1     1            100         100     0
2     1            100         100     0
3     1            100         75      25
person Rachel    schedule 06.09.2012

Вы можете рассчитать промежуточные итоги и суммы для платных и текущих столбцов, а затем выполнить некоторые математические операции, чтобы получить текущие значения столбцов. Я попробовал, но увяз в математике, чтобы получить окончательные значения. Это не простой путь, но это путь.

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
person Narthring    schedule 06.09.2012
comment
Спасибо, на самом деле я сделал что-то очень похожее на это и опубликовал это как свой собственный ответ :) В итоге я использовал оператор CASE, чтобы определить, была ли оплата полностью оплачена, частично оплачена или не оплачена вообще и только нужно немного математики, если заряд был частично оплачен. - person Rachel; 06.09.2012