2016-06-30 4 views
1

Я пытаюсь найти среднее количество моих запасов в диапазоне дат от параметра @StartDate путем усреднения конца qty с каждого дня. У меня есть три таблицы: таблица деталей, таблица транзакций деталей и таблица хранилища, издеваются ниже.Вычислить среднее количество под рукой инвентаря

PartNum  | PartNum  TranDate  TranQty  | PartNum OnHandQty 
---------- | ------------------------------------ | -------------------- 
P1   | P1   6/28/2016 5   | P1  30 
P2   | P1   6/26/2016 3   | P2  2 
      | P1   6/26/2016 -1   | 
      | P1   6/15/2016 2   | 
      | P2   6/15/2016 1   | 

Если сегодня 6/30/2016 и @StartDate = 6/1/2016, я ожидаю результат, как:

PartNum AverageOnHand 
------------------------ 
P1   22.9 
P2   1.5 

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

select 
    [Part].[PartNum] as [Part_PartNum], 
    (max(PartWhse.OnHandQty)*datediff(day,max(PartTran.TranDate),Constants.Today)) as [Calculated_WeightedSum], 
    (WeightedSum/DATEDIFF(day, @StartDate, Constants.Today)) as [Calculated_AverageOnHand] 
from Erp.Part as Part 
right outer join Erp.PartTran as PartTran on 
    Part.PartNum = PartTran.PartNum 

inner join Erp.PartWhse as PartWhse on 
    Part.PartNum = PartWhse.PartNum 

group by [Part].[PartNum] 
+3

, который rdbms (sql-server, oracle, mysql, postgresql)? – Matt

+4

также используя ваши примеры таблиц, вы можете показать мне, как вы пришли к 22.1 (мне любопытно, что ваша формула не синтаксис). Таким образом, таблица склада всегда является текущим количеством? и таблица транзакций, когда положительный он добавляет к этому количеству или вычитает из него? Другими словами, это положительное целое число продажи или возврата? – Matt

+0

@Matt sql-server. У вас все правильно, таблица склада возвращает текущее количество. Таблица транзакций является положительной, когда она добавляется. Таким образом, в конце дня 27/26/2016 было 25 P1 под рукой. Мне 22.1, вычисляя количество, доступное в конце каждого дня, беря сумму и деля по счету. Это приводит к среднему по времени (в отличие от месяца, часа или непрерывно). – soytsauce

ответ

0

Я смог решить это с помощью суммы. Во-первых, я умножил конечную величину на количество дней в диапазоне. Затем я умножил каждое изменение запасов на время от @StartDate до TransDate.

select 
    [Part].[PartNum] as [Part_PartNum], 
    (max(PartWhse.OnHandQty)*datediff(day,@StartDate,Constants.Today)- 
     sum(PartTran.TranQty*datediff(day,@StartDate,PartTran.TranDate))) as [Calculated_WeightedSum], 
    (WeightedSum/DATEDIFF(day, @StartDate, Constants.Today)) as [Calculated_AverageOnHand] 
from Erp.Part as Part 
right outer join Erp.PartTran as PartTran on 
    Part.PartNum = PartTran.PartNum 

inner join Erp.PartWhse as PartWhse on 
    Part.PartNum = PartWhse.PartNum 

group by [Part].[PartNum] 

Спасибо за помощь! Ты действительно помог мне продумать это.

+0

Откуда берется «WeightedSum»? Это должно быть то же самое, что и выражение перед ним? Я не мог получить ваш запрос, чтобы совпасть с моим. – shawnt00

+0

Если я правильно прочитал ваш запрос, вы бы сделали ((OnHand * TotalDaysDiff) + (TransactionQuantity * DaysDiffFromStart))/TotalDaysDiff? Это определенно не среднее количество DailyOnHand. для вашего примера возьмите свой P1, который будет ((30 * 29 дней) + ((2 * 14) + (- 1 * 25) + (3 * 25) + (5 * 27)))/29, который даст вам 37.34483 ............................................ – Matt

+0

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

2

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

http://rextester.com/JLD19862

with trn as (
    select PartNum, TranDate, TranQty from PartTran 
    union all 
    select PartNum, cast('20160601' as date), 0 from PartWhse 
    union all 
    select PartNum, cast('20160630' as date), 0 from PartWhse 
), qty as (
    select 
     t.PartNum, t.TranDate, 
     -- assumes that end date corresponds with OnHandQty 
     min(w.OnHandQty) + sum(t.TranQty) 
      - sum(sum(t.TranQty)) 
       over (partition by t.PartNum order by t.TranDate desc) as DailyOnHand, 
     coalesce(
      lead(t.TranDate) over (partition by t.PartNum order by t.TranDate), 
      dateadd(day, 1, t.TranDate) 
     ) as NextTranDate 
     -- if lead() isn't available... 
     -- coalesce(
     -- (
     --  select min(t2.TranDate) from trn as t2 
     --  where t2.PartNum = t.PartNum and t2.TranDate > t.TranDate 
     -- ), 
     -- dateadd(day, 1, t.TranDate) 
     --) as NextTranDate 
    from PartWhse as w inner join trn as t on t.PartNum = w.PartNum 
    where t.TranDate between '20160601' and '20160630' 
    group by t.PartNum, t.TranDate 
) 
select 
    PartNum, 
    sum(datediff(day, TranDate, NextTranDate) * DailyOnHand) * 1.00 
     /sum(datediff(day, TranDate, NextTranDate)) as DailyAvg 
from qty 
group by PartNum; 
2

Вот SQL-сервер 2012 + метод, который интересен.

;WITH cte AS (
    SELECT 
     p.PartNum 
     ,CAST(t.TranDate AS DATE) AS TranDate 
     ,i.OnHandQty 
     --,SUM(SUM(t.TranQty)) OVER (PARTITION BY p.PartNum ORDER BY CAST(t.TranDate AS DATE) DESC) AS InventoryChange 
     ,i.OnHandQty - SUM(SUM(t.TranQty)) OVER (PARTITION BY p.PartNum ORDER BY CAST(t.TranDate AS DATE) DESC) AS InventoryOnDate 
     ,DATEDIFF(day, 
      CAST(ISNULL(LAG(MAX(TranDate)) OVER (PARTITION BY p.PartNum ORDER BY CAST(t.TranDate AS DATE) ASC),@StartDate) AS DATE) 
      ,CAST(t.TranDate AS DATE) 
     ) AS DaysAtInventory 
    FROM 
     #Parts p 
     LEFT JOIN #Transact t 
     ON p.PartNum = t.PartNum 
     LEFT JOIN #Inventory i 
     ON p.PartNum = i.PartNum 
    GROUP BY 
     p.PartNum 
     ,CAST(t.TranDate AS DATE) 
     ,i.OnHandQty 
) 

SELECT 
    PartNum 
    ,(SUM(ISNULL(DaysAtInventory,0) * ISNULL(InventoryOnDate,0)) 
    + ((DATEDIFF(day,MAX(TranDate),CAST(GETDATE() AS DATE)) + 1) * ISNULL(MAX(OnHandQty),0))) 
    /((DATEDIFF(day,CAST(@StartDate AS DATE),CAST(GETDATE() AS DATE)) + 1) * 1.00) AS AvgDailyInventory 
FROM 
    cte 
GROUP BY 
    PartNum 

Это одна на самом деле дал мне 22.9, но 1.53333 333 получают введенные потому, что один день должен получить куда-то деть, так что я воткнул его в качестве текущей инвентаризации.

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

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

DECLARE @StartDate DATETIME = '6/1/2016' 

;WITH cteDates AS (
    SELECT @StartDate AS d 
    UNION ALL 
    SELECT 
     d + 1 AS d 
    FROM 
     cteDates c 
    WHERE c.d + 1 <= CAST(CAST(GETDATE() AS DATE) AS DATETIME) 
    --get dates to today beginning of day 
) 

, ctePartsDaysCross AS (
    SELECT 
     d.d 
     ,p.PartNum 
     ,ISNULL(i.OnHandQty,0) AS OnHandQty 
    FROM 
     cteDates d 
     CROSS JOIN #Parts p 
     LEFT JOIN #Inventory i 
     ON p.PartNum = i.PartNum 
) 

, cteTransactsQuantityByDate AS (
    SELECT 
     CAST(t.TranDate AS DATE) as d 
     ,t.PartNum 
     ,TranQty = SUM(t.TranQty) 
    FROM 
     #Transact t 
    GROUP BY 
     CAST(t.TranDate AS DATE) 
     ,t.PartNum 
) 

,cteDailyInventory AS (
    SELECT 
     c.d 
     ,c.PartNum 
     ,c.OnHandQty - SUM(ISNULL(t.TranQty,0)) OVER (PARTITION BY c.PartNum ORDER BY c.d DESC) AS DailyOnHand 
    FROM 
     ctePartsDaysCross c 
     LEFT JOIN cteTransactsQuantityByDate t 
     ON c.d = t.d 
     AND c.PartNum = t.PartNum 
) 

SELECT 
    PartNum 
    ,AVG(CAST(DailyOnHand AS DECIMAL(6,3))) 
FROM 
    cteDailyInventory 
GROUP BY 
    PartNum 

Вот тестовые данные:

IF OBJECT_ID('tempdb..#Parts') IS NOT NULL 
    BEGIN 
     DROP TABLE #Parts 
    END 

IF OBJECT_ID('tempdb..#Transact') IS NOT NULL 
    BEGIN 
     DROP TABLE #Transact 
    END 

IF OBJECT_ID('tempdb..#Inventory') IS NOT NULL 
    BEGIN 
     DROP TABLE #Inventory 
    END 

CREATE TABLE #Parts (
    PartNum CHAR(2) 
) 

CREATE TABLE #Transact (
    AutoId INT IDENTITY(1,1) NOT NULL 
    ,PartNum CHAR(2) 
    ,TranDate DATETIME 
    ,TranQty INT 
) 

CREATE TABLE #Inventory (
    PartNum CHAR(2) 
    ,OnHandQty INT 
) 

INSERT INTO #Parts (PartNum) VALUES ('P1'),('P2'),('P3') 

INSERT INTO #Transact (PartNum, TranDate, TranQty) 
VALUES ('P1','6/28/2016',5),('P1','6/26/2016',3),('P1','6/26/2016',-1) 
,('P1','6/15/2016',2) ,('P2','6/15/2016',1) 

INSERT INTO #Inventory (PartNum, OnHandQty) VALUES ('P1',30),('P2',2) 

Я имею в виду 1 рекурсивное ОТВ может быть проще, может опубликовать, что в качестве обновления.

Смежные вопросы