SQL Server Stored Procedure for Menu Performance Report - sql

I have four tables in my SQL database i.e MenuItems, Categories, Invoices and InvoiceDetails. Now what I want is to show the menu performance report for a certain date i.e total Qty and total Amount for
each menu item for a specific date. It shows the desired result without the date in the where clause but excludes menu items with null values.
Here is my stored procedure:
CREATE PROCEDURE spGetMenuPerformanceByDay
#Date date,
#Terminal int
AS
BEGIN
SELECT
M.Name,
ISNULL(SUM(D.Amount), 0) AS Amount,
ISNULL(SUM(D.Qty), 0) AS Qty
FROM
MenuItems AS M
JOIN
Categories AS C ON C.Id = M.CategoryId
LEFT JOIN
InvoiceDetails AS D ON M.Id = D.ItemId
LEFT JOIN
Invoices I ON I.Id = d.InvoiceId
WHERE
#Terminal IN (I.TerminalId, C.TerminalId)
AND CONVERT(date, I.Time) = #Date
OR NULL IN (Amount, Qty)
GROUP BY
M.Name, M.Id, D.ItemId
ORDER BY
(Qty) DESC
END
The result this stored procedure returns on adding Date in where clause:
Item
Amount
Qty
KOFTA ANDA
1950
3
HOT N SOUR SOUP
550
1
CHICKEN CHOWMEIN
250
1
CHICKEN KORMA
850
1
And the result I want is but don't get it on adding Date in where clause :
Item
Amount
Qty
KOFTA ANDA
1950
3
HOT N SOUR SOUP
550
1
CHICKEN CHOWMEIN
250
1
CHICKEN KORMA
850
1
CRISPY CHICKEN
0
0
MEXICAN BURGER
0
0

What if you don't put criteria for Invoices in the WHERE clause?
Sample data
create table Categories (
Id int primary key,
Name varchar(30) not null,
TerminalId int not null
);
create table MenuItems (
Id int identity(21,1) primary key,
Name varchar(30) not null,
CategoryId int not null,
foreign key (CategoryId) references Categories(Id)
);
create table Invoices (
Id int identity(31,1) primary key,
TerminalId int not null,
ItemId int not null,
Time datetime,
foreign key (ItemId) references MenuItems(Id)
);
create table InvoiceDetails (
InvoiceDetailId int identity(41,1) primary key,
InvoiceId int,
Amount decimal(10,2),
Qty int,
foreign key (InvoiceId) references Invoices(Id)
);
insert into Categories (Id, Name, TerminalId) values
(1,'KOFTA', 1),
(2,'SOUP', 1),
(3,'CHICKEN', 1),
(4,'BURGER', 1);
insert into MenuItems (CategoryId, Name) values
(1,'KOFTA ANDA'),
(2,'HOT N SOUR SOUP'),
(3,'CHICKEN CHOWMEIN'),
(3,'CHICKEN KORMA'),
(3,'CRISPY CHICKEN'),
(4,'MEXICAN BURGER');
insert into Invoices (ItemId, TerminalId, Time)
select itm.Id, cat.TerminalId, GetDate() as Time
from MenuItems itm
join Categories cat on cat.Id = itm.CategoryId
where itm.Name in (
'KOFTA ANDA',
'HOT N SOUR SOUP',
'CHICKEN CHOWMEIN',
'CHICKEN KORMA'
);
insert into InvoiceDetails (InvoiceId, Amount, Qty) values
(31, 1950, 3),
(32, 550, 1),
(33, 250, 1),
(34, 850, 1);
Query
DECLARE #TerminalId INT = 1;
DECLARE #Date DATE = GetDate();
SELECT
V.[Date],
C.Name AS Category,
M.Name AS MenuItemName,
ISNULL(SUM(D.Amount), 0) AS Amount,
ISNULL(SUM(D.Qty), 0) AS Qty
FROM Categories AS C
CROSS JOIN (SELECT #Date AS [Date], #TerminalId AS TerminalId) V
JOIN MenuItems AS M
ON M.CategoryId = C.Id
LEFT JOIN Invoices I
ON I.ItemId = M.Id
AND I.TerminalId = V.TerminalId
AND CAST(I.Time AS DATE) = V.[Date]
LEFT JOIN InvoiceDetails AS D
ON D.InvoiceId = I.Id
WHERE C.TerminalId = V.TerminalId
GROUP BY V.[Date], C.Id, M.Id, C.Name, M.Name
ORDER BY SUM(D.Qty) DESC
Date
Category
MenuItemName
Amount
Qty
2021-12-18
KOFTA
KOFTA ANDA
1950.00
3
2021-12-18
SOUP
HOT N SOUR SOUP
550.00
1
2021-12-18
CHICKEN
CHICKEN CHOWMEIN
250.00
1
2021-12-18
CHICKEN
CHICKEN KORMA
850.00
1
2021-12-18
CHICKEN
CRISPY CHICKEN
0.00
0
2021-12-18
BURGER
MEXICAN BURGER
0.00
0
Demo on db<>fiddle here

Here's my crack at your goal. Notice the changes. I found the reference to TerminalId in Category table highly suspicious - so much that I suspect it is a model flaw. Along those lines I note that TerminalId should likely have a foreign key to a missing table for Terminals. So I ignore that.
With that out, references to Category are now irrelevant. So that was removed as well. I also changed the procedure name since I find the reference to "day" misleading. It is highly likely "menu performance" would be evaluated on a "day" basis since retail (especially food service) sales vary by day of week consistently. So let's not mislead anyone thinking that is what this procedure does.
For simplicity and clarity, I removed the ISNULL usage. Add it back if desired but such things are usually better handled by the consumer of the resultset. I left the ORDER BY clause as a stub for you to re-evaluate (and you need to).
So how does this work? Simply calculate the sums directly in the CTE and then outer join from the menu items to the CTE sums to get all menu items along with the relevant performance information for the date specified.
CREATE PROCEDURE dbo.GetMenuPerformanceByDate
#Date date,
#Terminal int
AS
BEGIN
with sales as (
select det.ItemId, SUM(det.Amount) as amt, SUM(det.Qty) as qty
from dbo.Invoices as inv
inner join dbo.InvoiceDetails as det
on inv.Id = det.InvoiceId
where cast(inv.Time as date) = #Date
and inv.TerminalId = #Terminal
group by det.ItemId
)
select menu.name, sales.amt, sales.qty
from dbo.MenuItems as menu
left join sales
on menu.Id = sles.ItemId
order by ...
;
END;
One last note. This filter:
cast(inv.Time as date) = #Date
is generally not a good method of filtering a datetime column. Far better to use inclusive lower and exclusive upper boundaries like:
inv.Time >= #date and inv.Time < dateadd(day, 1, #date)
for this reason.
My last note - there is a potential flaw regarding MenuItems. Presumably "name" is unique. It is highly unlikely that multiple rows would have the same name, but "unlikely" is not a restriction. If you generate rows based on name and name turns out to NOT be unique, your results are difficult to interpret.

Related

Need help understanding why this query is producing these results

Consider the database entries below. The distinguishing identifier is the "short" item number, 89721.
Data:
F3003
IRKIT IRKITL IRMMCU IRMCU IREFFF IREFFT IRAN8 IRTRT IRUOM
89721 7N74N050046 B20 NXT 12/06/2015 12/31/2040 200038 M SY
89721 7N74N050046 B70 NXT 07/28/2021 12/31/2040 200038 M SY
F0101
ABAN8 ABALPH ABAC01
200038 Company XYZ CON
F4101
IMITM IMDSC1
89721 TWIN RIB N05 ONYX HS-SY 46.5"
F41021
LIITM LIPBIN LIPQOH
89721 S 256
...
[a total of 99 "S" entries where LIPQOH sums to 16554]
F4211
SDITM SDNXTR SDDCTO SDUORG
89721 540 SO 4700.00
SQL:
SELECT F3003.IRAN8 AS CUST_NO,
F3003.IRKIT AS SHORT_ITEM,
F3003.IRKITL AS ITEM_NO,
F3003.IRUOM AS UOM,
F3003.IRMCU AS WC,
F0101.ABALPH AS CUST_NAME,
F4101.IMDSC1 AS ITEM_DESC,
SUM(F41021.LIPQOH / 100) AS ON_HAND
FROM PROD2DTA.F3003 AS F3003
INNER JOIN PROD2DTA.F0101 AS F0101
ON F3003.IRAN8 = F0101.ABAN8
INNER JOIN PROD2DTA.F4101 AS F4101
ON F3003.IRKIT = F4101.IMITM
INNER JOIN PROD2DTA.F41021 AS F41021
ON F3003.IRKIT = F41021.LIITM
WHERE F3003.IRMCU LIKE '%NXT'
AND F3003.IRTRT = 'M'
AND F0101.ABAC01 = 'CON'
AND F41021.LIPBIN = 'S'
AND CURRENT_DATE BETWEEN DATE(CONCAT(CAST(F3003.IREFFF / 1000 AS INT) + 1900, RIGHT(MOD(F3003.IREFFF, 1000) + 1000, 3))) AND DATE(CONCAT(CAST(F3003.IREFFT / 1000 AS INT) + 1900, RIGHT(MOD(F3003.IREFFT, 1000) + 1000, 3)))
GROUP BY F3003.IRAN8,
F3003.IRKIT,
F3003.IRKITL,
F3003.IRUOM,
F3003.IRMCU,
F3003.IRDSC1,
F0101.ABALPH,
F4101.IMDSC1
HAVING SUM(F41021.LIPQOH / 100) > 0
When I run the query above, I get the following results:
CUST NO CUST NAME ITEM NO ITEM DESC UOM ON HAND
200038 Company XYZ 7N74N050046 TWIN RIB N05 ONYX HS-SY 46.5" SY 16,554.00
This is correct based on comparison with JD Edwards applications.
Note that in the F3003 table above, there are two entries that meet the filtering criteria in the query. I have provided the IRMMCU column to show a source of multiple results, even though I'm not selecting it in the SQL. This represents a plant location where this item may be manufactured. The dates also may contribute to multiple results, because they both meet the filter critera, but are not selected in the SQL.
Now, consider the following query, which uses the one above as a subquery:
SELECT CUST_SPEC_ON_HAND.CUST_NO AS CUST_NO,
CUST_SPEC_ON_HAND.SHORT_ITEM AS SHORT_ITEM,
CUST_SPEC_ON_HAND.ITEM_NO AS ITEM_NO,
CUST_SPEC_ON_HAND.UOM AS UOM,
CUST_SPEC_ON_HAND.WC AS WC,
CUST_SPEC_ON_HAND.CUST_NAME AS CUST_NAME,
CUST_SPEC_ON_HAND.ITEM_DESC AS ITEM_DESC,
CUST_SPEC_ON_HAND.ON_HAND AS ON_HAND,
SUM(F4211.SDUORG / 100) AS OPEN_ORDER
FROM (SELECT F3003.IRAN8 AS CUST_NO,
F3003.IRKIT AS SHORT_ITEM,
F3003.IRKITL AS ITEM_NO,
F3003.IRUOM AS UOM,
F3003.IRMCU AS WC,
F0101.ABALPH AS CUST_NAME,
F4101.IMDSC1 AS ITEM_DESC,
SUM(F41021.LIPQOH / 100) AS ON_HAND
FROM PROD2DTA.F3003 AS F3003
INNER JOIN PROD2DTA.F0101 AS F0101
ON F3003.IRAN8 = F0101.ABAN8
INNER JOIN PROD2DTA.F4101 AS F4101
ON F3003.IRKIT = F4101.IMITM
INNER JOIN PROD2DTA.F41021 AS F41021
ON F3003.IRKIT = F41021.LIITM
WHERE F3003.IRMCU LIKE '%NXT'
AND F3003.IRTRT = 'M'
AND F0101.ABAC01 = 'CON'
AND F41021.LIPBIN = 'S'
AND CURRENT_DATE BETWEEN DATE(CONCAT(CAST(F3003.IREFFF / 1000 AS INT) + 1900, RIGHT(MOD(F3003.IREFFF, 1000) + 1000, 3))) AND DATE(CONCAT(CAST(F3003.IREFFT / 1000 AS INT) + 1900, RIGHT(MOD(F3003.IREFFT, 1000) + 1000, 3)))
GROUP BY F3003.IRAN8,
F3003.IRKIT,
F3003.IRKITL,
F3003.IRUOM,
F3003.IRMCU,
F0101.ABALPH,
F4101.IMDSC1
HAVING SUM(F41021.LIPQOH / 100) > 0) CUST_SPEC_ON_HAND
LEFT JOIN PROD2DTA.F4211 AS F4211
ON CUST_SPEC_ON_HAND.SHORT_ITEM = F4211.SDITM
AND F4211.SDDCTO IN ( 'SO', 'SM', 'S2' )
AND F4211.SDNXTR < 999
GROUP BY CUST_SPEC_ON_HAND.CUST_NO,
CUST_SPEC_ON_HAND.SHORT_ITEM,
CUST_SPEC_ON_HAND.ITEM_NO,
CUST_SPEC_ON_HAND.UOM,
CUST_SPEC_ON_HAND.WC,
CUST_SPEC_ON_HAND.CUST_NAME,
CUST_SPEC_ON_HAND.ITEM_DESC,
CUST_SPEC_ON_HAND.ON_HAND
When I run this query for the same "short" item number 89721, I get the following:
CUST # CUSTOMER NAME ITEM # ITEM DESCRIPTION UOM ON HAND OPEN ORDERS
200038 Company XYZ 7N74N050046 TWIN RIB N05 ONYX HS-SY 46.5" SY 33,108.00 4,700.00
Notice that the on-hand quantity is now twice what it was from the first query.
I can't figure out why this is happening. It does not happen for all data in the result set. From what I can tell, the items with an inflated on-hand quantity are those items in the F3003 table with entries for multiple manufacturing plants (F3003.IRMMCU). But why would it work correctly for the simpler query, and not for the second query that is using the first query?
Below are CREATE TABLE statements to create the tables shown above. Note: The actual JDE tables contain many more columns than what is shown here.
You should also note that JDE stores dates as an integer in a JDE "Julian" date format. The format is as follows: CYYDDD, where C is the century, YY is the year and DDD is the day of the year. For example, April 28, 2022 is represented as 122118.
CREATE TABLE F3003
(
IRTRT NCHAR(3) NULL, -- Type of Routing
IRKIT INT NULL, -- Parent (short) Item Number
IRKITL NCHAR(25) NULL, -- Kit - 2nd Item Number
IRMMCU NCHAR(12) NULL, -- Branch
IRMCU NCHAR(12) NULL, -- Business Unit
IREFFF NUMERIC(6) NULL, -- Effective - From Date
IREFFT NUMERIC(6) NULL, -- Effective - Thru Date
IRUOM NCHAR(2) NULL, -- Unit of Measure as Input
IRAN8 INT NULL, -- Address Number
);
CREATE TABLE F0101
(
ABAN8 INT NULL, -- Address Number
ABALPH NCHAR(40) NULL, -- Name - Alpha
ABAC01 NCHAR(3) NULL, -- Category Code - Address Book 01
);
CREATE TABLE F4101
(
IMITM INT NULL, -- Item Number - Short
IMDSC1 NCHAR(30) NULL, -- Description
);
CREATE TABLE F41021
(
LIITM INT NULL, -- Item Number - Short
LIPBIN NCHAR(1) NULL, -- Primary Location (P/S)
LIPQOH INT NULL, -- Quantity on Hand - Primary units
);
CREATE TABLE F4211
(
SDDCTO NCHAR(2) NULL, -- Order Type
SDITM INT NULL, -- Item Number - Short
SDNXTR NCHAR(3) NULL, -- Status Code - Next
SDUORG INT NULL, -- Units - Order/Transaction Quantity
);
If you really want some information overload, you can find all sorts of information about the JDE data ecosystem by going to http://www.jdetables.com/.
INSERT statements:
Note that the dates are in the JDE Julian date format.
INSERT INTO F3003 (IRKIT,IRKITL,IRMMCU,IRMCU,IREFFF,IREFFT,IRAN8,IRTRT,IRUOM) VALUES (89721,'7N74N050046','B20','NXT',115340,140366,200038,'M','SY');
INSERT INTO F3003 (IRKIT,IRKITL,IRMMCU,IRMCU,IREFFF,IREFFT,IRAN8,IRTRT,IRUOM) VALUES (89721,'7N74N050046','B70','NXT',121209,140366,200038,'M','SY');
INSERT INTO F0101 (ABAN8,ABALPH,ABAC01) VALUES (200038,'Company XYZ','CON');
INSERT INTO F4101 (IMITM,IMDSC1) VALUES (89721,'TWIN RIB N05 ONYX HS-SY 46.5"');
Note that the on-hand quantity (LIPQOH) is stored with 2 places after the decimal point. To get the actual value, it must be divided by 100. Also, I have just one insert statement to replicate the on-hand quantity, rather than inserting 99 lines.
INSERT INTO F41021 (LIITM,LIPBIN,LIPQOH) VALUES (89721,'S',1655400);
Note that the ordered quantity (SDUORG) is stored with 2 places after the decimal point. To get the actual value, it must be divided by 100.
INSERT INTO F4211 (SDITM,SDNXTR,SDDCTO,SDUORG) VALUES (89721,540,'SO',470000);

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

Assign a product name based on the conditions given in SQL Server

I am trying to write a sql query based on the conditions below:
PRODUCT
ORDER
The Product Table has 3 columns based on the following query:
CREATE Product (MasterProduct nvarchar(50), Productkey int, ProductName nvarchar(50))
The Order Table has 3 columns based on the following query:
CREATE Order (MasterProduct nvarchar(50), Orderno int, Productkey int, ParentProduct int)
Scenario:
The 'MasterProduct' column in Product table should be assigned to a particular order in Order Table based on the following conditions:
1) If all the productkeys in product table for a specific Master Product match the order number in Order table
AND
The first ProductKey is a parent to its immediate product key in the Order Table
Example: The Order Number 'S1' is assigned a Kebab as Master Product for Order Table because product keys 1 and 2 from Product Table both exist in S1 and also product key 1 is a parent product key to product keys 2 and 4.
On the other hand, Order Number 'S2' isn't assigned any master product (NULL) because it only has '1' as product key and '2' is not present else it would be assigned a Kebab.
S3 is also assigned a Subway because it has product keys 30 and 31 from product table present for that order number, and product key 30 that is a parent product is assigned to at least one child product that it '31'.
S4 is not assigned any Master Product although it has all product keys for Subway (30,31) because 30 that is a parent product isn't assigned to any child product.
This is a bit convoluted, but here is a working version:
declare #p table(MasterProduct nvarchar(50), Productkey int, ProductName nvarchar(50))
declare #o table(OrderLineNumberKey int, Orderno int, Productkey int, ParentProduct int)
insert #p values('kebab',1,'chicken'),('kebab',2,'mayo'),('subway',30,'bread'),('subway',31,'salad')
insert #o values(1,1,1,null),(2,1,2,1),(3,1,4,1),(4,1,7,null)
insert #o values(1,2,1,null),(2,2,9,null),(3,2,14,1)
insert #o values(1,3,30,null),(2,3,31,30),(3,3,35,null)
insert #o values(1,4,30,null),(2,4,31,null),(3,4,39,null)
;with ParentChild as (
select o.orderno, o.ProductKey as TopProductKey, o.productkey, p.masterproduct
from #o o
join #p p on p.productKey=o.ProductKey
where parentproduct is null
union all
select pc.orderno,pc.ProductKey as TopProductKey,o.productkey,p.masterproduct
from #o o
join #p p on p.productKey=o.ProductKey
join ParentChild pc on pc.orderno=o.orderno and pc.ProductKey=o.ParentProduct
)
select case when HaveAll=1 then ordercheck.masterproduct end as masterproduct,
o.orderno, o.productkey,o.parentproduct
from #o o
left join(
select orderno, masterproduct, max(HaveAll) as HaveAll from (
select pc1.orderno, p.masterproduct, min(case when pc2.productkey is null then 0 else 1 end) as HaveAll
from ParentChild pc1
join #p p on p.masterproduct=pc1.masterproduct
left join ParentChild pc2 on pc2.orderno=pc1.orderno and pc2.MasterProduct=p.MasterProduct and pc2.productkey=p.productkey and pc2.topproductkey=pc1.topproductkey
where pc1.TopProductKey=pc1.ProductKey
group by pc1.orderno, p.masterproduct, pc1.TopProductKey
) q
group by orderno, masterproduct
) ordercheck on ordercheck.orderno=o.orderno
To explain it, firstly in the CTE we look recursively through the orders looking for all possible parents with a null parent, and finding all their child rows. You will see that order 4 has 2 possible candidates which complicates things.
Then in the main query, there is an inner sub query that compares the all the parent+child rows against the product model, and checks that we have all products. It has to do this for each of the candidate parents, and then rolls that up a second time for the overall order. It has to do this in case there were two candidate parents, one of which was satisfied and one wasnt.
Finally we join this to the orders to return the masterproduct if we had all the products, or null if we didnt.

Query matching stock trading buyers/sellers based on conditions

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

SQL Server - need a SQL Query to identify/highlight specific changes in an audit table

Say I have some data stored in an audit table, where triggers on the main data table write all invoice record updates to this audit table. The audit table contains this data:
InvoiceID CustomerID ItemSold AmountSold SalesPerson ModifyDate
1001 96 Widget 800 Robert 2001-1-1
1006 85 Thinger 350 Phil 2001-1-8
1001 96 Widget 800 Bobby 2001-1-9
1005 22 Widget 400 Robert 2001-1-10
1006 44 Thinger 500 Mike 2001-2-5
1001 96 Widget 250 Robert 2001-6-4
And I want to write a query which will identify whenever the SalesPerson field changes, for any particular InvoiceID (eg: whenever a salesman changes the sale to his name).
So in the example above, I'd like to identify the change which took place on 2001-1-9, where the sale for InvoiceID 1001 went from Robert to Bobby, and the change on 2001-6-4 where it went back to Robert from Bobby...so two changes for that particular ID. And I'd also like to identify the change on 2001-2-5 where the sale for InvoiceID 1006 went from Phil to Mike.
How can I write a SQL query which will identify/highlight these changes?
The table doesn't currently contain a primary key, but I can add one if needed.
If you add a primary key (which you should do, it will make some of the querying you need on this table easier in the long run)
Then what you need is a self join. Something like this might do it:
select a.invoiceId, a.SalesPerson as FirstSalesPerson,
a.Modifydate as FirstModifyDate, b.SalesPerson as SecondSalesPerson,
B.Modifydate as SecondModifyDate
from myaudittable a
join myadudittable b
on a.InvoiceID = b.InvoiceID
where a.AuditIDd <>b.AuditID and a.ModifyDate < b.ModifyDate
and a.SalesPerson<>b.SalesPerson
order by InvoiceID
This should do it.
declare #Audit table (
InvoiceID int,
CustomerID int,
ItemSold varchar(10),
AmountSold int,
SalesPerson varchar(10),
ModifyDate datetime
)
insert into #Audit
(InvoiceID, CustomerID, ItemSold, AmountSold, SalesPerson, ModifyDate)
values
(1001, 96, 'Widget', 800, 'Robert', '2001-1-1'),
(1006, 85, 'Thinger', 350, 'Phil', '2001-1-8'),
(1001, 96, 'Widget', 800, 'Bobby', '2001-1-9'),
(1005, 22, 'Widget', 400, 'Robert', '2001-1-10'),
(1006, 44, 'Thinger', 500, 'Mike', '2001-2-5'),
(1001, 96, 'Widget', 250, 'Robert', '2001-6-4')
select a2.InvoiceID, a2.SalesPerson, a2.ModifyDate
from #Audit a1
inner join #Audit a2
on a1.InvoiceID = a2.InvoiceID
and a1.ModifyDate < a2.ModifyDate
and a1.SalesPerson <> a2.SalesPerson
Here's a more complete answer, I think. It assumes:
at least SQL Server 2005
that the ModifyDate column is the time at which the record is created in the audit log.
the existence of an identity primary key, AuditID
declare #Audit table
(
AuditID int identity(1,1),
InvoiceID int,
CustomerID int,
ItemSold varchar(10),
AmountSold int,
SalesPerson varchar(10),
ModifyDate datetime
)
;with orders (InvoiceID, SalesPerson, ModifyDate, idx)
as
(
select
InvoiceID,
SalesPerson,
ModifyDate,
row_number() over (partition by InvoiceID order by AuditID desc)
from #Audit
)
select o2.InvoiceID, o2.SalesPerson, o2.ModifyDate from orders o1 inner join orders o2
on
o1.InvoiceID = o2.InvoiceID and
o1.SalesPerson <> o2.SalesPerson and
o1.idx = o2.idx-1
order by InvoiceID, ModifyDate desc
I used some bits and pieces from the posted answers, but the only way I was able to isolate the actual changes in salesperson was to use a subquery. Otherwise I was getting too many results and it was difficult to isolate the actual dates that the record changed salespersons.
select InvoiceId,SalesPerson,auditdate from myaudittable where InvoiceId in
(select distinct a.InvoiceId
from myaudittable a inner join myaudittable b on a.InvoiceId = b.InvoiceId and
a.SalesPerson <> b.SalesPerson)
group by InvoiceId,SalesPerson