СОВЕТЫ по биллингу Query в SQL Server 2000 - PullRequest
0 голосов
/ 09 апреля 2009

Мне нужен совет по решению вопроса. Я могу справиться с этим во внешнем приложении, однако, из-за дизайна, я должен дополнить это во внутреннем интерфейсе. У меня есть следующее

<code>
CREATE TABLE [dbo].[openitems](
    [id] [varchar](8) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
    [type] [char](5) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
    [date] [smalldatetime] NULL,
    [amount] [decimal](9, 2) NULL,
    [daysOpen] [smallint] NULL,
    [balance] [decimal](9, 2) NULL
) ON [PRIMARY]




insert into openitems values('A12399','INV','2008-12-05',491.96,123)

insert into openitems values('A12399','INV','2008-12-12',4911.37,116)

insert into openitems values('A12399','INV','2008-12-05',3457.69,109)

В таблице выше есть все открытые счета для клиента. Мне нужно применить платеж к этим счетам, начиная с самого старого счета (столбец daysOpen в таблице). Поэтому, если у меня есть платеж в размере 550,00 долларов США, я сначала применю его к счету со сроком действия 123 дня, то есть 491,96-500 долларов (то есть 8,04 доллара для применения к следующему счету ... и т. Д.), Затем обновлю эту запись (баланс столбец в таблице) до 0,00 и перейти к следующему и применить оставшиеся. Это будет $ 4911,37 - $ 8,04, что оставит $ 4903,33. Поскольку баланс не применяется, цикл завершается.

Столбец баланса теперь должен читаться как

0
4903.33
3457.69

Примечание: мне нужно сделать это для всех клиентов в таблице (около 10 000). Клиент имеет в среднем около 20 открытых счетов.

Спасибо

Ответы [ 4 ]

2 голосов
/ 10 апреля 2009

Рассмотрим следующее:

платеж либо применяется полностью к балансу, либо частично применяется к балансу, либо переплачивает баланс.

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

create view cumulative_balance as
select a.*, 
  (select sum( balance ) 
  from openitems b 
  where b.id = a.id and b.type = a.type and a.daysOpen >= a.daysOpen)
  as cumulative_balance
from openitems a;

Теперь мы можем найти первый совокупный баланс, меньший или равный сумме платежа, для любого идентификатора и типа и сохранить его, daysOpen и совокупный баланс в переменных сервера.

Затем мы обновляем все openItems с этим идентификатором и типом, где daysOpen <= полученное нами значение, устанавливая все эти балансы на ноль. </p>

Затем мы находим первый ненулевой баланс этого идентификатора и типа и устанавливаем его баланс как его баланс - (оплата - накопленный баланс, который мы сохранили). при переплате этот баланс будет правильно отрицательным.

При правильном запросе вы сможете выполнить поиск и первое обновление в одном выражении.

Есть две проблемы. Одна из них заключается в том, что вы не можете определить два или более копий с одинаковыми идентификатором и типом и daysOpen, которые должны быть оплачены в первую очередь. Добавление уникального идентификатора к вашему столу послужило бы для этих случаев тай-брейком.

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

Еще лучшим рефакторингом было бы иметь две таблицы, одну для счетов-фактур и одну для платежей: тогда представление может просто выполнять всю работу, сравнивая совокупные сальдо с совокупными платежами, создавая список неоплаченных сальдо или переплат.

На самом деле, я разработал именно такую ​​систему для крупной ипотечной компании с инициалами FM. Это было немного сложнее, чем у вас, в том смысле, что сальдо были рассчитаны на основе ряда формул сумм и процентов, и нескольким плательщикам (фактически, страховщикам, это было для ипотечных кредитов, которые пошли на дефолт), приходилось выставлять счета в установленный порядок в соответствии с другими правилами, за каждую просроченную ипотеку.

Все это было сделано в представлениях с помощью короткой (100 строк или около того) хранимой процедуры, которая, по сути, выполняла то, что я изложил выше: использовала представление, которое упорядочивало выставление счетов по этим правилам, применяемые платежи (в вид), рассчитывая, какие дополнительные платежи необходимо выставить в счет, на какую дату, к какому страховщику. Затем хранимая процедура только что сгенерировала счета на текущую дату (какую текущую дату можно установить, опять же с использованием представления, на любую дату для целей тестирования).

(Ирония заключается в том, что я взял на себя обещание написать на С ++; единственный написанный мною С ++ использовал API Oracle и Sybase C для передачи данных из системы Oracle в систему Sybase.)

1 голос
/ 10 апреля 2009

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

DECLARE
    @paid_amount DECIMAL(9, 2)

SET @paid_amount = 500

UPDATE
    OI
SET
    balance =
            CASE
                WHEN @paid_amount - SQ.running_total > balance THEN 0
                ELSE balance - (@paid_amount - SQ.running_total)
            END
FROM
    dbo.OpenItems OI
INNER JOIN (
    SELECT
        I1.id,
        I1.invoice_id,
        I1.date,
        ISNULL(SUM(I2.amount), 0) AS running_total
    FROM
        OpenItems I1
    LEFT OUTER JOIN OpenItems I2 ON
        I2.id = I1.id AND
        I2.type = 'INV' AND
        I2.daysopen > I1.daysopen
    GROUP BY
        I1.id,
        I1.invoice_id,
        I1.date
) AS SQ ON
    SQ.id = OI.id AND
    SQ.invoice_id = OI.invoice_id
WHERE
    @paid_amount > SQ.running_total
0 голосов
/ 10 апреля 2009

Если это не разовое усилие ...

Похоже, что это логика бизнеса, и она относится к вашему бизнес-уровню вашего приложения.

0 голосов
/ 09 апреля 2009

Вы должны использовать пару курсоров и две вложенные циклы. (это может быть немного медленно)

Один, чтобы прочитать все платежи - я предполагаю, Клиент, сумма

Затем для каждого клиента создайте еще один курсор для открытых позиций.

Первый цикл будет считывать платежи, пока не будет выполнено

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

Просмотрите все открытые позиции и примените платежи, как вы описали

Тогда получите следующий платеж.

Повторяйте до тех пор, пока больше нет платежей.

...