Сложный запрос SQL для столбца промежуточных итогов - PullRequest
3 голосов
/ 02 мая 2009

Я пытаюсь выполнить довольно сложный запрос в SQL Server 2008. Я хотел бы получить от экспертов по SQL здесь.

Представьте, что у меня есть таблица платежей с этими полями:

PaymentID int, CustomerID int, PaymentDate datetime, Десятичная сумма

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

То, что мы пытаемся выяснить, - это SQL для расчета максимальной суммы общей суммы, уплаченной за клиента.

Итак, если Фред сделал 3 платежа: первый за 5 долларов, второй за 5 долларов, третий за - 3 доллара. В отчете будет показано, что максимальная общая выплаченная сумма Фреда составляла 10 долларов (при его втором платеже), а его окончательная заплаченная сумма составляла 7 долларов.

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

Есть ли хороший способ структурировать этот запрос без сохранения промежуточных итогов в БД? Мы хотели бы избежать сохранения предварительно рассчитанных значений, если это вообще возможно.

Ответы [ 5 ]

7 голосов
/ 02 мая 2009

Ваш вопрос, кажется, таков:

SELECT CustomerID, SUM(Ammount) FROM table WHERE Amount > 0 GROUP BY CustomerID
SELECT CustomerID, SUM(Ammount) FROM table GROUP BY CustomerID

Однако, я думаю, вы имеете в виду, что вам нужна таблица, которая выглядит следующим образом

Customer  Payment  HighPoint  RunningTotal
123       5        5          5
123       5        10         10
123       -3       10         7

В этом случае я бы создал вид с двумя вариантами выбора, указанными выше, чтобы он был похож на

SELECT CusotmerID, 
  PaymentDate, 
  Ammount, 
  (SELECT SUM(Ammount) 
    FROM table as ALIAS 
    WHERE ALIAS.Amount > 0 
      AND ALIAS.PaymentDate <= PaymentDate 
      AND ALIAS.CustomerID = CustomerID), 
  (SELECT SUM(Ammount) 
    FROM table as ALIAS 
    WHERE ALIAS.CustomerID = CustomerID 
    AND ALIAS.PaymentDate <= PaymentDate)
FROM table

Кроме того, вы можете рассмотреть неуникальный индекс в столбце Сумма таблицы, чтобы ускорить просмотр.

4 голосов
/ 02 мая 2009

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

create table #payments (
    paymentid int identity,
    customerid int,
    paymentdate datetime,
    amount decimal
)

insert into #payments (customerid,paymentdate,amount) values (1,'2009-01-01',1.00)
insert into #payments (customerid,paymentdate,amount) values (1,'2009-01-02',2.00)
insert into #payments (customerid,paymentdate,amount) values (1,'2009-01-03',-1.00)
insert into #payments (customerid,paymentdate,amount) values (1,'2009-01-04',2.00)
insert into #payments (customerid,paymentdate,amount) values (1,'2009-01-05',-3.00)
insert into #payments (customerid,paymentdate,amount) values (2,'2009-01-01',10.00)
insert into #payments (customerid,paymentdate,amount) values (2,'2009-01-02',-5.00)
insert into #payments (customerid,paymentdate,amount) values (2,'2009-01-03',7.00)

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

select cur.customerid, cur.paymentdate, sum(prev.amount)
from #payments cur
inner join #payments prev
    on cur.customerid = prev.customerid
    and cur.paymentdate >= prev.paymentdate
group by cur.customerid, cur.paymentdate

Это генерирует данные:

Customer  Paymentdate        Balance after payment
1         2009.01.01         1
1         2009.01.02         3
1         2009.01.03         2
1         2009.01.04         4
1         2009.01.05         1
2         2009.01.01         10
2         2009.01.02         5
2         2009.01.03         12

Чтобы посмотреть на максимум, вы можете создать группу по текущему итоговому запросу:

select customerid, max(balance)
from (
    select cur.customerid, cur.paymentdate, balance = sum(prev.amount)
    from #payments cur
    inner join #payments prev
        on cur.customerid = prev.customerid
        and cur.paymentdate >= prev.paymentdate
    group by cur.customerid, cur.paymentdate
) runningtotal
group by customerid

Что дает:

Customer   Max balance
1          4
2          12

Надеюсь, это полезно.

4 голосов
/ 02 мая 2009

Операция линейная по количеству платежей для каждого клиента. Таким образом, вам придется пересматривать каждый платеж, сохраняя промежуточную сумму и максимальный балл, и в конце всех платежей у вас будет свой ответ. Делаете ли вы это в хранимой процедуре CLR (сразу пришло мне в голову) или используете курсор или временную таблицу или что-то еще, это, вероятно, не будет быстрым.

Если вам нужно многократно запускать этот отчет, вам следует серьезно подумать о том, чтобы оставить поле для отметки «верхний уровень» и обновлять его (или нет) при поступлении платежа. Таким образом, ваш отчет будет тривиальным, но для чего нужны витрины данных.

1 голос
/ 02 мая 2009

как ответ Андомара. Вы можете сделать промежуточный итог для каждого платежа. Тогда найдите максимальный пиковый платеж ...

with
rt as (
  select
    Payments.*,
    isnull(sum(p.Amount), 0) + Payments.Amount as running
  from
    Payments
    left outer join Payments p on Payments.CustomerID = p.CustomerID
      and p.PaymentDate <= Payments.PaymentDate
      and p.PaymentID < Payments.PaymentID
),
highest as
(
  select
    CustomerID, PaymentID, running as peak_paid
  from
    rt
  where
    PaymentID = (select top 1 rt2.PaymentID 
        from rt rt2 
        where rt2.CustomerID = rt.CustomerID
        order by rt2.running desc, rt2.PaymentDate, rt2.PaymentID)
)

select
  *,
  (select sum(amount) from Payments where Payments.CustomerID = highest.CustomerID) as total_paid  
from
  highest;

однако, поскольку у вас есть около 1 миллиона платежей, это может быть довольно медленным. Как говорят другие, вы бы хотели сохранить CustomerID, PaymentID и peak_paid в отдельной таблице. Эта таблица может быть обновлена ​​на каждой вкладке платежа или в виде sqljob.

Обновлен запрос на использование соединения вместо подзапросов. Поскольку у PaymentDate нет времени, я отфильтровываю несколько платежей в один и тот же день по идентификатору платежа.

1 голос
/ 02 мая 2009
list = list of amounts ordered by date
foreach in list as amount
  running += amount
  if running >= high
    high = running

Чтобы сохранить его быстрым, вам потребуется промежуточная сумма, увеличенная на сумму по триггеру, и высокое значение для каждого клиента (также может быть обновлено триггером, чтобы сделать повторный запрос еще проще).

Я не думаю, что вы можете делать такие вещи без кода (хранимые процедуры - это код)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...