Map replenishment to requirement based on field value - SQL Server - sql
I am attempting to find which "replenishment" (a positive transaction quantity) can be matched to a "requirement" (a negative transaction quantity).
The basic logic would be: For a given requirement, find the first available replenishment (whether that replenishment be from existing inventory, or from an upcoming change).
I am working with a table dbo_purchases_new that looks like this:
| Element_ID | Element | Transaction_Date | Transaction_Quantity | Total_Inventory |
|:----------:|:----------:|:----------------:|:--------------------:|:---------------:|
| | STOCK | | 5 | 5 |
| MO302 | Make_Order | 1/3/2019 | 1 | 6 |
| SO105 | Sale | 2/1/2019 | -1 | 5 |
| SO106 | Sale | 2/1/2019 | -1 | 4 |
| MO323 | Make_Order | 2/2/2019 | 1 | 5 |
| SO107 | Sale | 2/4/2019 | -1 | 4 |
| SO191 | Sale | 2/5/2019 | -1 | 3 |
| SO123 | Sale | 2/6/2019 | -1 | 2 |
| SO166 | Sale | 3/1/2019 | -1 | 1 |
| SO819 | Sale | 3/5/2019 | -1 | 0 |
| SO603 | Sale | 3/10/2019 | -4 | -3 |
| MO400 | Make_Order | 3/15/2019 | 1 | -2 |
| MO459 | Make_Order | 3/15/2019 | 1 | -1 |
| MO460 | Make_Order | 3/18/2019 | 1 | 0 |
| MO491 | Make_Order | 3/19/2019 | 1 | 1 |
| MO715 | Make_Order | 4/1/2019 | 3 | 4 |
| SO100 | Sale | 4/2/2019 | -1 | 3 |
| SO322 | Sale | 4/3/2019 | -1 | 2 |
| SO874 | Sale | 4/4/2019 | -1 | 1 |
| SO222 | Sale | 4/5/2019 | -1 | 0 |
| MO999 | Make_Order | 4/5/2019 | 1 | 1 |
| SO999 | Sale | 4/6/2019 | -1 | 0 |
that is being created as a result of this question.
I am now attempting to track which Make_Order will fulfill which Sale by tracking the Transaction_Quantity.
Ideally, the resulting dataset would look like this, where Replenishment and Replenishment_Date are newly added columns:
| Element_ID | Element | Transaction_Date | Transaction_Quantity | Total_Inventory | Replenishment | Replenishment_Date |
|:----------:|:----------:|:----------------:|:--------------------:|:---------------:|:-------------:|:------------------:|
| | STOCK | | 5 | 5 | NULL | NULL |
| MO302 | Make_Order | 1/3/2019 | 1 | 6 | NULL | NULL |
| SO105 | Sale | 2/1/2019 | -1 | 5 | STOCK | NULL |
| SO106 | Sale | 2/1/2019 | -1 | 4 | STOCK | NULL |
| MO323 | Make_Order | 2/2/2019 | 1 | 5 | NULL | NULL |
| SO107 | Sale | 2/4/2019 | -1 | 4 | STOCK | NULL |
| SO191 | Sale | 2/5/2019 | -1 | 3 | STOCK | NULL |
| SO123 | Sale | 2/6/2019 | -1 | 2 | STOCK | NULL |
| SO166 | Sale | 3/1/2019 | -1 | 1 | MO302 | 1/3/2019 |
| SO819 | Sale | 3/5/2019 | -1 | 0 | MO323 | 2/2/2019 |
| SO603 | Sale | 3/10/2019 | -4 | -3 | MO460 | 3/18/2019 |
| MO400 | Make_Order | 3/15/2019 | 1 | -2 | NULL | NULL |
| MO459 | Make_Order | 3/15/2019 | 1 | -1 | NULL | |
| MO460 | Make_Order | 3/18/2019 | 1 | 0 | NULL | NULL |
| MO491 | Make_Order | 3/19/2019 | 1 | 1 | NULL | NULL |
| MO715 | Make_Order | 4/1/2019 | 3 | 4 | NULL | NULL |
| SO100 | Sale | 4/2/2019 | -1 | 3 | MO491 | 3/19/2019 |
| SO322 | Sale | 4/3/2019 | -1 | 2 | MO715 | 4/1/2019 |
| SO874 | Sale | 4/4/2019 | -1 | 1 | MO715 | 4/1/2019 |
| SO222 | Sale | 4/5/2019 | -1 | 0 | MO715 | 4/1/2019 |
| MO999 | Make_Order | 4/5/2019 | 1 | 1 | NULL | NULL |
| SO999 | Sale | 4/6/2019 | -1 | 0 | SO999 | 4/5/2019 |
The ruleset would essentially be:
For a given requirement (a negative transaction quantity of arbitrary value), find which replenishment (a positive transaction quantity of arbitrary value) satisfies it.
Stock is assigned to the first requirements until it runs out. NOTE
-- it could be the case that stock does not exist, so this only applies IF stock does exist
Then, map replenishments to requirements based on the
Transaction_Date in ASC order
I am very confused on how to accomplish this. I imagine some pseudocode would look something like:
for curr in transaction_quantity:
if curr < 0:
if stock.exists() and stock.notempty():
fill in data from that
else:
find next replenishment
fill in data from that
else:
next
Right now, I have this so far, but I know that it will not run. I am very confused on where to go from here. I have tried looking at posts like this, but that did not have an answer. I then tried looking up CURSOR, but that was very confusing to me and I am unsure how I can apply that to this problem.
/****** WiP Script ******/
SELECT
[jerry].[dbo].[purchases_new].*,
CASE WHEN Transaction_Quantity < 0 THEN -- (SELECT Element_ID FROM the_current_row WHERE transaction_quantity > 0)
ELSE NULL AS "Replenishment",
-- (SELECT Transaction_Date FROM [jerry].[dbo].[purchases_new] WHERE Element_ID
-- Not sure how to grab the correct date of the element id from the column before
FROM
[jerry].[dbo].[purchases_new]
Any assistance is appreciated. I have been pulling my hair out on this problem. The comments contain additional information.
NOTE - I have continually tried to update this question as users have requested more information.
Here is one attempt. You will need to modify if with another layer of abstraction for offsets if you need to support transaction increments/decrements > 1. It basically aligns the order of sales with the order of debits and then uses that as join back to the main dataset.
Sql Fiddle
The idea is to put additions and subtractions into two sets, orderd chronologically by set, while also remembering order of each item back into the main list. This way, you can align each subtraction with the nearest addition. This is pretty straightforward when dealing with 1's.
Edit --> Dealing with values > 1.
Computing Transaction_Amount > (+/-)1 adds a little complexity, but still solvable. Now we need to stretch each addition and subtraction transaction set out by the Transaction_Amount, so the dataset is lengthened, however, the original algorithm will still be applied to a now longer dataset. This will allow for the recording of "partial fulfillments". So (12 A 5) would equate to (12 A 1), (12 A 1), (12 A 1), (12 A 1), (12 A 1). Now, when the subtractors are lengthened in similar fashion, (with all rows in the same order as the first of the sequence) the alignment will still work and addition and subtractions can be matched with the nearest neighbor(s).
DECLARE #T TABLE(Element_ID NVARCHAR(50),Element NVARCHAR(50), Transaction_Date DATETIME,Transaction_Quantity INT,Total_Inventory INT)
INSERT #T VALUES
('MO301','Make_Order','1/1/2019',5,1),
('MO302','Make_Order','1/3/2019',1,2),
('SO105','Sale','2/1/2019',-2,1),
('SO106','Sale','2/1/2019',-1,0),
('MO323','Make_Order','2/2/2019',1,1),
('SO107','Sale','2/4/2019',-1,0),
('SO191','Sale','2/5/2019',-1,-1),
('SO123','Sale','2/6/2019',-1,-2),
('SO166','Sale','3/1/2019',-1,-3),
('SO603','Sale','3/2/2019',-1,-4),
('MO400','Make_Order','3/15/2019',1,-3),
('MO459','Make_Order','3/15/2019',1,-2),
('MO460','Make_Order','3/18/2019',1,-1),
('MO491','Make_Order','3/19/2019',1,0)
;WITH Normalized AS
(
SELECT *, RowNumber = ROW_NUMBER() OVER (ORDER BY (SELECT 0)), IsAdd = CASE WHEN Transaction_Quantity>0 THEN 1 ELSE 0 END FROM #T
)
,ReplicateAmount AS
(
SELECT Element_ID, Element, Transaction_Date, Transaction_Quantity=ABS(Transaction_Quantity) ,Total_Inventory, RowNumber, IsAdd
FROM Normalized
UNION ALL
SELECT R.Element_ID, R.Element, R.Transaction_Date, Transaction_Quantity=(R.Transaction_Quantity - 1), R.Total_Inventory, R.RowNumber, R.IsAdd
FROM ReplicateAmount R INNER JOIN Normalized N ON R.RowNumber = N.RowNumber
WHERE ABS(R.Transaction_Quantity) > 1
)
,NormalizedAgain AS
(
SELECT Element_ID, Element, Transaction_Date, Transaction_Quantity=1, Total_Inventory, RowNumber = ROW_NUMBER() OVER (ORDER BY RowNumber), IsAdd FROM ReplicateAmount
)
,Additives AS
(
SELECT *, AddedOrder = ROW_NUMBER() OVER (ORDER BY (SELECT 0)) FROM NormalizedAgain WHERE IsAdd=1
)
,Subtractions AS
(
SELECT Element_ID, Element, Transaction_Date, Transaction_Quantity=-1 , Total_Inventory, RowNumber, SubtractedOrder = ROW_NUMBER() OVER (ORDER BY (SELECT 0))FROM NormalizedAgain WHERE IsAdd=0
)
,WithTies AS
(
SELECT
S.RowNumber,
S.Element_ID,
BoughtFromRowNumber = A.RowNumber,
SoldToID =S.Element_ID,
BoughFromID=A.Element_ID,
S.Element,
S.Transaction_Date,
S.Transaction_Quantity,
S.Total_Inventory
FROM
Additives A
LEFT OUTER JOIN Subtractions S ON A.AddedOrder=S.SubtractedOrder
UNION
SELECT
A.RowNumber,
A.Element_ID,
BoughtFromRowNumber = S.RowNumber,
SoldToID = NULL,
BoughFromID=NULL,
A.Element,
A.Transaction_Date,
A.Transaction_Quantity,
A.Total_Inventory
FROM
Additives A
LEFT OUTER JOIN Subtractions S ON A.AddedOrder=S.SubtractedOrder
)
SELECT
T.RowNumber,
T.Element_ID,
T.Element,
T.Transaction_Date,
T.Transaction_Quantity,
T.Total_Inventory,
T2.SoldToID,
T.BoughFromID
FROM
WithTies T
LEFT OUTER JOIN WithTies T2 ON T2.BoughtFromRowNumber= T.RowNumber
WHERE
NOT T.RowNumber IS NULL
ORDER BY
T.RowNumber
Related
mySQL : Populate a column with the result of a query
I have a table ORDERS with a column named cache_total_price, spent by each client. +----+-----------+------------+-----------+------------------+ | id | client_id | date | reference | cache_total_price| +----+-----------+------------+-----------+------------------+ | 1 | 20 | 2019-01-01 | 004214 | 0 | | 2 | 3 | 2019-01-03 | 007120 | 0 | | 3 | 11 | 2019-01-04 | 002957 | 0 | | 4 | 6 | 2019-01-07 | 003425 | 0 | I have another table ORDERS_REFS where there is the price total spent for each orders id +-----+-------------+------------+----------+---------------+------------+ | id | order_id | name | quantity | unit_price | total_price| +-----+-------------+------------+----------+---------------+------------+ | 1 | 1 | Produit 19 | 3 | 49.57 | 148.71 | | 2 | 1 | Produit 92 | 4 | 81.24 | 324.96 | | 3 | 1 | Produit 68 | 2 | 17.48 | 34.96 | | 4 | 2 | Produit 53 | 4 | 83.69 | 334.76 | | 5 | 2 | Produit 78 | 6 | 5.99 | 35.94 | I want to had to column cache_total_price the result of my query : select sum(total_price) from orders_refs group by order_id; result : +--------------------+ | sum(total_price) | +--------------------+ | 508.6299819946289 | | 370.700008392334 | | 132.3699951171875 | | 2090.1800079345703 | I've tried some queries with Insert into select or update set, but didn't worked :(
If you're wanting to update all the orders at once, in MySQL a subquery like this should do the trick. You could always add a WHERE clause to do one at a time. UPDATE ORDERS SET cache_total_price = ( SELECT sum(total_price) from ORDERS_REF where order_id = ORDERS.id )
update t set t.cache_total_price=x.sum_total_price from ORDERS as t join ( select order_id,sum(total_price)as sum_total_price from orders_refs group by order_id )x on t.id=x.order_id In SQL Server you can try this one.
Finding MAX date aggregated by order - Oracle SQL
I have a data orders that looks like this: | Order | Step | Step Complete Date | |:-----:|:----:|:------------------:| | A | 1 | 11/1/2019 | | | 2 | 11/1/2019 | | | 3 | 11/1/2019 | | | 4 | 11/3/2019 | | | 5 | 11/3/2019 | | | 6 | 11/5/2019 | | | 7 | 11/5/2019 | | B | 1 | 12/1/2019 | | | 2 | 12/2/2019 | | | 3 | | | C | 1 | 10/21/2019 | | | 2 | 10/23/2019 | | | 3 | 10/25/2019 | | | 4 | 10/25/2019 | | | 5 | 10/25/2019 | | | 6 | | | | 7 | 10/27/2019 | | | 8 | 10/28/2019 | | | 9 | 10/29/2019 | | | 10 | 10/30/2019 | | D | 1 | 10/30/2019 | | | 2 | 11/1/2019 | | | 3 | 11/1/2019 | | | 4 | 11/2/2019 | | | 5 | 11/2/2019 | What I need to accomplish is the following: For each order, assign the 'Order_Completion_Date' field as the most recent 'Step_Complete_Date'. If ANY 'Step_Complete_Date' is NULL, then the value for 'Order_Completion_Date' should be NULL. I set up a SQL FIDDLE with this data and my attempt, below: SELECT OrderNum, MAX(Step_Complete_Date) FROM OrderNums WHERE Step_Complete_Date IS NOT NULL GROUP BY OrderNum This is yielding: ORDERNUM MAX(STEP_COMPLETE_DATE) D 11/2/2019 A 11/5/2019 B 12/2/2019 C 10/30/2019 How can I achieve: | OrderNum | Order_Completed_Date | |:--------:|:--------------------:| | A | 11/5/2019 | | B | NULL | | C | NULL | | D | 11/2/2019 |
Aggregate function with KEEP can handle this select ordernum, max(step_complete_date) keep (DENSE_RANK FIRST ORDER BY step_complete_date desc nulls first) res FROM OrderNums GROUP BY OrderNum
You can use a CASE expression to first count if there are any NULL values and if not then find the maximum value: Query 1: SELECT OrderNum, CASE WHEN COUNT( CASE WHEN Step_Complete_Date IS NULL THEN 1 END ) > 0 THEN NULL ELSE MAX(Step_Complete_Date) END AS Order_Completion_Date FROM OrderNums GROUP BY OrderNum Results: | ORDERNUM | ORDER_COMPLETION_DATE | |----------|-----------------------| | D | 11/2/2019 | | A | 11/5/2019 | | B | (null) | | C | (null) |
First, you are representing dates as varchars in mm/dd/yyyy format (at least in fiddle). With max function it can produce incorrect result, try for example order with dates '11/10/2019' and '11/2/2019'. Second, the most simple solution is IMHO to use fallback date for nulls and get null back when fallback date wins: SELECT OrderNum, NULLIF(MAX(NVL(Step_Complete_Date,'~')),'~') FROM OrderNums GROUP BY OrderNum (Example is still for varchars since tilde is greater than any digit. For dates, you could use 9999-12-31, for instance.)
SQL calculating sum and number of distinct values within group
I want to calculate (1) total sales amount (2) number of distinct stores per product in one query, if possible. Suppose we have data: +-----------+---------+-------+--------+ | store | product | month | amount | +-----------+---------+-------+--------+ | Anthill | A | 1 | 1 | | Anthill | A | 2 | 1 | | Anthill | A | 3 | 1 | | Beetle | A | 1 | 1 | | Beetle | A | 3 | 1 | | Cockroach | A | 1 | 1 | | Cockroach | A | 2 | 1 | | Cockroach | A | 3 | 1 | | Anthill | B | 1 | 1 | | Beetle | B | 2 | 1 | | Cockroach | B | 3 | 1 | +-----------+---------+-------+--------+ I have tried this with no luck: select [product] ,[month] ,[amount] ,cnt_distinct_stores = count(distinct(stores)) from dbo.temp group by [product] ,[month] order by 1,2 Would there be possible any combination of GROUP BY clause with window functions like SUM(amount) OVER(partition by [product],[month] ORDER BY [month] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
Try SELECT product, SUM(amount), COUNT(DISTINCT store) FROM dbo.temp GROUP BY product
Sum data from two tables with different number of rows
There are 3 Tables (SorMaster, SorDetail, and InvWarehouse): SorMaster: +------------+ | SalesOrder | +------------+ | 100 | | 101 | | 102 | +------------+ SorDetail: +------------+------------+---------------+ | SalesOrder | MStockCode | MBackOrderQty | +------------+------------+---------------+ | 100 | PN-1 | 4 | | 100 | PN-2 | 9 | | 100 | PN-3 | 1 | | 100 | PN-4 | 6 | | 101 | PN-1 | 6 | | 101 | PN-3 | 2 | | 102 | PN-2 | 19 | | 102 | PN-3 | 14 | | 102 | PN-4 | 6 | | 102 | PN-5 | 4 | +------------+------------+---------------+ InvWarehouse: +------------+-----------+-----------+ | MStockCode | Warehouse | QtyOnHand | +------------+-----------+-----------+ | PN-1 | A | 1 | | PN-2 | B | 9 | | PN-3 | A | 0 | | PN-4 | B | 1 | | PN-1 | A | 0 | | PN-3 | B | 5 | | PN-2 | A | 9 | | PN-3 | B | 4 | | PN-4 | A | 6 | | PN-5 | B | 0 | +------------+-----------+-----------+ Desired Results: +------------+-----------------+--------------+ | MStockCode | SumBackOrderQty | SumQtyOnHand | +------------+-----------------+--------------+ | PN-1 | 10 | 10 | | PN-2 | 28 | 1 | | PN-3 | 17 | 5 | | PN-4 | 12 | 13 | | PN-5 | 11 | 6 | +------------+-----------------+--------------+ I have been going around in circles with no end in sight. Seems like it should be simple but just can't wrap my head around it. The SumBackOrderQty obviously getting counted twice as the SumQtyOnHand is evaluated. To this point I have been doing the calculations in the PHP instead of the select statement but would like to clean things up a bit where possible. Current query statement is: SELECT SorDetail.MStockCode, SUM(SorDetail.MBackOrderQty) AS 'SumMBackOrderQty', SUM(InvWarehouse.QtyOnHand) AS 'SumQtyOnHand' FROM SysproCompanyJ.dbo.SorMaster SorMaster, SysproCompanyJ.dbo.SorDetail SorDetail LEFT OUTER JOIN SysproCompanyJ.dbo.InvWarehouse InvWarehouse ON SorDetail.MStockCode = InvWarehouse.StockCode WHERE SorMaster.SalesOrder = SorDetail.SalesOrder AND SorMaster.ActiveFlag != 'N' AND SorDetail.MBackOrderQty > '0' AND SorDetail.MPrice > '0' GROUP BY SorDetail.MStockCode ORDER BY SorDetail.MStockCode ASC
Without providing the complete picture, in terms of your RDBMS, database schema, a description of the problem you're trying to solve and sample data that matches the aforementioned, the following is just an illustration of what a solution based on Barmar's comment could look like: SELECT SD.MStockCode, SD.SumBackOrderQty, IW.SumQtyOnHand FROM (SELECT MStockCode, SUM(MBackOrderQty) AS `SumBackOrderQty` FROM SorDetail JOIN SorMaster ON SorDetail.SalesOrder=SorMaster.SalesOrder WHERE SorMaster.ActiveFlag != 'N' AND SorDetail.MBackOrderQty > 0 AND SorDetail.MPrice > 0 GROUP BY MStockCode) AS SD LEFT JOIN (SELECT MStockCode, SUM(QtyOnHand) AS `SumQtyOnHand` FROM InvWarehouse GROUP BY MStockCode) AS IW ON SD.MStockCode=IW.MStockCode ORDER BY SD.MStockCode;
Here's one approach: select MStockCode, (select sum(MBackOrderQty) from sorDetail as T2 where T2.MStockCode = T1.MStockCode ) as SumBackOrderQty, (select sum(QtyOnHand) from invWarehouse as T3 where T3.MStockCode = T1.MStockCode ) as SumQtyOnHand from ( select mstockcode from sorDetail union select mstockcode from invWarehouse ) as T1 In a fiddle here: http://sqlfiddle.com/#!9/fdaca/6 Though my SumQtyOnHand values don't match yours (as #Gordon pointed out).
How do you join records one to one with multiple possible matches ...?
I have a table of transactions like the following | ID | Trans Type | Date | Qty | Total | Item Number | Work Order | ------------------------------------------------------------------------- | 1 | Issue | 11/27/2012 | 3 | 3.50 | NULL | 10 | | 2 | Issue | 11/27/2012 | 3 | 3.50 | NULL | 11 | | 3 | Issue | 11/25/2012 | 1 | 1.25 | NULL | 12 | | 4 | ID Issue | 11/27/2012 | -3 | -3.50 | 100 | NULL | | 5 | ID Issue | 11/27/2012 | -3 | -3.50 | 102 | NULL | | 6 | ID Issue | 11/25/2012 | -1 | -1.25 | 104 | NULL | These transactions are duplicates where the 'Issue's have a work order ID while the 'ID Issue' transactions have the item number. I would like to update the [Item Number] field for the 'Issue' transactions to include the Item Number. When I do a join on the Date, Qty, and Total I get something like this | ID | Trans Type | Date | Qty | Total | Item Number | Work Order | ------------------------------------------------------------------------- | 1 | Issue | 11/27/2012 | 3 | 3.50 | 100 | 10 | | 1 | Issue | 11/27/2012 | 3 | 3.50 | 102 | 10 | | 2 | Issue | 11/27/2012 | 3 | 3.50 | 100 | 11 | | 2 | Issue | 11/27/2012 | 3 | 3.50 | 102 | 11 | | 3 | Issue | 11/25/2012 | 1 | 1.25 | 104 | 12 | The duplicates are multiplied! I would like this | ID | Trans Type | Date | Qty | Total | Item Number | Work Order | ------------------------------------------------------------------------- | 1 | Issue | 11/27/2012 | 3 | 3.50 | 100 | 10 | | 2 | Issue | 11/27/2012 | 3 | 3.50 | 102 | 11 | | 3 | Issue | 11/25/2012 | 1 | 1.25 | 104 | 12 | Or this (Item Number is switched for the two matches) | ID | Trans Type | Date | Qty | Total | Item Number | Work Order | ------------------------------------------------------------------------- | 1 | Issue | 11/27/2012 | 3 | 3.50 | 102 | 10 | | 2 | Issue | 11/27/2012 | 3 | 3.50 | 100 | 11 | | 3 | Issue | 11/25/2012 | 1 | 1.25 | 104 | 12 | Either would be fine. What would be a simple solution?
Use SELECT DISTINCT to filter same results out or you could partition your results to get the first item in each grouping. UPDATE Here's the code to illustrate the partition approach. SELECT ID, [Trans Type], [Date], [Qty], [Total], [Item Number], [Work Order] FROM ( SELECT ID, [Trans Type], [Date], [Qty], [Total], [Item Number], [Work Order], ROW_NUMBER() OVER (PARTITION BY ID, [Trans Type], [Date], [Qty], [Total] ORDER BY [Item Number]) AS ItemRank FROM YourTable ) AS SubQuery WHERE ItemRank = 1