SQL calculations based on results previous rows - recursive - sql

I'm trying to do a calculation within SQL which I'm struggling with.
Below is code to create the example that I am working with
CREATE TABLE #temptable ( [AccountNo] bigint, [Month] int,
[PaymentCycle] int, [OpeningBal] decimal(2,2), [Issue] decimal(13,2),
[Collections] decimal(38,2), [Rebates] decimal(38,2), [ClosingBal]
decimal(38,2) )
INSERT INTO #temptable
VALUES
( 1, 201703, 0, 0.00, 100.00, 0.00, 0.00, 100.00 ),
( 1, 201704, 1, NULL, 0.00, -31.60, 0.00, NULL ),
( 1, 201705, 2, NULL, 0.00, 0.00, 0.00, NULL ),
( 1, 201706, 3, NULL, 0.00, 0.00, 0.00, NULL ),
( 1, 201707, 4, NULL, 0.00, -60.00, 0.00, NULL ),
( 1, 201708, 5, NULL, 0.00, -78.00, 0.00, NULL ),
( 1, 201709, 6, NULL, 0.00, 0.00, 0.00, NULL ),
( 1, 201710, 7, NULL, 0.00, 0.00, 0.00, NULL ),
( 1, 201711, 8, NULL, 0.00, 0.00, 0.00, NULL )
In this example I know what the values will be for the first row (Payment Cycle 0) and therefore can manually feed these in, for all of the subsequent rows I need to do 3 calculations.
1) OpeningBal - This is the LAG of the previous rows closing figure
2) Revenue - Calculated as the OpeningBal x 20%
3) ClosingBal - This value is the sum of all components within the row so
OpeningBal + Revenue + Issue + Collections + Rebates
I am able to achieve this for Payment Cycle 1 using the LAG function however I want to this to cycle through and store the previous closing balances in memory to use in the next cycle - I've read up on recursive CTE's but am struggling to implement.
Any help would be greatly appreciated.
Regards,
Nathan
Edit: Added desired result below

Related

Calculate COGS with average method in SQL Server

I have an inventory transaction table. I want to calculate COGS with average method for each row
CREATE TABLE [dbo].[SampleData]
(
[date] [datetime] NULL,
[product_id] [varchar](15) NULL,
[Qty] [decimal](18,2) NULL,
[price] [money] NULL,
[progress] [varchar](10) NULL,
)
INSERT INTO sampledata VALUES ('20170228', 'SLG00034', 86.5, 3300, 'IN')
INSERT INTO sampledata VALUES ('20170307', 'SLG00034', -5, 0, 'OUT')
INSERT INTO sampledata VALUES ('20170312', 'SLG00034', -1, 0, 'OUT')
INSERT INTO sampledata VALUES ('20170318', 'SLG00034', -1, 0, 'OUT')
INSERT INTO sampledata VALUES ('20170511', 'SLG00034', 100, 3380, 'IN')
INSERT INTO sampledata VALUES ('20170518', 'SLG00034', -0.5, 0, 'OUT')
INSERT INTO sampledata VALUES ('20170522', 'SLG00034', -2, 0, 'OUT')
INSERT INTO sampledata VALUES ('20170604', 'SLG00034', -2, 0, 'OUT')
INSERT INTO sampledata VALUES ('20180606', 'SLG00034', 20, 6000, 'IN')
INSERT INTO sampledata VALUES ('20180720', 'SLG00034', -0.5, 0, 'OUT')
INSERT INTO sampledata VALUES ('20180728', 'SLG00034', -4, 0, 'OUT')
INSERT INTO sampledata VALUES ('20180827', 'SLG00034', -4, 0, 'OUT')
INSERT INTO sampledata VALUES ('20180907', 'SLG00034', -1, 0, 'OUT')
INSERT INTO sampledata VALUES ('20191213', 'SLG00034', 10, 7000, 'IN')
INSERT INTO sampledata VALUES ('20200710', 'SLG00034', 20, 4500, 'IN')
INSERT INTO sampledata VALUES ('20200926', 'SLG00034', -0.5, 0, 'OUT')
INSERT INTO sampledata VALUES ('20201004', 'SLG00034', -215, 0, 'OUT')
Desired output:
https://i.stack.imgur.com/h4QNH.png
anybody pls help, i cant figure out avgcost as image above
Edit
I need to calculate closing avgcost for each row with rules :
(Open amount + In amount)รท(Open unit + In unit) = Avg cost per unit
Average cost per unit * Units sold = Cost of Goods Sold
Average cost per unit * closing unit = closing inventory amount
closing inventory amount we will use as an opening amount in next row
closing unit we will use as an opening unit in next row
I came across this FIFO solution a long time ago.
https://social.technet.microsoft.com/wiki/contents/articles/18711.t-sql-fifo-inventory-problem-cost-of-goods-sold.aspx
This may help too.
http://westclintech.com/Blog/EntryId/136/Inventory-Calculations-in-SQL-Server

Insert multiple row with a unique value - SQL

i want to insert multiples rows, my first column (MATR) should be each number of the table #MATR
DECLARE #MATR TABLE (id INT)
INSERT INTO #MATR values (22),(23),(99),(101)
INSERT INTO [dbo].[GPI_DOS_ANC_EXP]
([MATR]
,[ANC_APQ_YEAR]
,[ANC_ACQ_ENS_NB_YEAR]
,[ANC_ACQ_ENS_NB_DAY])
VALUES (*Something here* ,NULL ,'0','0.00')
the database should look like this (22, null, 0, 0.00), (23, null, 0, 0.00), (99, null, 0, 0.00), (101, null, 0, 0.00)
I need some help please !
I think you want insert . . . select:
INSERT INTO [dbo].[GPI_DOS_ANC_EXP] ([MATR], [ANC_APQ_YEAR], [ANC_ACQ_ENS_NB_YEAR],[ANC_ACQ_ENS_NB_DAY])
SELECT m.id, NULL, 0, 0.00
FROM #MATR m;

MS SQL FIFO Partial transfers

I have a number of transactions that transfer inventory from one account to another account. I can transfer all inventory and I can transfer partial inventory.
I need to pay commission to the owner of the account where inventory resides at my commission date.
My report needs to show the original origin of the inventory items if they have transferred and provide a unit_balance that I can calculate commission from.
Example Transactions:
Account 100
Account, trxid, transacted_units, transactiontype, transferfrom, transferto, date
100, 1, 100, buy, NULL, NULL, 1/1/2020
100, 2, 50, transfer in, 200, NULL, 1/2/2020
Account 200
Account, trxid, transacted_units, transactiontype, transferfrom, transferto, date
200, 3, 40, buy, NULL, NULL, 12/1/2019
200, 4, 30, buy, NULL, NULL, 12/2/2019
200, 5, 7, sell, NULL, NULL, 12/3/2019
200, 6, 50, transfer out, NULL, 100, 1/2/2020
My report output needs to show the full details of accounts associated with the inventory that relates to the unit_balance
Report Output:
[level], Account, trxid, parenttrxid, transacted_units, transactiontype, transferfrom, transferto, date, units_balance
0, 100, 1, NULL, 100, buy, NULL, NULL, 1/1/2020, 100
0, 100, 2, NULL, 50, transfer in, 200, NULL, 1/2/2020, NULL
1, 200, 3, 2, 40, buy, NULL, NULL, 12/1/2019, 33
1, 200, 4, 2, 30, buy, NULL, NULL, 12/2/2019, 17
1, 200, 5, 2, 7, sell, NULL, NULL, 12/3/2019, 0
1, 200, 6, 2, 50, transfer out, NULL, 100, 1/2/2020, 0
*The FIFO logic applies the 7 units sold to the first buy for account 200. The transfer out should then calculate the units_balance on the remaining eligible transactions.
The SQL code I have today only works when I transfer out the full inventory amount, not partial transfers:
select
[level],
parentid,
trxid,
account,
transactiontype,
date,
rnk,
transacted_units,
cumulative,
CASE
WHEN cumulative>0 and transacted_units>=cumulative THEN cumulative
WHEN cumulative>0 and transacted_units<cumulative THEN transacted_units
ELSE 0
END units_bal
from (
select
*,
sum(transacted_units*Positive_Negative_Indicator) over (partition by parenttrxid, account order by rnk, date, trxid RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) cumulative
from (
select *,
CASE
WHEN transacted_units*Positive_Negative_Indicator < 0 THEN 0
ELSE ROW_NUMBER() OVER (PARTITION BY parenttrxid, account ORDER BY Positive_Negative_Indicator ASC, date ASC, trxid ASC)
END rnk
from Transactions
) a
) a
The positive_negative_indicator field represents the direction of a transaction. A sell or transfer out is negative whereas the others are positive.
for each current "in" transaction, calculate the running total (of units) for the previous "in" transactions. Then assign as many "out" units that haven't been consumed by the previous "in" transactions (as many "out" units== running total of "out" units, that can be consumed by the current "in" transaction).
declare #t table
(
Account int,
trxid int,
trunits int,
trtype varchar(20),
transfrom int,
transto int,
thedate date
);
insert into #t(Account, trxid, trunits, trtype, transfrom, transto, thedate)
values
(100, 1, 100, 'buy', NULL, NULL, '20200101'),
(100, 2, 50, 'transfer in', 200, NULL, '20200201'),
(200, 3, 40, 'buy', NULL, NULL, '20190112'),
(200, 4, 30, 'buy', NULL, NULL, '20190213'),
(200, 5, 10, 'buy', NULL, NULL, '20190214'),
(200, 6, 7, 'sell', NULL, NULL, '20190315'),
(200, 7, 9, 'sell', NULL, NULL, '20190316'),
(200, 8, 25, 'buy', NULL, NULL, '20190317'),
(200, 9, 39, 'sell', NULL, NULL, '20190318'),
(200, 10, 18, 'sell', NULL, NULL, '20190319'),
(200, 11, 14, 'sell', NULL, NULL, '20190320'),
(200, 11, 50, 'transfer out', NULL, 100, '20200201');
select *, case when t.trtype not in ('sell', 'transfer out') then t.trunits -isnull(otu.out_units, 0) else null end as leftover_units
from
(
select *, sum(case when trtype not in ('sell', 'transfer out') then trunits else 0 end) over (partition by Account order by thedate rows between unbounded preceding and 1 preceding) as previous_in_running_units
from #t
) as t
outer apply
(
select top (1) ort.out_units_running_total - isnull(t.previous_in_running_units, 0) as out_units
from
(
select sum(o.trunits) over(order by o.thedate) as out_units_running_total
from #t as o
where o.trtype in ('sell', 'transfer out')
and o.Account = t.Account
and t.trtype not in ('sell', 'transfer out') --no calculations needed when cross applying for "out" transactions
) as ort --out running totals
where ort.out_units_running_total-isnull(t.previous_in_running_units, 0) <= t.trunits --<-- ("in") use as many out units as can be consumed by current t.transaction/date after deducting what has been consumed by the previous t.transaction/date
and ort.out_units_running_total-isnull(t.previous_in_running_units, 0) > 0 --not needed(?) if balance is guaranteed.. total_out = total_in
order by ort.out_units_running_total desc
) as otu; --"out units"

find the first value based on date and id column

I want to find the values of time taken by a given depot for the stationary.
Below is the code for the create table and values. I have also achieved the other requirements for the same table and also have shared the code below.
I want to create an new column [StationaryFirstWaitTime] where I can get the First wait time for the same scenario based.
For a given ShipmentId, VehicleId,
on where DepotId = StationayId get the [StationaryEndTime] - [StationaryStarttime] for the first value which is received on an given date for an specific vehicle and shipmentid.
below is the code
CREATE TABLE [dbo].[Table_Consolidate_Friday](
[Sno] [int] NOT NULL,
[VehicleId] [nchar](10) NULL,
[DepotId] [int] NULL,
[DepotVisitStartTime] [datetime2](7) NULL,
[DepotVisitEndTime] [datetime2](7) NULL,
[StationaryId] [int] NULL,
[StationaryStartTime] [datetime2](7) NULL,
[StationaryEndTime] [datetime2](7) NULL,
[ActualQty] [bigint] NULL,
[AggreageQty] [bigint] NULL,
[StationaryWaitTimeTotal] [datetime2](7) NULL,
[StationaryFirstWaitTime] [datetime2](7) NULL,
[StationaryRowCount] [bigint] NULL
) ON [PRIMARY]
GO
INSERT [dbo].[Table_Consolidate_Friday] ([Sno], [VehicleId], [DepotId], [DepotVisitStartTime], [DepotVisitEndTime], [StationaryId], [StationaryStartTime], [StationaryEndTime], [ActualQty], [AggreageQty], [StationaryWaitTimeTotal], [StationaryRowCount]) VALUES
(1, N'TN1 ', 15, '2019-02-15T07:25:33', '2019-02-15T17:25:33', 15, '2019-02-15T07:55:32', '2019-02-15T08:15:23', 10, 119, '2019-02-22T02:02:47', 4),
(1, N'TN1 ', 3, '2019-02-15T07:25:33', '2019-02-15T17:25:33', 3, '2019-02-15T09:22:52', '2019-02-15T09:45:59', 20, 119, '2019-02-22T02:02:47', 4),
(1, N'TN1 ', 8, '2019-02-15T07:25:33', '2019-02-15T17:25:33', 8, '2019-02-15T11:25:36', '2019-02-15T02:35:37', 33, 119, '2019-02-22T02:02:47', 4),
(1, N'TN1 ', 12, '2019-02-15T07:25:33', '2019-02-15T17:25:33', 12, '2019-02-15T15:15:33', '2019-02-15T15:25:21', 56, 119, '2019-02-22T02:02:47', 4),
(2, N'KA2 ', 23, '2019-02-15T06:12:52', '2019-02-15T11:21:35', 23, '2019-02-15T10:25:13', '2019-02-15T11:15:23', 72, 114, '2019-02-22T01:24:10', 2),
(2, N'KA2 ', 20, '2019-02-15T06:12:52', '2019-02-15T11:21:35', 20, '2019-02-15T07:11:33', '2019-02-15T07:45:33', 42, 114, '2019-02-22T01:24:10', 2),
(3, N'AP3 ', 20, '2019-02-15T06:32:52', '2019-02-15T11:21:35', 20, '2019-02-15T07:13:13', '2019-02-15T08:05:01', 15, 37, '2019-02-22T01:14:18', 2),
(3, N'AP3 ', 21, '2019-02-15T06:32:52', '2019-02-15T11:21:35', 21, '2019-02-15T09:43:12', '2019-02-15T10:05:42', 22, 37, '2019-02-22T01:14:18', 2),
(3, N'AP3 ', 15, '2019-02-15T13:12:21', '2019-02-15T19:23:32', 15, '2019-02-15T14:13:13', '2019-02-15T14:45:21', 34, 34, '2019-02-22T00:32:08', 1)
I have written code to add and aggregate values and count as below
SELECT
AggreageQty = SUM(ActualQty) OVER (PARTITION BY Sno, DepotVisitStartTime),
StationaryWaitTimeTotal = CAST(DATEADD(SECOND, SUM(DATEDIFF(SECOND, StationaryStartTime, StationaryEndTime) ) OVER (PARTITION BY Sno, DepotVisitStartTime), 0) AS TIME),
StationaryRowCount = COUNT(*) OVER (PARTITION BY Sno, DepotVisitStartTime)
FROM [dbo].[Table_Consolidate]
I need to get the result as below for [StationaryFirstWaitTime] as below
FirstWaitTime
0:-19:-51
0:-19:-51
0:-19:-51
0:-19:-51
0:-50:-10
0:-50:-10
0:-51:-48
0:-51:-48
0:-32:-8
Platform: Azure SQL Datawarehouse
Window aggregate function: FIRST_VALUE.
Requested extra column is indeed has a non-standard look, so FORMAT() to meet such requirement:
SQL:
SELECT
AggreageQty = SUM(ActualQty) OVER (PARTITION BY Sno, DepotVisitStartTime),
StationaryWaitTimeTotal = CAST(DATEADD(SECOND, SUM(DATEDIFF(SECOND, StationaryStartTime, StationaryEndTime) ) OVER (PARTITION BY Sno, DepotVisitStartTime), 0) AS TIME),
StationaryRowCount = COUNT(*) OVER (PARTITION BY Sno, DepotVisitStartTime),
StationaryFirstWaitTime = FORMAT(FIRST_VALUE ( CAST(DATEADD(SECOND, DATEDIFF(SECOND, StationaryStartTime, StationaryEndTime) , 0) AS datetime) ) OVER (PARTITION BY Sno, DepotVisitStartTime order by StationaryStartTime), 'H:-m:-s')
FROM [dbo].[Table_Consolidate_Friday]
That extra column of interest results to:
StationaryFirstWaitTime
0:-19:-51
0:-19:-51
0:-19:-51
0:-19:-51
0:-34:-0
0:-34:-0
0:-51:-48
0:-51:-48
0:-32:-8
Update:
OP uses SQL Datawarehouse. FORMAT() is not available there, workaround:
StationaryFirstWaitTime = REPLACE(CONVERT(VARCHAR(8),FIRST_VALUE ( CAST(DATEADD(SECOND, DATEDIFF(SECOND, StationaryStartTime, StationaryEndTime) , 0) AS TIME) ) OVER (PARTITION BY Sno, DepotVisitStartTime order by StationaryStartTime), 8), ':', ':-')
Which results to:
StationaryFirstWaitTime
00:-19:-51
00:-19:-51
00:-19:-51
00:-19:-51
00:-34:-00
00:-34:-00
00:-51:-48
00:-51:-48
00:-32:-08

Optimize aggregating query

I have a view that has suddenly gotten too slow and I'm at a loss of how to optimize it. The tables currently contain 15000 (#dispatchPallet) and 135000 (#pickLog) rows respectively.
I've written a minimized piece of code to show the important parts below.
DECLARE #dispatchPallet TABLE
(
[PICK_PALL_NUM] [bigint] NOT NULL,
[PALLET_PLACEMENT] [nvarchar](4) NOT NULL,
[SHIPMENT_ID] [nvarchar](255) NULL
)
DECLARE #pickLog TABLE
(
[LINE_NUM] [int] NOT NULL,
[QTY_PRE] [numeric](9, 2) NULL,
[QTY_SUF] [numeric](9, 2) NULL,
[PICK_PALL_NUM] [bigint] NULL,
[ROWID] [uniqueidentifier] NOT NULL,
[WEIGHT_GROSS] [numeric](9, 3) NULL,
[VOLUME] [numeric](9, 3) NULL
)
INSERT INTO #dispatchPallet ([PICK_PALL_NUM], [PALLET_PLACEMENT], [SHIPMENT_ID])
VALUES
(4797753, 'B', 'SHIPMENT-1'),
(4797752, 'B', 'SHIPMENT-2'),
(4797750, 'B', 'SHIPMENT-3'),
(4797749, 'B', 'SHIPMENT-4'),
(4797739, 'B', 'SHIPMENT-5'),
(4797732, 'B', 'SHIPMENT-6'),
(4797731, 'B', 'SHIPMENT-7'),
(4797730, 'B', 'SHIPMENT-7'),
(4797723, 'B', 'SHIPMENT-8'),
(4797713, 'B', 'SHIPMENT-9')
INSERT INTO #pickLog ([LINE_NUM], [QTY_PRE], [QTY_SUF], [PICK_PALL_NUM], [ROWID], [WEIGHT_GROSS])
VALUES
(30, 54, 54, 4797753, NEWID(), 1070.280),
(10, 24, 24, 4797752, NEWID(), 471.360),
(30, 12, 12, 4797750, NEWID(), 237.960),
(320, 25, 25, 4797749, NEWID(), 102.750),
(110, 3, 3, 4797739, NEWID(), 40.650),
(40, 12, 12, 4797732, NEWID(), 238.080),
(50, 4, 4, 4797732, NEWID(), 78.560),
(20, 20, 20, 4797731, NEWID(), 110.000),
(20, 40, 40, 4797730, NEWID(), 220.000),
(1340, 3, 3, 4797723, NEWID(), 14.250),
(410, 2, 2, 4797723, NEWID(), 4.780),
(440, 2, 2, 4797723, NEWID(), 21.000),
(480, 1, 1, 4797723, NEWID(), 3.500),
(1290, 2, 2, 4797723, NEWID(), 39.280),
(470, 1, 1, 4797723, NEWID(), 8.500),
(280, 3, 3, 4797723, NEWID(), 16.500),
(10, 2, 2, 4797723, NEWID(), 10.700),
(500, 2, 2, 4797723, NEWID(), 6.600),
(290, 1, 1, 4797713, NEWID(), 0.540),
(40, 2, 2, 4797713, NEWID(), 33.800)
SELECT
[dispatchPallet].[SHIPMENT_ID],
SUM([pickLog].[QTY_SUF]) AS KOLLI,
COUNT(DISTINCT [pickLog].[LINE_NUM]) AS LINES,
SUM([pickLog].[WEIGHT_GROSS]) AS PICKED_WEIGHT,
COUNT(DISTINCT [pickLog].[PICK_PALL_NUM]) AS PALLETS,
COUNT(DISTINCT CASE WHEN [dispatchPallet].[PALLET_PLACEMENT] = 'B' THEN [dispatchPallet].[PICK_PALL_NUM] ELSE NULL END) AS BOTTOM_PALLETS
FROM
#dispatchPallet dispatchPallet
INNER JOIN #pickLog pickLog ON [dispatchPallet].[PICK_PALL_NUM] = [pickLog].[PICK_PALL_NUM]
GROUP BY
[dispatchPallet].[SHIPMENT_ID]
-- Expected output:
-- SHIPMENT_ID KOLLI LINES PICKED_WEIGHT PALLETS BOTTOM_PALLETS
-- SHIPMENT-1 54.00 1 1070.280 1 1
-- SHIPMENT-2 24.00 1 471.360 1 1
-- SHIPMENT-3 12.00 1 237.960 1 1
-- SHIPMENT-4 25.00 1 102.750 1 1
-- SHIPMENT-5 3.00 1 40.650 1 1
-- SHIPMENT-6 16.00 2 316.640 1 1
-- SHIPMENT-7 60.00 1 330.000 2 2
-- SHIPMENT-8 18.00 9 125.110 1 1
-- SHIPMENT-9 3.00 2 34.340 1 1
You should at least create primary constraint on as
ALTER TABLE #dispatchPallet TABLE ADD PRIMARY KEY (PICK_PALL_NUM);
Foreign Key constraint as
ALTER TABLE #pickLog TABLE ADD foreign key (PICK_PALL_NUM) references #dispatchPallet(PICK_PALL_NUM)
Also create a unique index on
CREATE UNIQUE NONCLUSTERED INDEX idx_PALLET_PLACEMENT_notnull
ON #dispatchPallet(PALLET_PLACEMENT)
WHERE PALLET_PLACEMENT IS NOT NULL;
Your query is simple and there isn't much room to optimize. You should check that you at least have indexes on dispatchPallet by SHIPMENT_ID and on pickLog by PICK_PALL_NUM. These would be the best choices for your query:
CREATE NONCLUSTERED INDEX NCI_dispatchPallet_shipment_ID
ON dispatchPallet (SHIPMENT_ID, PICK_PALL_NUM)
INCLUDE (PALLET_PLACEMENT)
CREATE NONCLUSTERED INDEX NCI_pickLog_pick_pall_num
ON pickLog (PICK_PALL_NUM)
INCLUDE (QTY_SUF, LINE_NUM, WEIGHT_GROSS)
You should also validate if you need your COUNT to be DISTINCT or not (distinct is an expensive operation).
Last but not least, you should really check how you access the view; if you are filtering it, joining it, etc. These other conditions might generate different query plans and make your performance go down if not managed correctly (even with the right indexes!).
For starters there should be primary keys and foreign keys on these tables so that this query can do index seeks/scans (paparazzo's comment above) as opposed to full table seeks/scans.
In addition to the bigint/int, what's the purpose of the uniqueidentifier?