Query matching stock trading buyers/sellers based on conditions - sql

I'm trying to create a stored procedure that matches buyers and sellers for a fake stock market program. I eventually I'd like to use this to populate a transaction table to finalize peoples orders. This would also be in a SQL Server Agent Job. Here's the table I have:
Id UserId Type QtyRemaining Price CreatedOn
1 3 BUY 50 1.00 2012-09-09 05:25:48.4470000
2 6 BUY 50 1.00 2012-09-09 19:25:34.4300000
3 5 SELL 30 1.00 2012-09-09 19:22:59.5900000
4 3 SELL 50 0.90 2012-09-09 06:39:34.9100000
5 2 SELLALL 50 1.00 2012-09-09 04:10:01.8400000
There are several conditions that need to be satisfied to make these matches:
A buyer must look for a seller that has >= amount of shares that the buyer wants if its a 'SELL' order. If its a 'SELLALL' order then the Qty must be equal for the buyer to buy the stock. E.g. The seller must be selling 50 shares and the buyer MUST be buying 50 shares at the same price or lower.
A buyer wants the minimum price for the stock.
If there are multiple sellers with the conditions above a buyer takes the oldest selling stock first after the minimum price.
So the pairs of traders would be #1 and #4, #2 and #5. Therefore #3 would still be pending.
Here's the code I was playing around with but I can't get it to match up properly with the minimum price and the oldest first:
select o.*, oa.*, r.* from [Order] o
join OrderActivity oa on o.Id = oa.OrderId
join (
select o2.Id, o2.VideoId, o2.UserId, oa2.Price, oa2.QtyRemaining, o2.[Type] from [Order] o2
join OrderActivity oa2 on o2.Id = oa2.OrderId
where o2.Type = 'buy' and oa2.Status = 'open'
) as r on (o.VideoId = r.VideoId and oa.Price <= r.Price and r.QtyRemaining = oa.QtyRemaining and o.UserId != r.UserId)
where (o.Type = 'sell' or o.Type = 'sellall') and oa.Status = 'open'

try this
Briefly ,
1.Rank buyer based on the createddate(named as stocks in the below query) 2.Rank seller based on the price,createddate(named as lowNoldstock)3.Get the matching rank
select stocks.*,lowNoldStock.*
from (select *,row_number() over(order by createdon) as buyerrank
from stocktable(nolock) where c='buy' ) stocks
inner join
(select *,row_number() over(order by price,createdon) as sellerrank
from stocktable(nolock) where [type]='sell' or [type]='sellall' ) lowNoldstock
on (stocks.qty<=lowNoldStock.qty and lowNoldStock.type='sell')
or (lowNoldStock.type='sellall' and stocks.qty=lowNoldStock.qty and stocks.price>=lowNoldStock.price)
where lowNoldStock.sellerrank=stocks.buyerrank
test script in sql fiddle ,for some reason it is showing partial result in sql fiddle
This works in my local database

You are welcome to play with this:
declare #Transactions as Table
( Id Int, UserId Int, Type VarChar(8), QtyRemaining Int, Price Money, CreatedOn DateTime )
insert into #Transactions ( Id, UserId, Type, QtyRemaining, Price, CreatedOn ) values
( 1, 3, 'BUY', 50, 1.00, '2012-09-09 05:25:48.447' ),
( 2, 6, 'BUY', 50, 1.00, '2012-09-09 19:25:34.430' ),
( 3, 5, 'SELL', 30, 1.00, '2012-09-09 19:22:59.590' ),
( 4, 3, 'SELL', 50, 0.90, '2012-09-09 06:39:34.910' ),
( 5, 2, 'SELLALL', 50, 1.00, '2012-09-09 04:10:01.840' )
-- Split the transactions into temporary working tables.
declare #Buyers as Table
( Id Int, UserId Int, Type VarChar(8), QtyRemaining Int, Price Money, CreatedOn DateTime )
declare #Sellers as Table
( Id Int, UserId Int, Type VarChar(8), QtyRemaining Int, Price Money, CreatedOn DateTime )
insert into #Buyers
select Id, UserId, Type, QtyRemaining, Price, CreatedOn
from #Transactions
where Type = 'BUY'
insert into #Sellers
select Id, UserId, Type, QtyRemaining, Price, CreatedOn
from #Transactions
where Type like 'SELL%'
-- Process the buy orders in the order in which they were created.
declare #BuyerId as Int = ( select top 1 Id from #Buyers order by CreatedOn )
declare #SellerId as Int
declare #Balance as Int
while #BuyerId is not NULL
begin
-- Pair a seller, if possible, with the current buyer.
; with Willard as (
select Row_Number() over ( order by S.Price, S.CreatedOn ) as Priority,
S.Id as S_Id, S.QtyRemaining as S_QtyRemaining,
B.QtyRemaining as B_QtyRemaining
from #Sellers as S inner join
#Buyers as B on B.Id = #BuyerId and
case
when S.Type = 'SELL' and B.QtyRemaining <= S.QtyRemaining then 1
when S.Type = 'SELLALL' and B.QtyRemaining = S.QtyRemaining then 1
else 0
end = 1
)
select #SellerId = S_Id, #Balance = S_QtyRemaining - B_QtyRemaining
from Willard
where Priority = 1
-- Display the result.
select #BuyerId as BuyerId, #SellerId as SellerId, #Balance as RemainingShares
-- Update the working tables.
if #Balance = 0
delete from #Sellers
where Id = #SellerId
else
update #Sellers
set QtyRemaining = #Balance
where Id = #SellerId
delete from #Buyers
where Id = #BuyerId
-- Find the next buy order.
set #BuyerId = ( select top 1 Id from #Buyers order by CreatedOn )
end
-- Display any unfilled orders.
select 'Unmatched Buy', *
from #Buyers
select 'Unmatched Sell', *
from #Sellers

Related

Get Orders status that can be delivered with reducing quantity from stock without curson or While loop

Just like any retail business we have an Orders table and an Inventory table. What I am trying to do is to check Orders for which we have enough stock available to dispatch. A few things I need to consider:
If all the items in an order are available only then consider this order to be “Deliverable”
Check Order's deliverable status in the order of OrderID (int value) .i.e OrderID = 1 then 2 and so on.
Before checking for deliverability of next order, reduce the available of stock for the next order (not update the Inventory table but just take into account the stock quantity that has been already consumed by previous orders).
If we do not have enough stock for 1 or more items in the order, completely ignore the order and do not reduce available stock quantity for the next order to be checked.
In the following example:
Order = 100 is fully deliverable because we have enough stock for all products.
Order = 200 is not fully deliverable because PID 2 requires Qty 5 but we only have 3 left after 2 being consumed by the Order 100
Finally, Order = 300 is also fully deliverable because we have enough stock for all products.
Test data
INSERT INTO #Inventory (PID, Qty)
VALUES (1 , 10)
, (2 , 5)
, (3 , 2)
INSERT INTO #Order (OrderID, PID, Qty)
VALUES (100 , 1 , 2) --\
, (100 , 2 , 2) ----> This order is fully available
, (100 , 3 , 1) --/
, (200 , 1 , 2) --\
, (200 , 2 , 5) ----> This order is not fully available
, (200 , 3 , 1) --/ because of PID 2 only 3 QTY left
, (300 , 1 , 2) --\
, (300 , 2 , 2) ----> This order is fully available
, (300 , 3 , 1); --/
Expected output:
OrderID Status
------------------------
100 Deliverable
200 NOT Deliverable
300 Deliverable
My attempt: I know that it is far from the actual solution but I still wanted to share what I have been trying :)
WITH OrderCTE AS
(
SELECT
DENSE_RANK() OVER (ORDER BY OrderID) AS OrderRN
, OrderID
, PID
, Qty
FROM
#Order
)
, CTE AS
(
SELECT
o.OrderID
, o.PID
, o.Qty
, i.Qty - o.Qty AS QtyAvailable
, o.OrderRN AS OrderRN
FROM
OrderCTE o
INNER JOIN
#Inventory i ON i.PID = o.PID
WHERE
o.OrderID IN (SELECT TOP 1 o.OrderID
FROM #Order o
WHERE NOT EXISTS (SELECT 1 FROM #Inventory i
WHERE i.PID = o.PID AND i.Qty < o.Qty)
ORDER BY o.OrderID)
UNION ALL
SELECT
o.OrderID
, o.PID
, o.Qty
, o.Qty - c.QtyAvailable
, c.OrderRN + 1
FROM
OrderCTE o
INNER JOIN
#Inventory i ON i.PID = o.PID
INNER JOIN
CTE c ON c.OrderRN + 1 = o.OrderRN AND c.PID = o.PID
WHERE
o.Qty <= c.QtyAvailable
)
SELECT *
FROM CTE
The method below doesn't produce correct results. When I put all pieces together I got:
+---------+--------------------+
| OrderID | OrderIsDeliverable |
+---------+--------------------+
| 100 | 1 |
| 200 | 0 |
| 300 | 0 |
+---------+--------------------+
Order=300 was marked as non deliverable, because my query processes all Products independently and this is not correct. The previous Order=200 hogged the quantity for PID=3, even though this Order=200 was not deliverable overall (based on Products other than PID=3) and it should not affect the following orders. But it did affect the following orders, which is not correct.
I don't see how to write a single query without explicit loop(s) here.
Alas.
You can simulate a loop using recursive CTE.
I will show you a query that does the core thing and leave the rest to you, because overall it becomes too long.
The main idea - you need a running total that resets when it reaches a threshold. There are many questions on this topic, I used this as a basis for my answer.
In the query below I'm looking only at a slice of your data, only at one specific PID = 2.
CTE_RN gives us row numbers to iterate upon. CTE_Recursive is the main loop which checks if the running total exceeds the limit. If it does, it discards the Qty from that Order and sets the OrderIsDeliverable flag.
Query
WITH
CTE_RN
AS
(
SELECT
O.OrderID
,O.PID
,O.Qty
,I.Qty AS LimitQty
,ROW_NUMBER() OVER (ORDER BY O.OrderID) AS rn
FROM
#Order AS O
INNER JOIN #Inventory AS I ON I.PID = O.PID
WHERE O.PID = 2 -- this would become a parameter
)
,CTE_Recursive
AS
(
SELECT
CTE_RN.OrderID
,CTE_RN.PID
,CTE_RN.Qty
,CTE_RN.LimitQty
,CTE_RN.rn
-- this would generate a simple running total
--,CTE_RN.Qty AS SumQty
-- the very first order may exceed the limit
,CASE WHEN CTE_RN.Qty > CTE_RN.LimitQty
THEN 0
ELSE CTE_RN.Qty
END AS SumQty
,CASE WHEN CTE_RN.Qty > CTE_RN.LimitQty
THEN 0
ELSE 1
END AS OrderIsDeliverable
FROM
CTE_RN
WHERE
CTE_RN.rn = 1
UNION ALL
SELECT
CTE_RN.OrderID
,CTE_RN.PID
,CTE_RN.Qty
,CTE_RN.LimitQty
,CTE_RN.rn
-- this would generate a simple running total
--,CTE_RN.Qty + CTE_Recursive.SumQty AS SumQty
-- check if running total exceeds the limit
,CASE WHEN CTE_RN.Qty + CTE_Recursive.SumQty > CTE_RN.LimitQty
THEN CTE_Recursive.SumQty -- don't increase the running total
ELSE CTE_RN.Qty + CTE_Recursive.SumQty
END AS SumQty
,CASE WHEN CTE_RN.Qty + CTE_Recursive.SumQty > CTE_RN.LimitQty
THEN 0
ELSE 1
END AS OrderIsDeliverable
FROM
CTE_RN
INNER JOIN CTE_Recursive ON CTE_Recursive.rn + 1 = CTE_RN.rn
)
SELECT * FROM CTE_Recursive
;
Result
+---------+-----+-----+----------+----+--------+--------------------+
| OrderID | PID | Qty | LimitQty | rn | SumQty | OrderIsDeliverable |
+---------+-----+-----+----------+----+--------+--------------------+
| 100 | 2 | 2 | 5 | 1 | 2 | 1 |
| 200 | 2 | 5 | 5 | 2 | 2 | 0 |
| 300 | 2 | 2 | 5 | 3 | 4 | 1 |
+---------+-----+-----+----------+----+--------+--------------------+
Now you need to run this query for each PID. I would wrap this query into a table-valued function with parameter and pass PID as parameter. Maybe you can do it without a function as well. Obviously, to create a function you can't have table variables, you need actual tables to reference in your function, so adjust the code accordingly.
Then call it something like this:
SELECT
...
FROM
#Inventory AS I
CROSS APPLY dbo.MyFunc(I.PID) AS A
This would return the same number of rows as in the #Order table. Then you need to group this by OrderID and look at the OrderIsDeliverable flag. If this flag is 0 at least once for an Order, this Order is not deliverable.
Something like this:
SELECT
A.OrderID
,MIN(OrderIsDeliverable) AS OrderIsDeliverable
FROM
#Inventory AS I
CROSS APPLY dbo.MyFunc(I.PID) AS A
GROUP BY
A.OrderID
;
Ideally, you should try various approaches (cursor, recursive CTE, etc.), make sure that you have appropriate indexes, measure their performance on your real data and hardware and decide which one to use.
EDIT:
Because I'm ambitious, I've now also found a solution with CTE. Please give my feedback if you find any bug or incorrect results. My old cursor solution is below.
New code with CTE:
DECLARE #OrderQty TABLE
(OrderID INT NOT NULL,
PID INT NOT NULL,
CountOfOrder INT NOT NULL,
StockQty INT NOT NULL,
Qty INT NOT NULL,
DeliverableOrderQty INT NOT NULL,
PRIMARY KEY CLUSTERED(OrderID,PID))
INSERT INTO #OrderQty
(OrderID, PID, CountOfOrder, StockQty, Qty, DeliverableOrderQty)
SELECT o.OrderID,
o.PID,
foo.CountOfOrder,
foo.StockQty,
o.Qty,
foo.StockQty / IIF(o.Qty = 0,1,o.Qty) AS DeliverableOrderQty
FROM #Order AS o
INNER JOIN (SELECT o.PID,
COUNT(DISTINCT o.OrderID) AS CountOfOrder,
i.Qty AS StockQty,
SUM(o.Qty) AS TotalOrderOty
FROM #Order AS o
INNER JOIN #Inventory AS i ON o.PID = i.PID
GROUP BY o.PID,
i.Qty) AS foo ON o.PID = foo.PID
DECLARE #OrdersDeliverableQty TABLE
(OrderID INT NOT NULL PRIMARY KEY,
CountOfOrder INT NOT NULL,
DeliverableQty INT NOT NULL)
INSERT INTO #OrdersDeliverableQty
(OrderID, CountOfOrder, DeliverableQty)
SELECT oq.OrderID,
oq.CountOfOrder,
MIN(oq.DeliverableOrderQty) AS DeliverableQty
FROM #OrderQty AS oq
GROUP BY oq.OrderID,
oq.CountOfOrder
DECLARE #AllOrders TABLE
(OrderID INT NOT NULL PRIMARY KEY)
INSERT INTO #AllOrders
(OrderID)
SELECT o.OrderID
FROM #Order AS o
GROUP BY o.OrderID
DECLARE #DeliverableOrder TABLE
(OrderID INT NOT NULL PRIMARY KEY);
WITH CTE_1(RankID, OrderID, PID, StockQty, Qty)
AS (SELECT RANK() OVER(
ORDER BY oq.PID,
oq.DeliverableOrderQty DESC,
oq.Qty,
oq.OrderID) AS RankID,
oq.OrderID,
oq.PID,
oq.StockQty,
oq.Qty
FROM #OrderQty AS oq
INNER JOIN #OrdersDeliverableQty AS ohmttoq ON oq.OrderID = ohmttoq.OrderID
AND oq.DeliverableOrderQty = ohmttoq.DeliverableQty),
CTE_2(MinRankID, MaxRankID)
AS (SELECT MIN(c.RankID) AS MinRankID,
MAX(c.RankID) AS MaxRankID
FROM CTE_1 AS c),
CTE_3(NextRankID, MaxRankID, RankID, OrderID, PID, StockQty, RestQty, Qty)
AS (SELECT c2.MinRankID + 1 AS NextRankID,
c2.MaxRankID AS MaxRankID,
c.RankID,
c.OrderID,
c.PID,
c.StockQty,
c.StockQty - c.Qty AS RestQty,
c.Qty
FROM CTE_1 AS c
INNER JOIN CTE_2 AS c2 ON c.RankID = c2.MinRankID
UNION ALL
SELECT c3.NextRankID + 1 AS NextRankID,
c3.MaxRankID,
c3.NextRankID,
c1.OrderID,
c1.PID,
c1.StockQty,
CASE
WHEN c3.PID = C1.PID
THEN c3.RestQty
ELSE c1.StockQty
END - c1.Qty AS RestQty,
c1.Qty
FROM CTE_3 AS c3
INNER JOIN CTE_1 AS c1 ON c3.NextRankID = c1.RankID
WHERE c3.NextRankID <= c3.MaxRankID)
INSERT INTO #DeliverableOrder
(OrderID)
SELECT c.OrderID
FROM CTE_3 AS c
WHERE c.RestQty >= 0
SELECT ao.OrderID,
CASE
WHEN oo.OrderID IS NULL
THEN 'NOT Deliverable'
ELSE 'Deliverable'
END AS STATUS
FROM #AllOrders AS ao
LEFT JOIN #DeliverableOrder AS oo ON ao.OrderID = oo.OrderID
Test Data:
DECLARE #Inventory TABLE
(PID INT NOT NULL PRIMARY KEY,
Qty INT NOT NULL)
DECLARE #Order TABLE
(OrderID INT NOT NULL,
PID INT NOT NULL,
Qty INT NOT NULL,
PRIMARY KEY CLUSTERED(OrderID,PID))
INSERT INTO #Inventory
(PID, Qty)
VALUES (1,10),
(2,6),
(3,5)
INSERT INTO #Order
(OrderID, PID, Qty)
VALUES (100,1,2), (100,2,2), (100,3,2),
(200,1,2), (200,2,5), (200,3,1),
(300,1,2), (300,2,2), (300,3,0),
(400,1,2), (400,2,1), (400,3,2),
(500,1,5), (500,2,5), (500,3,5),
(600,1,1), (600,2,1), (600,3,1),
(700,1,0), (700,2,1), (700,3,1)
Result:
OrderID Status
100 Deliverable
200 NOT Deliverable
300 Deliverable
400 NOT Deliverable
500 NOT Deliverable
600 Deliverable
700 Deliverable
If you need more information or explanations, leave a comment.
Old code with cursor:
DECLARE #OrderQty TABLE
(OrderID INT NOT NULL,
PID INT NOT NULL,
CountOfOrder INT NOT NULL,
StockQty INT NOT NULL,
Qty INT NOT NULL,
DeliverableOrderQty INT NOT NULL,
PRIMARY KEY CLUSTERED(OrderID,PID))
INSERT INTO #OrderQty
(OrderID, PID, CountOfOrder, StockQty, Qty, DeliverableOrderQty)
SELECT o.OrderID,
o.PID,
foo.CountOfOrder,
foo.StockQty,
o.Qty,
foo.StockQty / IIF(o.Qty = 0,1,o.Qty) AS DeliverableOrderQty
FROM #Order AS o
INNER JOIN (SELECT o.PID,
COUNT(DISTINCT o.OrderID) AS CountOfOrder,
i.Qty AS StockQty,
SUM(o.Qty) AS TotalOrderOty
FROM #Order AS o
INNER JOIN #Inventory AS i ON o.PID = i.PID
GROUP BY o.PID,
i.Qty) AS foo ON o.PID = foo.PID
DECLARE #OrdersDeliverableQty TABLE
(OrderID INT NOT NULL PRIMARY KEY,
CountOfOrder INT NOT NULL,
DeliverableQty INT NOT NULL)
INSERT INTO #OrdersDeliverableQty
(OrderID, CountOfOrder, DeliverableQty)
SELECT oq.OrderID,
oq.CountOfOrder,
MIN(oq.DeliverableOrderQty) AS DeliverableQty
FROM #OrderQty AS oq
GROUP BY oq.OrderID,
oq.CountOfOrder
DECLARE #AllOrders TABLE
(OrderID INT NOT NULL PRIMARY KEY)
INSERT INTO #AllOrders
(OrderID)
SELECT o.OrderID
FROM #Order AS o
GROUP BY o.OrderID
DECLARE #DeliverableOrder TABLE
(OrderID INT NOT NULL PRIMARY KEY)
DECLARE #OrderID INT,
#PID INT,
#StockQty INT,
#Qty INT
DECLARE #LastPIDCursor INT
DECLARE #QtyRest INT
DECLARE order_qty_cursor CURSOR
FOR SELECT oq.OrderID,
oq.PID,
oq.StockQty,
oq.Qty
FROM #OrderQty AS oq
INNER JOIN #OrdersDeliverableQty AS ohmttoq ON oq.OrderID = ohmttoq.OrderID
AND oq.DeliverableOrderQty = ohmttoq.DeliverableQty
ORDER BY oq.PID,
oq.DeliverableOrderQty DESC,
oq.Qty
OPEN order_qty_cursor
FETCH NEXT FROM order_qty_cursor INTO #OrderID,
#PID,
#StockQty,
#Qty
WHILE ##Fetch_Status = 0
BEGIN
IF #LastPIDCursor IS NULL
OR #LastPIDCursor <> #PID
BEGIN
SET #QtyRest = #StockQty - #Qty
END
ELSE
BEGIN
SET #QtyRest = #QtyRest - #Qty
END
IF #QtyRest >= 0
AND NOT EXISTS (SELECT 1
FROM #DeliverableOrder
WHERE OrderID = #OrderID)
BEGIN
INSERT INTO #DeliverableOrder
(OrderID)
VALUES
(#OrderID)
END
SET #LastPIDCursor = #PID
FETCH NEXT FROM order_qty_cursor INTO #OrderID,
#PID,
#StockQty,
#Qty
END
CLOSE order_qty_cursor
DEALLOCATE order_qty_cursor
SELECT ao.OrderID,
CASE
WHEN oo.OrderID IS NULL
THEN 'NOT Deliverable'
ELSE 'Deliverable'
END AS STATUS
FROM #AllOrders AS ao
LEFT JOIN #DeliverableOrder AS oo ON ao.OrderID = oo.OrderID

MS SQL How to avoid loops when allocating amounts

I'm building a system that needs to automatically allocate amounts to a Charge_Detail table.
I have the following tables:
-- a list of charges that are outstanding
DECLARE Charge_Master TABLE
( ID INT NOT NULL,
CompanyID VARCHAR(6) NOT NULL,
EntryDate SMALLDATETIME,
Ref VARCHAR(30) NOT NULL,
Amount DECIMAL(12,2) NOT NULL
)
-- directly linked to master table to represent charges paid in detail
DECLARE Charge_Detail TABLE
( ID INT NOT NULL,
Charge_MasterID INT NOT NULL, --Foreign Key
EntryDate SMALLDATETIME,
Ref VARCHAR(30) NOT NULL,
Amount DECIMAL(12,2) NOT NULL
)
INSERT Charge_Master
VALUES ('ABC123', '01/01/2018', 'INV-111', 25),
('ABC123', '21/03/2018', 'INV-222', 30),
('ABC123', '11/05/2018', 'INV-333', 15)
The objective is to have a query take the following parameters:
CompanyId e.g. 'ABC123'
Amount e.g. 45
Ref e.g. 'REF-142'
Based on the parameters it should work out the records that need to be INSERTED into the Charge_Detail table and the associated Charge_MasterID.
Example:
If the total amount to allocate is 45 against CompanyId: ABC123
then this is the Expected output inserted into the Charge_Detail table.
/* Charge_Master Table
*
* ID CompanyID EntryDate Ref Amount
* 1 ABC123 01/01/2018 INV-111 25
* 2 ABC123 21/03/2018 INV-222 30
* 3 ABC123 11/05/2018 INV-333 15
*/
/* Charge_Detail Table
*
* ID Charge_MasterID EntryDate Ref Amount
* 1 1 12/08/2018 REF-142 25
* 2 2 12/08/2018 REF-142 20 -- cannot fully allocate therefore 10 still remaining to be allocated for next time
*/
What's the best approach with this? Would a CTE help? I'm not too familiar with them but I have tried a SELECT query with subqueries and case statement but I can't get it to decrement the amount remaining without using a loop.
Any advice would be much appreciated!
It should be something like this:
DECLARE #CompanyID VARCHAR(6) = 'ABC123',
#Ref VARCHAR(30) = 'REF-142',
#Amount DECIMAL(12,2) = 45
;WITH cte as (
SELECT *, SUM (Amount) OVER (ORDER BY Id) AS RunningAmount
FROM Charge_Master
), Limits as (
SELECT TopRow = (SELECT MIN(ID) FROM CTE WHERE RunningAmount > #Amount),
LastRow = (SELECT MAX(ID) FROM CTE WHERE RunningAmount < #Amount))
SELECT c.ID, c.EntryDate, #Ref, c.Amount
FROM Limits as l INNER JOIN cte as c ON c.ID < l.TopRow
UNION ALL
SELECT c2.ID, c2.EntryDate, #Ref, #Amount-c1.RunningAmount
FROM Limits as l
INNER JOIN cte as c1 ON c1.ID = l.LastRow
INNER JOIN cte as c2 ON c2.ID = l.TopRow
ORDER BY 1

Retrieve specific value if set, or general if not

I have to write a query which will import some data from table. Table structure is like:
Item_ID
Plant
Price
I have second table with
Item_ID
Plant
Second table is a key, and I have to match rows from first table to get valid price. Seems to be easy, however:
In first table column plant might determinate specific plant, or have value 'ALL'. What I want to do is retrieve price for given plant, if it is set, or get price for value 'ALL' if there is no row for given plant. In other words:
If first.Plant = second.Plant
return price
Else If first.Plant = 'ALL'
return price
Else
return NULL
I can't use simple ON first.Plant = second.Plant OR first.Plant = 'ALL', because there might be two rows: one for given plant and second for rest with value 'ALL'. I need to return only first price in that case. E.g.
Item_ID | Plant | Price
2 | M1 | 10,0
2 | All | 12,0
1 | All | 9,0
In that case for Item_ID = 2 and Plant = M1 the only valid price = 10, but for Item_ID = 2 and Plant = M2 price = 12, and for any Item_ID = 1 price = 9
I hope You understood something after my explanation ;)
Using ROW_Number and a Common Table Expression you can ensure that ROW_NUMBER is partitioned by Item_ID and Plant and ordered such that if there are two rows then 'All' is second. You then simple select the rows with a row number = 1:
Setup
CREATE TABLE #Price
(
Item_ID int,
Plant Varchar(20),
Price Decimal(6,2)
)
CREATE TABLE #Plant
(
Item_ID int,
Plant VARCHAR(20)
)
INSERT INTO #Price
VALUES (2, 'M1', 10.0),
(2, 'All', 12.0),
(1, 'All', 9.0)
INSERT INTO #Plant
VALUES (2, 'M1'),
(2, 'M2'),
(1, 'M1'),
(1, 'M2')
Here's the query for SQL Server >= 2012
;WITH CTE
AS
(
SELECT PL.Item_ID, PL.Plant, PR.PRice, ROW_NUMBER() OVER (PARTITION BY Pl.Item_ID, Pl.Plant ORDER BY IIF(PR.Plant = 'All', 1, 0)) AS RN
FROM #Plant PL
INNER JOIN #Price PR
ON PL.Item_Id = PR.Item_Id AND (PL.Plant = PR.Plant OR PR.Plant = 'ALL')
)
SELECT Item_Id, Plant, Price
FROM CTE
WHERE RN = 1
And here's a version using CASE which will work for all SQL Server >= 2008 R2
;WITH CTE
AS
(
SELECT PL.Item_ID, PL.Plant, PR.PRice, ROW_NUMBER() OVER (PARTITION BY Pl.Item_ID, Pl.Plant ORDER BY CASE WHEN PR.Plant = 'All' Then 1 Else 0 End) AS RN
FROM #Plant PL
INNER JOIN #Price PR
ON PL.Item_Id = PR.Item_Id AND (PL.Plant = PR.Plant OR PR.Plant = 'ALL')
)
SELECT Item_Id, Plant, Price
FROM CTE
WHERE RN = 1

SQL Server query to get the total for each row

I have two tables InvoiceLine and Discount:
I need to get a result set which includes invoice Line items with the total (SUM) of discounts in the discount table like below:
How can I achieve this using only one query?
DECLARE #InvoiceLine TABLE
(
InvoiceHeaderID INT ,
InvoiceLineNo INT ,
ProductCode VARCHAR(5) ,
Price MONEY
);
INSERT INTO #InvoiceLine
VALUES ( 1, 1, 'AB001', 1200 ),
( 2, 1, 'AC002', 1525 );
DECLARE #Discount TABLE
(
InvoiceHeaderID INT ,
InvoiceLineNo INT ,
DiscountCategory VARCHAR(10) ,
discountValue MONEY
);
INSERT INTO #Discount
VALUES ( 1, 1, 'SalesDisc', 120 ),
( 1, 1, 'FixedOffer', 100 ),
( 2, 1, 'SalesDisc', 152.50 );
SELECT l.InvoiceHeaderID ,
l.InvoiceLineNo ,
l.ProductCode ,
l.Price ,
ISNULL(SUM(d.discountValue),0) [TotalDiscount]
FROM #InvoiceLine l
LEFT JOIN #Discount d ON d.InvoiceHeaderID = l.InvoiceHeaderID
AND d.InvoiceLineNo = l.InvoiceLineNo
GROUP BY l.InvoiceHeaderID ,
l.InvoiceLineNo ,
l.ProductCode ,
l.Price;
Result
SELECT
A.InvoiceHeaderID, A.InvoiceLineNo, ProductCode, Price, SUM(DiscountValue) as TotDiscount
FROM
InvoiceLine A, Discount B
WHERE
A.InvoiceHeaderID = B.InvoiceHeaderID
AND A.InvoiceLineNo = B.InvoiceLineNo
GROUP BY
A.InvoiceHeaderID, A.InvoiceLineNo, ProductCode, Price
Order BY
A.InvoiceHeaderID, A.InvoiceLineNo
select
i.InvoiceHeaderId,
i.InvoiceLineNo,
i.ProductCode,
i.Price,
[TotDiscount]=isnull(d.TotDiscount)
from
[InvoiceLine] i
left join
[Discount] d on i.InvoiceHeaderId=d.InvoiceHeaderId and i.InvoiceLineNo=d.InvoiceLineNo
group by
i.InvoiceHeaderId,
i.InvoiceLineNo,
i.ProductCode,
i.Price
SELECT
InvoiceLine .InvoiceHeaderID, InvoiceLine .InvoiceLineNo, ProductCode, Price, SUM(DiscountValue) as TotDiscount
FROM
InvoiceLine, Discount
WHERE
InvoiceLine .InvoiceHeaderID = Discount .InvoiceHeaderID AND A.InvoiceLineNo = Discount .InvoiceLineNo
GROUP BY InvoiceLine .InvoiceHeaderID, InvoiceLine .InvoiceLineNo, ProductCode, Price
Order BY InvoiceLine .InvoiceHeaderID, InvoiceLine .InvoiceLineNo

Database Join Query

I am having a problem with a database join query and I am looking for someone to help me out.
Basically I've got two tables, Invoices and Receipts.
Invoices
Invoice ID
Amount
Date_Added
Receipts
ReceiptID
InvoiceID
Amount
Date_Added
The thing is that I need to produce a table like below, but I have multiple records in Receipts and I am pretty sure that the data is stored in a good way, just not exactly sure what the query would be.
InvoiceID RecieptID Amount Balance Date_Added
1 0 100.00 100.00 01.05.2012
1 1 100.00 0.00 02.05.2012
2 0 250.00 250.00 03.05.2012
3 0 100.00 350.00 04.05.2012
2 2 100.00 250.00 05.05.2012
Does this make sense? So it should be in date order. So effectively I can see line by line what is going on each date.
Setup:
USE tempdb;
GO
CREATE TABLE dbo.Invoices
(
InvoiceID INT,
Amount DECIMAL(12,2),
DateAdded SMALLDATETIME
);
CREATE TABLE dbo.Receipts
(
ReceiptID INT,
InvoiceID INT,
Amount DECIMAL(12,2),
DateAdded SMALLDATETIME
);
SET NOCOUNT ON;
INSERT dbo.Invoices SELECT 1, 100, '20120501'
UNION ALL SELECT 2, 250, '20120503'
UNION ALL SELECT 3, 100, '20120504';
INSERT dbo.Receipts SELECT 1, 1, 100, '20120502'
UNION ALL SELECT 2, 2, 100, '20120505';
Query:
;WITH x AS
(
SELECT InvoiceID, ReceiptID, Amount, DateAdded,
rn = ROW_NUMBER() OVER (ORDER BY DateAdded)
FROM
(
SELECT InvoiceID, ReceiptID = 0, Amount, DateAdded
FROM dbo.Invoices -- where clause?
UNION ALL
SELECT InvoiceID, ReceiptID, Amount, DateAdded
FROM dbo.Receipts -- where clause?
) AS y
),
z AS
(
SELECT xrn = x.rn, x.InvoiceID, x.ReceiptID, x.Amount, x.DateAdded,
PlusMinus = CASE WHEN x.ReceiptID > 0 THEN -x.Amount ELSE x.Amount END
FROM x LEFT OUTER JOIN x AS x2
ON x.rn = x2.rn + 1
)
SELECT InvoiceID, ReceiptID, Balance = (
SELECT SUM(COALESCE(PlusMinus, Amount))
FROM z AS z2
WHERE z2.xrn <= z.xrn
), Amount, DateAdded
FROM z
ORDER BY DateAdded;
Cleanup:
DROP TABLE dbo.Invoices, dbo.Receipts;
you can make a view that select data from the 2 tables like if the DB is oracle :
CREATE VIEW INVOICE_RECPT_VIEW as
select a.InvoiceID, b.RecieptID, b.Amount, b.Date_Added
from invoices a, receipts b
where a.InvoiceID = b.InvoiceID
order by Date_Added