Get value based on date - sql

I am trying to get the price point of a product based on future pricing. I can't really do it by Max (Expiration Date) since the price points are different. And I also can't do a Max(Price) since the high price might be the one expiring or it could be the new one. My data would look like this:
Supplier
Product
Price
Effective Date
Expiration Date
Supplier 1
A
800
04-01-2121
12-31-2023
Supplier 1
A
1000
01-01-2121
03-31-2023
Supplier 1
B
500
04-01-2121
12-31-2023
Supplier 1
B
400
01-01-2121
03-31-2023
Supplier 2
D
200
01-01-2121
12-31-2023
Supplier 2
C
600
01-01-2121
12-31-2023
The result I am trying to get is below:
Supplier
Product
Price
Effective Date
Expiration Date
Supplier 1
A
800
04-01-2121
12-31-2023
Supplier 1
B
500
04-01-2121
12-31-2023
Supplier 2
D
200
01-01-2121
12-31-2023
Supplier 2
C
600
01-01-2121
12-31-2023
Any ideas?

To have the product information when expirationdate is the greatest we can product wise group the rows in descending order of expirationdate and find the row with lowest serial number for each product or we can simply use subquery to select the information where expirationdate=max(expirationdate) within the product group.
First approach will be more efficient. But if your dbms doesn't support row_number() then you can use second approach.
Schema:
create table mydata(Supplier varchar(30),Product varchar(30), Price int,EffectiveDate date, ExpirationDate date);
insert into mydata values('Supplier 1', 'A', 800 ,'04-01-2121', '12-31-2023');
insert into mydata values('Supplier 1', 'A', 1000 ,'01-01-2121', '03-31-2023');
insert into mydata values('Supplier 1', 'B', 500 ,'04-01-2121', '12-31-2023');
insert into mydata values('Supplier 1', 'B', 400 ,'01-01-2121', '03-31-2023');
insert into mydata values('Supplier 2', 'D', 200 ,'01-01-2121', '12-31-2023');
insert into mydata values('Supplier 2', 'C', 600 ,'01-01-2121', '12-31-2023');
Query#1
WITH cte
AS (SELECT supplier,
product,
price,
effectivedate,
expirationdate,
ROW_NUMBER ()
OVER (PARTITION BY product
ORDER BY expirationdate DESC)
rn
FROM mydata)
SELECT supplier,
product,
price,
effectivedate,
expirationdate
FROM cte
WHERE rn = 1
Output:
supplier
product
price
effectivedate
expirationdate
Supplier 1
A
800
2121-04-01
2023-12-31
Supplier 1
B
500
2121-04-01
2023-12-31
Supplier 2
C
600
2121-01-01
2023-12-31
Supplier 2
D
200
2121-01-01
2023-12-31
Query#2 for older version of DBMS:
SELECT supplier,
product,
price,
effectivedate,
expirationdate
FROM mydata m
where effectivedate =
(select max(effectivedate) from mydata md where m.product=md.product)
Output:
supplier
product
price
effectivedate
expirationdate
Supplier 2
D
200
2121-01-01
2023-12-31
Supplier 2
C
600
2121-01-01
2023-12-31
Supplier 1
B
500
2121-04-01
2023-12-31
Supplier 1
A
800
2121-04-01
2023-12-31
db<>fiddle here

It looks you can accomplish what you need with a simple row_number by numbering the rows decending ordered against your ExpirationDate and filtering for the first row in each group.
select Supplier, Product, Price, EffectiveDate, ExpirationDate from (
select Supplier, Product, Price, EffectiveDate, ExpirationDate, row_number() over (partition by Product order by ExpirationDate desc)Seq
from table
)t
where Seq=1

Related

How to calculate needed amount for supply order?

Table "client_orders":
date
ordered
id
28.05
50
1
23.06
60
2
24.05
50
1
25.06
130
2
Table "stock":
id
amount
date
1
60
23.04
2
90
25.04
1
10
24.04
2
10
24.06
I want to calculate the amount I need to order (to fulfill the stock) for what date. For instance, it should be:
30 by 28.05 (60+10-50-50=-30) for id = 1
-90 by 25.06 (90-60+10-130=-90) for id = 2
I tried to do it with LAG function, but the problem is that the stock here is not updating.
SELECT *,
SUM(amount - ordered) OVER (PARTITION BY sd.id ORDER BY d.date ASC)
FROM stock sd
LEFT JOIN (SELECT date,
id,
ordered
FROM client_orders) AS d
ON sd.id = d.id
Couldn't find anything similar on the web. Grateful if you share articles/examples how to do that.
You could make a union of the two tables and sum all stock amounts with the negative of ordered amounts. For the date you could instead take the corresponding maximum value.
SELECT id,
SUM(amount),
MAX(date)
FROM (SELECT id,
-ordered AS amount,
date
FROM client_orders
UNION
SELECT *
FROM stock
) stock_and_orders
GROUP BY id
Try it here.

How to distribute sales with partitions

I have 2 tables:
1st table columns: ItemCode int, Amount float (I have over 1000 ItemCodes)
2nd table columns: ItemCode int, SoldAmount float, Price float (I have over 10000 sale rows for different items)
Example:
ItemId 1528's Amount in 1st table is 244. That items sales in the 2nd table is as below:
Sale 1 Amount = 120, Price = 10
Sale 2 Amount = 120, Price = 30
Sale 3 Amount = 100, Price = 20
Sale 4 Amount = 10, Price = 25
ItemCode
Amount
1528
244
1530
150
ItemCode
Date
Amount
Price
1528
2021.11.01
120
10
1530
2021.10.01
120
30
1528
2021.09.01
100
20
1530
2021.08.01
10
25
Tried cursor and loop , but no desired output.
The desired outcome is to distribute that 100 amount with the sales above like following:
Sale 1 Amount 60: 100 - 60 = 40 with price 5 --- So we continue to the next row and subtract whatever is left
Sale 2 Amount 30: 40 - 30 = 10 with price 6 --- So we continue to the next row and subtract whatever is left
Sale 3 Amount 20: 10 - 20 = -10 with price 7 --- So we stop here as the amount is equal to 0 or below .
As the result we should get this:
60 * 5 = 300
30 * 6 = 180
10 * 7 = 70 (that 10 is derived from whatever could be subtracted before it hits 0)
Desired table as below
ItemCode
Date
Amount
Price
1528
2021.11.01
120
10
1528
2021.10.01
120
30
1528
2021.09.01
4
20
My last attempt was as below
WITH CTE AS (
SELECT ItemCode, SUM(Amount) AS Amount
FROM table 1
GROUP BY STOCKREF )
SELECT *,
IIF(LAG(table1.Amount - table2.amount) OVER (PARTITION BY table1.Amount ORDER BY Date DESC) IS NULL, table1.Amount - table2.amount,
LAG(table1.Amount - table2.amount) OVER (PARTITION BY CTE.ItemCode ORDER BY Date DESC) - table2.AMOUNT) AS COL
FROM CTE JOIN (SELECT ItemCode, DATE_, AMOUNT, PRICE FROM table2) table 2 ON table1.ItemCode = table2.Amount
Hopefully this addresses the right question - if you're trying to create a running total per item_code, deducting the sale quantity from starting inventory from first-to-last sale, maybe this would work:
CREATE TABLE #items (item_code INT,
item_amount INT);
INSERT INTO #items (item_code, item_amount)
VALUES (1528, 244);
INSERT INTO #items (item_code, item_amount)
VALUES (1529, 240);
CREATE TABLE #sales (item_code INT,
sale_date DATE,
sale_amount INT,
sale_price DECIMAL(12,2));
INSERT INTO #sales (item_code, sale_date, sale_amount, sale_price)
VALUES (1528, '2021-12-01', 50, 5);
INSERT INTO #sales (item_code, sale_date, sale_amount, sale_price)
VALUES (1528, '2021-11-29', 120, 6.76292);
INSERT INTO #sales (item_code, sale_date, sale_amount, sale_price)
VALUES (1528, '2021-11-15', 120, 6.6453);
INSERT INTO #sales (item_code, sale_date, sale_amount, sale_price)
VALUES (1528, '2021-11-01', 100, 6.96875);
INSERT INTO #sales (item_code, sale_date, sale_amount, sale_price)
VALUES (1529, '2021-11-30', 48, 7.2);
INSERT INTO #sales (item_code, sale_date, sale_amount, sale_price)
VALUES (1529, '2021-11-18', 48, 3.5);
INSERT INTO #sales (item_code, sale_date, sale_amount, sale_price)
VALUES (1529, '2021-11-09', 96, 3.9);
INSERT INTO #sales (item_code, sale_date, sale_amount, sale_price)
VALUES (1529, '2021-11-05', 96, 3.75);
;WITH all_sales_with_running_totals AS ( --Calculate the running total of each item, deducting sale amount from total starting amount, in order of first sale to last
SELECT s.item_code,
s.sale_date,
s.sale_price,
i.item_amount AS starting_amount,
s.sale_amount,
i.item_amount - SUM(sale_amount) OVER(PARTITION BY s.item_code
ORDER BY s.sale_date
ROWS UNBOUNDED PRECEDING
) AS running_sale_amount
FROM #sales AS s
JOIN #items AS i ON s.item_code = i.item_code
),
sales_with_prev_running_total AS ( --Add the previous rows' running total, to assist with the final calculation
SELECT item_code,
sale_date,
sale_price,
starting_amount,
sale_amount,
running_sale_amount,
LAG(running_sale_amount, 1, NULL) OVER(PARTITION BY item_code
ORDER BY sale_date
)AS prev_running_sale_amount
FROM all_sales_with_running_totals
)
SELECT item_code, --Return the final running sale amount for each sale - if the inventory has already run out, return null. If there is insufficient inventory to fill the order, fill it with the qty remaining. Otherwise, fill the entire order.
sale_date,
sale_price,
starting_amount,
sale_amount,
running_sale_amount,
prev_running_sale_amount,
CASE WHEN prev_running_sale_amount <= 0
THEN NULL
WHEN running_sale_amount < 0
THEN prev_running_sale_amount
ELSE sale_amount
END AS result_sale_amount
FROM sales_with_prev_running_total;

Table with daily historical stock prices. How to pull stocks where the price reached a certain number for the first time

I have a table with historical stocks prices for hundreds of stocks. I need to extract only those stocks that reached $10 or greater for the first time.
Stock
Price
Date
AAA
9
2021-10-01
AAA
10
2021-10-02
AAA
8
2021-10-03
AAA
10
2021-10-04
BBB
9
2021-10-01
BBB
11
2021-10-02
BBB
12
2021-10-03
Is there a way to count how many times each stock hit >= 10 in order to pull only those where count = 1 (in this case it would be stock BBB considering it never reached 10 in the past)?
Since I couldn't figure how to create count I've tried the below manipulations with min/max dates but this looks like a bit awkward approach. Any idea of a simpler solution?
with query1 as (
select Stock, min(date) as min_greater10_dt
from t
where Price >= 10
group by Stock
), query2 as (
select Stock, max(date) as max_greater10_dt
from t
where Price >= 10
group by Stock
)
select Stock
from t a
join query1 b on b.Stock = a.Stock
join query2 c on c.Stock = a.Stock
where not(a.Price < 10 and a.Date between b.min_greater10_dt and c.max_greater10_dt)
This is a type of gaps-and-islands problem which can be solved as follows:
detect the change from < 10 to >= 10 using a lagged price
count the number of such changes
filter in only stock where this has happened exactly once
and take the first row since you only want the stock (you could group by here but a row number allows you to select the entire row should you wish to).
declare #Table table (Stock varchar(3), Price money, [Date] date);
insert into #Table (Stock, Price, [Date])
values
('AAA', 9, '2021-10-01'),
('AAA', 10, '2021-10-02'),
('AAA', 8, '2021-10-03'),
('AAA', 10, '2021-10-04'),
('BBB', 9, '2021-10-01'),
('BBB', 11, '2021-10-02'),
('BBB', 12, '2021-10-03');
with cte1 as (
select Stock, Price, [Date]
, row_number() over (partition by Stock, case when Price >= 10 then 1 else 0 end order by [Date] asc) rn
, lag(Price,1,0) over (partition by Stock order by [Date] asc) LaggedStock
from #Table
), cte2 as (
select Stock, Price, [Date], rn, LaggedStock
, sum(case when Price >= 10 and LaggedStock < 10 then 1 else 0 end) over (partition by Stock) StockOver10
from cte1
)
select Stock
--, Price, [Date], rn, LaggedStock, StockOver10 -- debug
from cte2
where Price >= 10
and StockOver10 = 1 and rn = 1;
Returns:
Stock
BBB
Note: providing DDL+DML as show above makes it much easier of people to assist.

tricky sql interview question(only give you 15mins to solve)

The given table (order info):
client Date Product order amt
1001, 2020-01-01, Desktop1, 100
1001, 2020-01-01, Mobile2, 200
1001, 2020-01-01, Mobile2, 100
1002, 2020-01-02, Mobile1, 100
1002, 2020-01-01, Mobile1, 100
1003, 2020-01-01, Desktop1, 100
1003, 2020-01-02, Desktop2, 100
1004, 2020-01-02, Mobile, 100
The return table should give following information:
On each date, how many client buy only one type of product(either mobile_unique or desktop_unique), and the total amount of order under each type of product
AND
On each date, how many client buy both types pf product, and the total amount of order.
So the return table should like this:
Date. product type total amount number of client
2020-01-01 mobile_only 100 1
2020-01-01 desktop_only 100 1
2020-01-01 both 400 1
2020-01-02 mobile_only 200 2
2020-01-02 desktop_only 100 1
I have solved it by creating multiple tables. But he interviewer only gives 15 mins to solve it, so I'd like to see any simple way to solve it.
You can "classify" clients at first (Mob, Des, Bot) and then group:
select date_, class, sum(amt), count(client)
from (
select date_, client, sum(order_amt) amt,
case when min(substr(product, 1, 1)) <> max(substr(product, 1, 1)) then 'B'
else min(substr(product, 1, 1))
end class
from orders group by date_, client)
group by date_, class order by date_, class
dbfiddle for Oracle
This seems to be an ill designed table. What if product happens to be a vegetable? I think you should test for sanity of the data a give error in that case.
select Date_, product_type, sum(total_amount) as total_amount, count(*) as number_of_clients
from (
select
Date_, sum(order_amt) as total_amount,
case
when sum(case when SUBSTRING(Product,1,7) = 'Desktop'
or SUBSTRING(Product,1,6) = 'Mobile'
then 0 else 1 end) > 0 then 'Error'
when count(distinct SUBSTRING(Product,1,6)) = 2 then 'both'
when min(SUBSTRING(Product,1,6)) = 'Mobile' then 'mobile_only'
else 'desktop_only'
end as product_type
from orders
group by Date_, client
)x
group by Date_, product_type
order by Date_, product_type desc
output:
Date_ product_type total_amount number_of_clients
2020-01-01 mobile_only 100 1
2020-01-01 desktop_only 100 1
2020-01-01 both 400 1
2020-01-02 mobile_only 200 2
2020-01-02 desktop_only 100 1

SQL Select all items from all clients with its last price

I have a table with all purchases made. with these columns:
clientnumber,
articlenumber,
datepurchased,
price,
qty
Sample data: (I have got more that 1000 clients and more than 50 products)
client1 - article1 - price 100 - qty 2 - date xx-xx-xxxx
client1 - article1 - price 111 - qty 5 - date xx-xx-xxxx
client1 - article2 - price 1 - qty 5 - date xx-xx-xxxx
client2 - article1 - price 114 - qty 5 - date xx-xx-xxxx
client2 - article1 - price 500 - qty 6 - date xx-xx-xxxx
etc..
i want get a list back that gives me all articles from each client purchased with its last price for each article and each client like this
Client 1, Artikel 1, 50 USD (this price should be the newest datepurchased)
client 1, articel 5, 30 usd
clients 2, articel 1, 30 usd
client 2, articel 2, 20 usd
...
You want to rank your records per client and item and show only the best ranked row (here: the latest purchase). Use ROW_NUMBER to do that.
select clientnumber, articlenumber, price
from
(
select
clientnumber, articlenumber, price,
row_number() over (partition by clientnumber, articlenumber
order by datepurchased desc) as rn
from purchases
) ranked
where rn = 1;
SELECT clientnumber,
articlenumber,
datepurchased,
(SELECT (yourtable.price / yourtable.qty) as unitPrice FROM yourtable yt WHERE yt.articlenumber = yourtable.articlenumber ORDER BY datepurchased DESC LIMIT 1) as latestPrice ,
qty
FROM yourtable
ORDER BY datepurchased DESC
SELECT TOP 1 articlenumber,
clientnumber,
datepurchased,
(yourtable.price / yourtable.qty) as pricePerArticle ,
qty
FROM yourtable
ORDER BY datepurchased DESC