Запрос, который возвращает записи об общей сумме количества, меньшего или равного некоторому значению - PullRequest
1 голос
/ 29 августа 2011
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).

Спасибо.

Ответы [ 3 ]

1 голос
/ 29 августа 2011

Вы можете использовать рекурсивный 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 голос
/ 29 августа 2011

запись 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 голос
/ 29 августа 2011

Что-то в этом роде должно сработать. (Примечание: причина наличия 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
                 )
...