Текущая сумма TSQL

#sql #sql-server #tsql

#sql #sql-сервер #tsql

Вопрос:

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

Товары:

 ItemNumber     QuantityOnHand
----------     --------------
B34233         25.0
B34234         10.0
  

ItemTransactions:

 TransDate      ItemNumber     Quantity
-----------    ----------     --------
1/1/2011       B34233         10.0
1/2/2011       B34234         -15.0
1/2/2011       B34233         -5.0
1/4/2011       B34234         -10.0
  

Вот результат, который я хочу получить от запроса:

 Date          ItemNumber      Quantity
----          ----------      --------
12/31/2010    B34233          20.0
12/31/2010    B34234          35.0
1/1/2011      B34233          30.0
1/1/2011      B34234          35.0
1/2/2011      B34233          25.0
1/2/2011      B34234          20.0
1/3/2011      B34233          25.0
1/3/2011      B34234          20.0
1/4/2011      B34233          25.0
1/4/2011      B34234          10.0
  

Как бы я написал этот запрос? Я довольно хорошо разбираюсь в TSQL, но не могу придумать способ написания этого запроса.

Комментарии:

1. Как вы получаете выходные данные из предоставленных входных данных? Где находится 12/31/2010 во входных данных?

2. @Quassnoi: Я предполагаю, что в Items таблице есть «текущий баланс» на текущий момент времени. Затем он хочет получить «историю» с произвольного дня (start day) в прошлом, скажем, с 01/01/2011 по настоящее время (или другого произвольного дня). 12/31/2010 является предыдущим днем «начального дня».

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

Ответ №1:

В SQL Server 2005 и выше:

 WITH    dates (itemNumber, quantity, currentDate, minDate) AS
        (
        SELECT  itemNumber, CAST(quantityOnHand AS DECIMAL(20, 2)), it.*
        FROM    items i
        CROSS APPLY
                (
                SELECT  MAX(transDate) AS currentDate,
                        MIN(transDate) AS minDate
                FROM    itemTransactions it
                ) it
        UNION ALL
        SELECT  d.itemNumber,
                CAST
                (
                d.quantity -
                COALESCE(
                (
                SELECT  it.quantity
                FROM    itemTransactions it
                WHERE   it.transDate = d.currentDate
                        AND it.itemNumber = d.itemNumber
                ), 0) AS DECIMAL(20, 2)),
                DATEADD(d, -1, currentDate),
                minDate
        FROM    dates d
        WHERE   currentDate >= minDate
        )
SELECT  currentDate, itemNumber, quantity
FROM    dates
ORDER BY
        currentDate, itemNumber
  

Это предполагает, что у вас есть одна транзакция на элемент в день (это ограничение рекурсивного CTE ввода SQL Server ).

Если вы этого не сделаете, вам следует добавить другой, CTE который агрегировал бы транзакции по дням и элементам и использовал бы его вместо items :

 WITH    itGrouped (transDate, itemNumber, quantity) AS
        (
        SELECT  transDate, itemNumber, SUM(quantity)
        FROM    itemTransactions
        GROUP BY
                transDate, itemNumber
        ),
        dates (itemNumber, quantity, currentDate, minDate) AS
        (
        SELECT  itemNumber, CAST(quantityOnHand AS DECIMAL(20, 2)), it.*
        FROM    items i
        CROSS APPLY
                (
                SELECT  MAX(transDate) AS currentDate,
                        MIN(transDate) AS minDate
                FROM    itGrouped it
                ) it
        UNION ALL
        SELECT  d.itemNumber,
                CAST
                (
                d.quantity -
                COALESCE(
                (
                SELECT  it.quantity
                FROM    itGrouped it
                WHERE   it.transDate = d.currentDate
                        AND it.itemNumber = d.itemNumber
                ), 0) AS DECIMAL(20, 2)),
                DATEADD(d, -1, currentDate),
                minDate
        FROM    dates d
        WHERE   currentDate >= minDate
        )
SELECT  currentDate, itemNumber, quantity
FROM    dates
ORDER BY
        currentDate, itemNumber
  

Комментарии:

1. У меня могло бы быть несколько записей в день для одного элемента. Должен ли я агрегировать транзакции по дням в инструкции UNION ALL -> COALESCE -> SELECT?

2. При запуске я получаю сообщение об ошибке: неправильный синтаксис рядом с ключевым словом ‘with’. Если этот оператор является обычным табличным выражением или предложением xmlnamespaces, предыдущий оператор должен заканчиваться точкой с запятой.

3. @Wili: добавь к нему точку с запятой: ; WITH … .

4. точка с запятой должна стоять перед WITH . Запрос отлично работает в моих тестах. Я собираюсь попробовать это на своих текущих данных. У меня такое чувство, что это займет много времени, поскольку у меня около 10 000 частей и 2 миллиона транзакций.

Ответ №2:

Простая версия, использующая дополнительный выбор для получения количества в наличии в день.

Хорошее / быстрое решение, если у вас нет транзакций 100k и / или приличного sql-блока.

Извините за беспорядочный SQL (кодирование в обеденное время: P)

 CREATE TABLE #transactions (ID INT, DTE DATETIME, PROD VARCHAR(25), QTY INT )
CREATE TABLE #products (ID VARCHAR(25))
CREATE TABLE #dates (DTE DATETIME)

-- create some dates - you would do this dynamically
INSERT INTO #dates values (convert(datetime, '01/01/2011', 103))
INSERT INTO #dates values (convert(datetime, '02/01/2011', 103))
INSERT INTO #dates values (convert(datetime, '03/01/2011', 103))

-- create some products - you would get these from where-ever they live
INSERT INTO #products values ('A')
INSERT INTO #products values ('B')

-- create some transactions - you would get these from where-ever they live
INSERT INTO #transactions values (1, convert(datetime, '01/01/2011', 103), 'A', 25)
INSERT INTO #transactions values (2, convert(datetime, '01/01/2011', 103), 'A', -5)
INSERT INTO #transactions values (3, convert(datetime, '02/01/2011', 103), 'A', 60)
INSERT INTO #transactions values (4, convert(datetime, '02/01/2011', 103), 'A', -15)
INSERT INTO #transactions values (5, convert(datetime, '03/01/2011', 103), 'A', 100)
INSERT INTO #transactions values (6, convert(datetime, '03/01/2011', 103), 'A', -20)

INSERT INTO #transactions values (7, convert(datetime, '01/01/2011', 103), 'B', 10)
INSERT INTO #transactions values (8, convert(datetime, '01/01/2011', 103), 'B', 5)
INSERT INTO #transactions values (9, convert(datetime, '02/01/2011', 103), 'B', -30)
INSERT INTO #transactions values (1, convert(datetime, '02/01/2011', 103), 'B', 50)
INSERT INTO #transactions values (11, convert(datetime, '03/01/2011', 103), 'B', 10)
INSERT INTO #transactions values (12, convert(datetime, '03/01/2011', 103), 'B', 200)

-- Join dates and transactions - Do a sub select from 'begining of time' to get qty on hand per day
SELECT CONVERT(VARCHAR(25), a.DTE, 103),  b.id, (SELECT sum(qty) from #transactions c where b.id = c.prod and c.DTE <= a.DTE)
FROM #dates a, #products b

-- One benefit to this approach means you can genereate qty_on_hand per days were no transactions have occured (if you needed this)

DROP TABLE #transactions
DROP TABLE #products
DROP TABLE #dates
  

Ответ №3:

Смотрите, запрос прост, но каковы ваши требования к дате. Я даю простой запрос, который даст вам представление, и я использую временную таблицу:

 Create Table #Transection
(
    ID int Identity(1,1) not null,Item_Number varchar(20),Transection_Date datetime,Quantity_Available decimal(10,2)
)

insert into #Transection (Item_Number)  (Select ItemNumber from Items)

Declare @Product varchar(20),@Next_Product Cursor, @Item varchar(20),@Quantity decimal(10,2)
set  @Next_Product= CURSOR FOR Select Item_Number from #Transection open @Next_Product Fetch Next from @Next_Product into @Product

    While @@Fetch_Status=0
        Begin
            set @Item=(Select ItemNumber from  ItemTransection where ItemNumber= @Product)
                if is not null
                    Begin
                        Set @Quantity=(Select top 1 Items.Quantityonhand -ItemsTrasection.Quant as Quantity from Items Join
                                       ItemsTrasection on ItemsTrasection.ItemNumber=Items.ItemNumber where ItemsTrasection.ItemNumber=@Item order by ItemsTrasection.TransDate desc)
                        update #Transection set Transection_Date=(Select top 1 TransDate from ItemsTrasection where ItemNumber=@Item order by TransDate desc), Quantity=@Quantity
                            where Item_Number=@Item
                    End
                Else
                    Begin
                        update #Transection set Transection_Date=(Select top 1 TransDate from Items where ItemNumber=@Item ), Quantity=(Select top 1 from Items where ItemNumber=@Item )
                            where Item_Number=@Item
                    End
        End
Select * from #Transection
  

Комментарии:

1. Это я сделал с помощью Cursor, и это определенно будет работать для вас с небольшими изменениями.

2. Измените ItemNumber на ваше исходное имя столбца, ItemTransection — на ваше имя таблицы сечения, Quant — на ваше количество, а Quantityonhand — на столбец Quantity_On_Hand

3. В случае возникновения какой-либо проблемы вы можете связаться со мной по моей электронной почте gaurav_goel209@yahoo.co.in