DECLARE @TotalMaxQty int
SET @TotalMaxQty = 1000

SELECT
  PKId
  ,Qty
FROM dbo.Sales

PKId          Qty
____          _____
1             100
2             200 
3             750
4             200
...

Мне нужно получить 1, 2, 3 записи из-за SUM(Qty) <= @TotalMaxQty (запись 3 должна быть включена частично с Qty = 700).

Спасибо.

1
garik 29 Авг 2011 в 17:03

3 ответа

Лучший ответ

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

declare @Sales table (PKId int, Qty int)

insert into @Sales values
(1,             100),
(2,             200), 
(3,             750),
(4,             200)

declare @TotalMaxQty int = 1000

;with OrderedSales as
(
  select PKId,
         Qty,
         row_number() over(order by PKId) as rn
  from @Sales
  --where "some where clause against Sales"
),
RunningSum as
(
  select OS.PKId,
         case when OS.Qty < @TotalMaxQty then OS.Qty
              else @TotalMaxQty
         end as Qty,
         @TotalMaxQty - OS.Qty as Rest,
         OS.rn
  from OrderedSales OS
  where rn = 1
  union all
  select OS.PKId,
         case when OS.Qty < RS.Rest then OS.Qty
              else RS.Rest
         end as Qty,
         RS.Rest - OS.Qty,
         OS.rn
  from OrderedSales as OS
    inner join RunningSum as RS
      on OS.rn = RS.rn + 1
  where RS.Rest > 0
)
select PKId,
       Qty
from RunningSum
option (maxrecursion 0)

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

declare @Sales table (PKId int, Qty int)

insert into @Sales values
(1,             100),
(2,             200), 
(3,             750),
(4,             200)

declare @TotalMaxQty int = 1000

declare @OrderedSales table
( 
  rn int primary key,
  PKId int,
  Qty int
)

insert into @OrderedSales
select row_number() over(order by PKId),
       PKId,
       Qty
from @Sales
--where "some where clause against Sales"

;with RunningSum as
(
  select OS.PKId,
         case when OS.Qty < @TotalMaxQty then OS.Qty
              else @TotalMaxQty
         end as Qty,
         @TotalMaxQty - OS.Qty as Rest,
         OS.rn
  from @OrderedSales OS
  where rn = 1
  union all
  select OS.PKId,
         case when OS.Qty < RS.Rest then OS.Qty
              else RS.Rest
         end as Qty,
         RS.Rest - OS.Qty,
         OS.rn
  from @OrderedSales as OS
    inner join RunningSum as RS
      on OS.rn = RS.rn + 1
  where RS.Rest > 0

)
select PKId,
       Qty
from RunningSum
option (maxrecursion 0)
1
Mikael Eriksson 29 Авг 2011 в 14:04

Что-то в этом роде должно помочь. (NB: причина наличия TOP и подзапроса состоит в том, чтобы остановить вычисления треугольного соединения, как только цель будет достигнута)

SELECT  *
FROM    Sales
WHERE   PKId < = ( SELECT TOP 1
                            S1.PKId
                   FROM     Sales S1
                            LEFT JOIN Sales S2 ON S1.PKId >= S2.PKId
                   GROUP BY S1.PKId
                   HAVING   SUM(S2.Qty) >= @TotalMaxQty
                   ORDER BY PKId
                 )
1
Martin Smith 29 Авг 2011 в 13:38

запись 3 должна быть включена частично с Qty = 700

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

Но если вы настаиваете:

SELECT s.PKId, CASE WHEN PriorTotal + Qty > @TotalMaxQty THEN @TotalMaxQty - PriorTotal ELSE Qty END As Qty
FROM SALES s
INNER JOIN (
   SELECT s1.PKId, Sum(s2.Qty) As PriorTotal 
   FROM SALES s1
   LEFT JOIN SALES s2 ON s2.PKId < s1.PKId
   GROUP BY s1.PKId
) q ON q.PKId = s.PKId
WHERE q.PriorTotal < @TotalMaxQty
ORDER BY s.Qty
1
Joel Coehoorn 29 Авг 2011 в 14:18