MySQL Inner join last 7 days find top sales - mysql

sqlfiddle.com/#!9/ecede/1
Tables and sample data:
CREATE TABLE orders(
order_id INT NOT NULL,
created_at DATE NOT NULL,
country CHAR(10),
total_net_revenue INT NOT NULL,
total_gross_revenue INT NOT NULL,
total_quantity INT NOT NULL
);
INSERT INTO orders VALUES
(101,'2018-03-08',"China",150,250,20),
(102,'2018-03-08',"China",140,280,67),
(103,'2018-03-08',"China",150,190,15),
(111,'2018-02-09',"China",150,190,15),
(104,'2018-03-07',"China",150,190,15);
CREATE TABLE products_inventory(
product_id INT NOT NULL,
product_name CHAR(10),
brand_name CHAR(10),
purchase_price INT NOT NULL,
selling_price INT NOT NULL,
units_remaining_in_inventory INT NOT NULL
);
INSERT INTO products_inventory VALUES
(97,"3ds","Nintendo",100,250,40),
(98,"Switch","Nintendo",140,280,102),
(99,"Mini","Nintendo",40,190,30),
(131,"Fail","Nintendo",40,190,1310);
CREATE TABLE items_in_order(
order_id INT NOT NULL,
country CHAR(10),
brand CHAR(10),
product_name CHAR(10),
product_ID INT NOT NULL,
net_revenue INT NOT NULL,
gross_revenue INT NOT NULL,
quantity INT NOT NULL
);
INSERT INTO items_in_order VALUES
(101,"China","Nintendo","3ds",97,150,250,20),
(102,"China","Nintendo","Switch",98,140,280,67),
(103,"China","Nintendo","Mini",99,150,190,15),
(111,"China","Nintendo","Fail",131,150,190,15),
(104,"China","Nintendo","3ds",97,150,250,20);
Query:
SELECT i1.product_name, i1.product_id, i1.brand,
SUM(i1.net_revenue) AS net_revenue_last7days,
(p2.selling_price - p2.purchase_price) AS item_margin,
remaining_stock
FROM items_in_order i1
INNER JOIN(
SELECT o1.order_id,o1.country,SUM(o1.total_net_revenue) AS net_revenue,
SUM(o1.total_gross_revenue) AS gross_revenue
FROM orders o1
WHERE (created_at >= NOW() - INTERVAL 7 DAY) AND o1.country = "China"
GROUP BY 1,2
) o2
ON o2.order_id = i1.order_id AND o2.country = i1.country AND o2.net_revenue = i1.net_revenue
AND o2.gross_revenue = i1.gross_revenue
INNER JOIN(
SELECT product_id, product_name, brand_name AS brand, purchase_price, selling_price,
SUM(units_remaining_in_inventory) AS remaining_stock
FROM products_inventory p1
GROUP BY 1,2,3
) p2
ON i1.product_id = p2.product_id AND i1.product_name = p2.product_name AND i1.brand = p2.brand
WHERE i1.country = "China" AND i1.brand = "Nintendo"
GROUP BY 1,2,3
ORDER BY 4 DESC
LIMIT 10
I'm trying to get a sum of the revenue on the last 7 days of per products per brand with a join on tables: orders & items_in_order.
Is there a problem with my INNER JOIN? It should show a net revenue for the last 7 days of 300 for the 3ds.
additional info what I'm trying to achieve:
Top revenue per items for Nintendo in China last 7 days
Show units of current inventory of each item
Show the margin for each item

This part of the first JOIN is preventing order #104 from being included:
AND o2.gross_revenue = i1.gross_revenue
The gross revenue in the orders table for order #104 is 190, but it's 250 in the items_in_order table.
If you remove that from the JOIN you get net_revenue_last7days = 300.
http://sqlfiddle.com/#!9/ecede/10

Related

Alternative to subquery (mysql)

I have tables that hold payroll header & detail info on a separate table. Schema of each below,
CREATE TABLE `Payroll` (
`payId` int,
`groupId` int,
`startDate` date ,
`endDate` date,
`paymentDate` date
);
insert into Payroll values
(20,2,'2022-06-01','2022-06-30','2022-06-30'),
(21,2,'2022-07-01','2022-07-31','2022-07-31'),
(18,1,'2022-05-01','2022-05-31','2022-05-31'),
(19,1,'2022-07-01','2022-07-31','2022-07-31')
;
CREATE TABLE `PayrollItems` (
`payId` int NOT NULL,
`employeeId` int ,
`payCategory` varchar(45) ,
`value` decimal(15,4) NOT NULL
);
insert into PayrollItems values
(20,12,'salary',200),
(20,12,'housing',500),
(20,13,'salary',400),
(20,14,'salary',1300),
(21,12,'salary',200),
(21,12,'housing',500),
(21,13,'salary',400),
(21,14,'salary',1300),
(18,13,'salary',400),
(18,13,'housing',1300),
(19,14,'salary',500),
(19,14,'housing',1200)
;
I am trying to get a query wherein given a payid i should get the previous payid details. Previous payid is identified by a combination of the paymentDate & groupId fields.
Therefore, for the data above, for payid 19 i should get records of payid 18 i.e each pay item value, as they both are of the same groupid, 1 , and paymentDate of payid 18 is prior to paymentDate of payid 19. There could be more records that have a paymentDate dated prior to payid 19, but only first record dated prior is required.
I tried,
SELECT
y.*
FROM
Payroll ppi2
JOIN
PayrollItems prr3 ON (`prr3`.`payId` = `ppi2`.`payId`)
LEFT JOIN
(SELECT
prr3.employeeId,
ppi2.paymentDate,
ppi2.payId,
ppi2.groupId,
'Last months standard salary' AS Particulars,
MAX(CASE
WHEN TRIM(prr3.payCategory) = 'salary' THEN value
ELSE 0
END) salary,
MAX(CASE
WHEN TRIM(prr3.payCategory) = 'housing' THEN value
ELSE 0
END) housing
FROM
Payroll ppi2
JOIN PayrollItems prr3 ON (`prr3`.`payId` = `ppi2`.`payId`)
AND ppi2.payId = 19
GROUP BY ppi2.payId , prr3.employeeId , ppi2.paymentDate,ppi2.groupId
ORDER BY ppi2.paymentDate DESC) AS y ON (y.groupId = ppi2.groupId)
AND y.paymentDate < ppi2.paymentDate
GROUP BY y.payId,y.employeeId,y.paymentDate,y.groupId,y.Particulars;
but i am not getting any results.
Expected result,given payid = 19, would be,
payid employeeid housing salary
18 13 1300 400
Would there be another way of doing this ?
dbfiddle
WITH
cte AS (
SELECT t1.payid prev_payid
FROM Payroll t1
JOIN Payroll t2 USING (groupId)
WHERE t2.payid = #payid
AND t1.startDate < t2.startDate
ORDER BY t1.startDate DESC LIMIT 1
)
SELECT payId,
employeeId,
SUM(value * (payCategory = 'housing')) housing,
SUM(value * (payCategory = 'salary')) salary
FROM PayrollItems
CROSS JOIN cte
WHERE payid = prev_payid
GROUP BY 1, 2
https://dbfiddle.uk/1LAdyksH

Change db schema or query to return balance for a given period of time

I have came up with the following schema:
CREATE TABLE products
(
id INT(10) UNSIGNED AUTO_INCREMENT NOT NULL,
name VARCHAR(255) NOT NULL,
quantity INT(10) UNSIGNED NOT NULL,
purchase_price DECIMAL(8,2) NOT NULL,
sell_price DECIMAL(8,2) NOT NULL,
provider VARCHAR(255) NULL,
created_at TIMESTAMP NULL,
PRIMARY KEY (id)
);
# payment methods = {
# "0": "CASH",
# "1": "CREDIT CARD",
# ...
# }
CREATE TABLE orders
(
id INT(10) UNSIGNED AUTO_INCREMENT NOT NULL,
product_id INT(10) UNSIGNED NOT NULL,
quantity INT(10) UNSIGNED NOT NULL,
payment_method INT(11) NOT NULL DEFAULT 0,
created_at TIMESTAMP NULL,
PRIMARY KEY (id),
FOREIGN KEY (product_id) REFERENCES products(id)
);
# status = {
# "0": "PENDING"
# "1": "PAID"
# }
CREATE TABLE invoices
(
id INT(10) UNSIGNED AUTO_INCREMENT NOT NULL,
price INT(10) UNSIGNED NOT NULL,
status INT(10) UNSIGNED NOT NULL DEFAULT 0,
created_at TIMESTAMP NULL,
PRIMARY KEY (id)
);
# payment methods = {
# "0": 'CASH',
# "1": 'CREDIT CARD',
# ...
# }
CREATE TABLE bills
(
id INT(10) UNSIGNED AUTO_INCREMENT NOT NULL,
name VARCHAR(255) NOT NULL,
payment_method INT(10) UNSIGNED NOT NULL DEFAULT 0,
price DECIMAL(8,2) NOT NULL,
created_at TIMESTAMP NULL,
PRIMARY KEY (id)
);
And the following query to select a balance:
SELECT ((orders + invoices) - bills) as balance
FROM
(
SELECT SUM(p.sell_price * o.quantity) as orders
FROM orders o
JOIN products p
ON o.product_id = p.id
) orders,
(
SELECT SUM(price) as invoices
FROM invoices
WHERE status = 1
) invoices,
(
SELECT SUM(price) as bills
FROM bills
) bills;
Its working and returning the right balance, but I want to create a chart using Morris.js and I need to change it to return a daily or monthly balance at a given period of time and in this format:
Daily (2017-02-27 to 2017-03-01)
balance | created_at
--------------------------
600.00 | 2017-03-01
50.00 | 2017-02-28
450.00 | 2017-02-27
And monthly (2017-01 to 2017-03)
balance | created_at
--------------------------
200.00 | 2017-03
250.00 | 2017-02
350.00 | 2017-01
What I need to change in my schema or query to return results in this way?
http://sqlfiddle.com/#!9/2289a9/2
Any hints are welcomed. Thanks in advance
Include the created_at date in the SELECT list and a GROUP BY clause in each query.
Ditch the old school comma operator for the join operation, and replace it with a LEFT JOIN.
To return dates for which there are no orders (or no payments, or no invoices) we need a separate row source that is guaranteed to return the date values. As an example, we could use an inline view:
SELECT d.created_dt
FROM ( SELECT '2017-02-27' + INTERVAL 0 DAY AS created_dt
UNION ALL SELECT '2017-02-28'
UNION ALL SELECT '2017-03-01'
) d
ORDER BY d.created_dt
The inline view is just an option. If we had a calendar table that contains rows for the three dates we're interested in, we could make use of that instead. What's important is that we have a query that is guaranteed to return to us exactly three rows with the distinct created_at date values we want to return.
Once we have that, we can add a LEFT JOIN to get the value of "bills" for that date.
SELECT d.created_dt
, b.bills
FROM ( SELECT '2017-02-27' + INTERVAL 0 DAY AS created_dt
UNION ALL SELECT '2017-02-28'
UNION ALL SELECT '2017-03-01'
) d
LEFT
JOIN ( SELECT DATE(bills.created_at) AS created_dt
, SUM(bills.price) AS bills
FROM bills
WHERE bills.created_at >= '2017-02-27'
AND bills.created_at < '2017-03-01' + INTERVAL 1 DAY
GROUP BY DATE(bills.created_at)
) b
ON b.created_dt = d.created_dt
ORDER BY d.created_dt
Extending that to add another LEFT JOIN, to get invoices
SELECT d.created_dt
, i.invoices
, b.bills
FROM ( SELECT '2017-02-27' + INTERVAL 0 DAY AS created_dt
UNION ALL SELECT '2017-02-28'
UNION ALL SELECT '2017-03-01'
) d
LEFT
JOIN ( SELECT DATE(bills.created_at) AS created_dt
, SUM(bills.price) AS bills
FROM bills
WHERE bills.created_at >= '2017-02-27'
AND bills.created_at < '2017-03-01' + INTERVAL 1 DAY
GROUP BY DATE(bills.created_at)
) b
ON b.created_dt = d.created_dt
LEFT
JOIN ( SELECT DATE(invoices.created_at) AS created_dt
, SUM(invoices.price) AS invoices
FROM invoices
WHERE invoices.status = 1
AND invoices.created_at >= '2017-02-27'
AND invoices.created_at < '2017-03-01' + INTERVAL 1 DAY
GROUP BY DATE(invoices.created_at)
) i
ON i.created_dt = d.created_dt
ORDER BY d.created_dt
Similarly, we can a LEFT JOIN to another inline view that returns total orders grouped by DATE(created_at).
It's important that the inline views return distinct value of created_dt, a single row for each date value.
Note that for dev, test and debugging, we can independently execute just the inline view queries.
When a matching row is not returned from a LEFT JOIN, for example no matching row returned from i because there were no invoices on that date, the query is going to return a NULL for the expression i.invoices. To replace the NULL with a zero, we can use the IFNULL function, or the more ANSI standard COALESCE function. For example:
SELECT d.created_dt
, IFNULL(i.invoices,0) AS invoices
, COALESCE(b.bills,0) AS bills
FROM ...
To get the results monthly, we'd need a calendar query that returns one row per month. Let's assume we're going to return a DATE value which as the first day of the month. For example:
SELECT d.created_month
FROM ( SELECT '2017-02-01' + INTERVAL 0 DAY AS created_month
UNION ALL SELECT '2017-03-01'
) d
ORDER BY d.created_month
The inline view queries will need to GROUP BY created_month, so they return a single value for each month value. My preference would be to use a DATE_FORMAT function to return the first day of the month, derived from created_at. But there are other ways to do it. The goal is return a single row for '2017-02-01' and a single row for '2017-03-01'. Note that the date ranges on created_at extend from '2017-02-01' up to (but not including) '2017-04-01', so we get the total for the whole month.
( SELECT DATE_FORMAT(bills.created_at,'%Y-%m-01') AS created_month
, SUM(bills.price) AS bills
FROM bills
WHERE bills.created_at >= '2017-02-01'
AND bills.created_at < '2017-03-01' + INTERVAL 1 MONTH
GROUP BY DATE_FORMAT(bills.created_at,'%Y-%m-01')
) b

Adding totals from multiple tables

I'm using Laravel and Eloquent on MySQL. Essentially I am trying to get results from invoices, including their 'total' and 'paid' amounts.
I have 4 tables:
invoices
id int(10),
date_due date,
client_id int(10),
deleted_at datetime
invoices_items
id int(10),
invoice_id int(10),
price decimal(15,2),
quantity int(10)
invoices_payments (this is a pivot table as payments can apply to other invoices too)
payment_id int(10),
invoice_id int(10),
amount decimal(15,2)
payments
id int(10),
payment_date date,
total decimal(15,2)
(there are other fields but they are not relevant)
I've been using this query, based on a few other answers and other research:
select
`invoices`.*,
SUM(
invoices_items.price * invoices_items.quantity
) as total ,
SUM(
invoices_payments.amount
) as paid
from
`invoices`
left join `invoices_items` on `invoices`.`id` = `invoices_items`.`invoice_id`
left join `invoices_payments` on `invoices`.`id` = `invoices_payments`.`invoice_id`
where
`invoices`.`deleted_at` is null
limit
25
The problem I am having is that the result always only returns 1 row (there are 5 invoices in the test db), and the amount for 'total' or 'paid' is not correct.
I'd like to add that there may not be any records in invoices_payments
-- SOLUTION --
Here is the final query in case anyone runs into similar situation
select
`invoices`.*,
COALESCE(SUM(
invoices_items.price * invoices_items.quantity
),0) as total,
COALESCE(SUM(invoices_payments.amount),0) as paid,
COALESCE(SUM(
invoices_items.price * invoices_items.quantity
),0) - COALESCE(SUM(invoices_payments.amount),0) as balance
from
`invoices`
left join `invoices_items` on `invoices`.`id` = `invoices_items`.`invoice_id`
left join `invoices_payments` on `invoices`.`id` = `invoices_payments`.`invoice_id`
where
`invoices`.`deleted_at` is null
group by
`invoices`.`id`
order by
`balance` desc
limit
25
Add a GROUP BY invoices.id after the WHERE

How to track product cost over time for a POS system?

I am designing a Point Of Sale "POS" database while using MySQL as my DBMS.
When adding products to the products database, I add the product name (ie. "Coca-Cola 2.L") and the UPC code, cost, price, department, supplier and the quantity.
So if I receive an order of 1000 bottles which cost me $1 per bottle
Then let's say 2 months down the road, I have only 100 bottles left in stock "my cost for these is $100". Now, I ordered 5,000 bottles more, but this time because I ordered 5,000 the supplier is giving me a 0.10 off each bottle (ie. cost of $0.90 each.) So, the cost is $4500 for my second order. I want to ensure that I track my profit very accurately. Therefore, the 100 that I bought at $1 I want to be able to track those separately at the $1 cost and the new order at the $0.90 cost.
The way I currently track cost for each sale item, by reading the current cost from the product table and I save it in the sales_transactions_items table along with the sold price, transaction_id, and product_id.
The issue that I am having now is that when I received the 5000 bottle, I changed that cost from $1 to $0.90 which made my profit goes up by (100 bottles x $0.10 saving per bottle) 100x0.10 = $10 in profit that I did not actually profited. After the quantity is sold, this discrepancy occurred because the the quantity value reached 5100 bottles where 100 were purchase at $1 and 5,000 were purchased at $0.90 each.
My question: how to I solve for this issue where I can truly capture how much each item cost me.
Here is a generic enough but oversimplified example of how your schema might look like
CREATE TABLE products
(
`id` int not null auto_increment primary key,
`name` varchar(13),
`price` decimal(12, 2), -- current sale price. You might want to extract it into it's own table `prices`
...
);
CREATE TABLE orders
(
`id` int not null auto_increment primary key,
`date` date,
...
);
CREATE TABLE order_items
(
`id` int not null auto_increment primary key,
`order_id` int not null,
`product_id` int,
`quantity` decimal(12, 3),
`cost` decimal(12, 2),
foreign key (`order_id`) references orders (id),
foreign key (`product_id`) references products (id)
);
CREATE TABLE sales
(
`id` int not null auto_increment primary key,
`date` datetime,
...
);
CREATE TABLE sale_items
(
`id` int not null auto_increment primary key,
`sale_id` int not null,
`product_id` int,
`quantity` decimal(12, 3),
`price` decimal(12, 2),
foreign key (`sale_id`) references sales (id),
foreign key (`product_id`) references products (id)
);
Here is a SQLFiddle demo
This gives you the ability to independently track your costs and sales.
One way to calculate total sales, total actual cost and margin per product
SELECT product_id, p.name, sales_quantity, sales_total, cost_total, sales_total - cost_total margin
FROM
(
SELECT product_id, sales_quantity, sales_total, SUM(
CASE WHEN sales_quantity >= running_quantity
THEN cost * quantity
WHEN sales_quantity BETWEEN running_quantity - quantity AND running_quantity
THEN cost * (sales_quantity - (running_quantity - quantity))
ELSE 0
END) cost_total
FROM
(
SELECT s.*, o.cost, o.quantity, o.running_quantity
FROM
(
SELECT product_id,
SUM(quantity * price) sales_total,
SUM(quantity) sales_quantity
FROM sale_items
GROUP BY product_id
) s JOIN
(
SELECT product_id, cost, quantity, (
SELECT SUM(quantity)
FROM order_items
WHERE product_id = i.product_id
AND order_id <= i.order_id
) running_quantity
FROM order_items i
) o
ON s.product_id = o.product_id
) q
GROUP BY product_id, sales_quantity, sales_total
) q JOIN products p
ON q.product_id = p.id
Sample output:
| PRODUCT_ID | NAME | SALES_QUANTITY | SALES_TOTAL | COST_TOTAL | MARGIN |
|------------|---------------|----------------|-------------|------------|--------|
| 1 | Coca-Cola 2.L | 150 | 187.5 | 145 | 42.5 |
Here is a SQLFiddle demo
You may see that in the example first 100 bottles out of 150 sold cost $100 ($1*100), and the rest 50 cost $45 (0.9*50)
Declare #FromDate DateTime=GetDate()
declare #ToDate DateTime=GetDate()
Select ToDateProfit.ProductID,ToDateProfit.Name,ToDateProfit.sales_quantity- ISNULL(FromDateProfit.sales_quantity,0) as Sales_Quantity
,ToDateProfit.sales_total-ISNULL(FromDateProfit.sales_total,0) as Sales_Total
,ToDateProfit.cost_total-ISNULL(FromDateProfit.cost_total,0) as Cost_Total
,ToDateProfit.margin-ISNULL(FromDateProfit.margin,0) as Margin
From
(
SELECT P.ProductID, p.name, sales_quantity, sales_total, cost_total, sales_total - cost_total margin
FROM
(
SELECT productid, sales_quantity, sales_total, SUM(
CASE WHEN sales_quantity >= running_quantity
THEN PurchasePrice* quantity
WHEN sales_quantity BETWEEN running_quantity - quantity AND running_quantity
THEN PurchasePrice * (sales_quantity - (running_quantity - quantity))
ELSE 0
END) cost_total
FROM
(
SELECT s.*, o.PurchasePrice, o.quantity, o.running_quantity
FROM
(
SELECT ProductID,
SUM((quantity * UnitPrice)/ExchangeRate) sales_total,
SUM(quantity) sales_quantity
FROM SaleDetail
Inner Join Sale on Sale.SaleID=SaleDetail.SaleID
where Convert(Date,Sale.SaleDate)<=Convert(date,#ToDate) --(must minus saledate <FromDate (GetDate() is ToDate) )
GROUP BY ProductID
) s JOIN
(
SELECT productid, BuyingPrice/ExchangeRate as PurchasePrice, quantity, (
SELECT SUM(quantity)
FROM PurchaseDetail
inner join Purchase Pur on Pur.PurchaseID=PurchaseDetail.PurchaseID
WHERE productid = i.productid
AND Pur.Date <= PurI.date ----Fifo (if want lifo change <= to >=)
) running_quantity
FROM PurchaseDetail i
Inner join Purchase PurI on PurI.PurchaseID=i.PurchaseID
) o
ON s.productid = o.productid
)q
GROUP BY productid, sales_quantity, sales_total
) q JOIN product p
ON q.ProductID = p.ProductID
)ToDateProfit
Left Join
(
SELECT P.ProductID, p.name, sales_quantity, sales_total, cost_total, sales_total - cost_total margin
FROM
(
SELECT productid, sales_quantity, sales_total, SUM(
CASE WHEN sales_quantity >= running_quantity
THEN PurchasePrice* quantity
WHEN sales_quantity BETWEEN running_quantity - quantity AND running_quantity
THEN PurchasePrice * (sales_quantity - (running_quantity - quantity))
ELSE 0
END) cost_total
FROM
(
SELECT s.*, o.PurchasePrice, o.quantity, o.running_quantity
FROM
(
SELECT ProductID,
SUM((quantity * UnitPrice)/ExchangeRate) sales_total,
SUM(quantity) sales_quantity
FROM SaleDetail
Inner Join Sale on Sale.SaleID=SaleDetail.SaleID
where Convert(Date,Sale.SaleDate)<Convert(date,#FromDate) --(must minus saledate <FromDate (GetDate() is ToDate) )
GROUP BY ProductID
) s JOIN
(
SELECT productid, BuyingPrice/ExchangeRate as PurchasePrice, quantity, (
SELECT SUM(quantity)
FROM PurchaseDetail
inner join Purchase Pur on Pur.PurchaseID=PurchaseDetail.PurchaseID
WHERE productid = i.productid
AND Pur.Date <= PurI.date ----Fifo (if want lifo change <= to >=)
) running_quantity
FROM PurchaseDetail i
Inner join Purchase PurI on PurI.PurchaseID=i.PurchaseID
) o ON s.productid = o.productid
) q
GROUP BY productid, sales_quantity, sales_total
) q JOIN product p
ON q.ProductID = p.ProductID
)FromDateProfit on FromDateProfit.ProductID=ToDateProfit.ProductID
Inner Join ---------Only Product
(
Select Product.ProductID From Product
Inner Join SaleDetail On SaleDetail.ProductID=Product.ProductID
Inner Join Sale On Sale.SaleID=SaleDetail.SaleID
Where CONVERT(Date,SaleDate)>=CONVERT(Date,#FromDate) And CONVERT(Date,SaleDate)<=CONVERT(Date,#ToDate)
Group By Product.ProductID)SP On SP.ProductID=ToDateProfit.ProductID
"I want to ensure that I track my profit very accurately. Therefore, the 100 that I bought at $1 I want to be able to track those separately at the $1 cost and the new order at the $0.90 cost." - This is not necessary. You have to calculate current average price and calculate the profit using it. You have no serial numbers for each bottle and for that reason even if you want to know the profit for each item - you cannot do it. And from owner's viewpoint, it is important to know the total profit from Coca Cola, not that "from this bottle it is 0.50, and from that bottle it is 0.45".

sub query in sql server

I have two tables :
create table sales (
unitcode int ,
categorycode smallint ,
ddate varchar(10) ,
price float
)
and
create table timetable (
year varchar(4) ,
month varchar(11) ,
ddate varchar(10)
)
I want to write a subquery to find :
in each month in each year which 2 products(unitcode , categorycode) have a top of price ?
Try this
;WITH cte AS (
SELECT unitcode,categorycode,t.ddate,price,ROW_NUMBER() OVER (PARTITION BY t.[year],t.[month] ORDER BY price desc) AS price_order,t.[year],t.[month]
FROM sales s
INNER JOIN timetable t
ON t.ddate = s.ddate
)
SELECT *
FROM cte
WHERE price_order <= 2
ORDER BY [year] ASC,[month] ASC,price DESC