SQLite print out history of daily price changes of a stock - sql

I have to output price change for each stock based on the dates that are held at the table pricedates.
What I have currently come up with is to group prices by id_stock and order by price_date in ASC order, then subtract from every row.
It should be separately done for buy_price and sell_price.
The logic should be like this
stock_id prices history
1 price 1 price 1
price 60 price 60-price1
price 98 price98-price60
2 price 34 price34
price 67 price67-price34
I am really struggling with putting everything together, here is something that I have tried, most probably it's a total mess:
select p.id_price
(select min(pd.price_date) from pricedates pd as po where po.price_date > pd.price_date) as next_id_price,
(po.buy_price - pd.buy_price) buy
(po.sell_price - pd.sell_price) sell
from prices p
left join pricedates pd on pd.id_date=p.id_price_date
group by p.id_stock
order by pd.price_date asc;

If you are running SQLite >= 3.25, you can use window function lag() to access the previous record of the same stock_id:
select
p.stock_id,
p.buy_price,
p.buy_price - lag(p.buy_price, 1, 0)
over(partition by p.stock_id order by pd.price_date) buy_history,
p.sell_price,
p.sell_price - lag(p.sell_price, 1, 0)
over(partition by p.stock_id order by pd.price_date) sell_history
from prices p
inner join pricedates pd
on pd.id_date = p.id_price_date
order by pd.price_date asc;
In earlier vesions of SQLite, where lag() is not available, one solution is to self-join the table with a correlated subquery that locates the previous record:
select
p.stock_id,
p.buy_price,
p.buy_price - coalesce(p1.buy_price, 0) buy_history,
p.sell_price,
p.sell_price - coalesce(p1.sell_price, 0) sell_history
from prices p
inner join pricedates pd
on pd.id_date = p.id_price_date
left join prices p1
on p1.stock_id = p.stock_id
and p1.id_date = (
select pd1.id_date
from pricedates pd1
where pd1.price_date > pd.price_date
order by pd1.price_date
limit 1
)
order by pd.price_date asc;

You would use lag() -- which is available in the more recent versions of SQLite:
select p.*,
(price -
lag(p.price, 1, 0) over (partition by stock_id order by id_price_date)
) as diff
from price p;

Related

SQL get top 3 values / bottom 3 values with group by and sum

I am working on a restaurant management system. There I have two tables
order_details(orderId,dishId,createdAt)
dishes(id,name,imageUrl)
My customer wants to see a report top 3 selling items / least selling 3 items by the month
For the moment I did something like this
SELECT
*
FROM
(SELECT
SUM(qty) AS qty,
order_details.dishId,
MONTHNAME(order_details.createdAt) AS mon,
dishes.name,
dishes.imageUrl
FROM
rms.order_details
INNER JOIN dishes ON order_details.dishId = dishes.id
GROUP BY order_details.dishId , MONTHNAME(order_details.createdAt)) t
ORDER BY t.qty
This gives me all the dishes sold count order by qty.
I have to manually filter max 3 records and reject the rest. There should be a SQL way of doing this. How do I do this in SQL?
You would use row_number() for this purpose. You don't specify the database you are using, so I am guessing at the appropriate date functions. I also assume that you mean a month within a year, so you need to take the year into account as well:
SELECT ym.*
FROM (SELECT YEAR(od.CreatedAt) as yyyy,
MONTH(od.createdAt) as mm,
SUM(qty) AS qty,
od.dishId, d.name, d.imageUrl,
ROW_NUMBER() OVER (PARTITION BY YEAR(od.CreatedAt), MONTH(od.createdAt) ORDER BY SUM(qty) DESC) as seqnum_desc,
ROW_NUMBER() OVER (PARTITION BY YEAR(od.CreatedAt), MONTH(od.createdAt) ORDER BY SUM(qty) DESC) as seqnum_asc
FROM rms.order_details od INNER JOIN
dishes d
ON od.dishId = d.id
GROUP BY YEAR(od.CreatedAt), MONTH(od.CreatedAt), od.dishId
) ym
WHERE seqnum_asc <= 3 OR
seqnum_desc <= 3;
Using the above info i used i combination of group by, order by and limit
as shown below. I hope this is what you are looking for
SELECT
t.qty,
t.dishId,
t.month,
d.name,
d.mageUrl
from
(
SELECT
od.dishId,
count(od.dishId) AS 'qty',
date_format(od.createdAt,'%Y-%m') as 'month'
FROM
rms.order_details od
group by date_format(od.createdAt,'%Y-%m'),od.dishId
order by qty desc
limit 3) t
join rms.dishes d on (t.dishId = d.id)

how to filter data in sql based on percentile

I have 2 tables, the first one is contain customer information such as id,age, and name . the second table is contain their id, information of product they purchase, and the purchase_date (the date is from 2016 to 2018)
Table 1
-------
customer_id
customer_age
customer_name
Table2
------
customer_id
product
purchase_date
my desired result is to generate the table that contain customer_name and product who made purchase in 2017 and older than 75% of customer that make purchase in 2016.
Depending on your flavor of SQL, you can get quartiles using the more general ntile analytical function. This basically adds a new column to your query.
SELECT MIN(customer_age) as min_age FROM (
SELECT customer_id, customer_age, ntile(4) OVER(ORDER BY customer_age) AS q4 FROM table1
WHERE customer_id IN (
SELECT customer_id FROM table2 WHERE purchase_date = 2016)
) q
WHERE q4=4
This returns the lowest age of the 4th-quartile customers, which can be used in a subquery against the customers who made purchases in 2017.
The argument to ntile is how many buckets you want to divide into. In this case 75%+ equals 4th quartile, so 4 buckets is OK. The OVER() clause specifies what you want to sort by (customer_age in our case), and also lets us partition (group) the data if we want to, say, create multiple rankings for different years or countries.
Age is a horrible field to include in a database. Every day it changes. You should have date-of-birth or something similar.
To get the 75% oldest value in 2016, there are several possibilities. I usually go for row_number() and count(*):
select min(customer_age)
from (select c.*,
row_number() over (order by customer_age) as seqnum,
count(*) over () as cnt
from customers c join
where exists (select 1
from customer_products cp
where cp.customer_id = c.customer_id and
cp.purchase_date >= '2016-01-01' and
cp.purchase_date < '2017-01-01'
)
)
where seqnum >= 0.75 * cnt;
Then, to use this for a query for 2017:
with a2016 as (
select min(customer_age) as customer_age
from (select c.*,
row_number() over (order by customer_age) as seqnum,
count(*) over () as cnt
from customers c
where exists (select 1
from customer_products cp
where cp.customer_id = c.customer_id and
cp.purchase_date >= '2016-01-01' and
cp.purchase_date < '2017-01-01'
)
) c
where seqnum >= 0.75 * cnt
)
select c.*, cp.product_id
from customers c join
customer_products cp
on cp.customer_id = c.customer_id and
cp.purchase_date >= '2017-01-01' and
cp.purchase_date < '2018-01-01' join
a2016 a
on c.customer_age >= a.customer_age;

Build SQL query with JOIN and limits

Help me please build PostgreSQL query.
There are 2 tables: products(id, title) and prices(id, product_id, price_type, moment, value)
moment - timestamp, can be in past or future
Assume that price_type has only two option: retail or purchase
But one product may has many retail prices with different moments.
I need select all products with actual retail and purchase prices, where moment less than now.
It's I can done
SELECT
products.id,
products.title_translations AS title,
retail_prices.moment AS ret_moment,
pur_prices.value AS purchase,
retail_prices.value AS retail
FROM products
LEFT OUTER JOIN prices AS pur_prices ON products.id=pur_prices.product_id AND pur_prices.price_type='purchase' AND pur_prices.moment<current_timestamp
LEFT OUTER JOIN prices AS retail_prices ON products.id=retail_prices.product_id AND retail_prices.price_type='retail' AND retail_prices.moment<current_timestamp
ORDER BY products.id;
It works, but returns
product with all prices, but I need only last prices(by moment).
Just use ROW_NUMBER to find what is the last price before current time
with last_prices as (
SELECT
products.id,
products.title_translations AS title,
prices.moment,
prices.value,
prices.price_type,
ROW_NUMBER() OVER (PARTITION BY product_id, price_type
ORDER BY moment DESC) as rn
FROM products
LEFT JOIN prices
ON products.id = prices.product_id
WHERE moment < now()
)
SELECT id, title,
MAX(CASE WHEN price_type = 'retail'
THEN moment
END) as retail_moment,
MAX(CASE WHEN price_type = 'retail'
THEN value
END) as retail_price,
MAX(CASE WHEN price_type = 'purchase'
THEN moment
END) as purchase_moment,
MAX(CASE WHEN price_type = 'purchase'
THEN value
END) as purchase_price
FROM last_prices
WHERE rn = 1
GROUP BY id, title
ORDER BY id
To keep things organized, and straight in my mind, I'd use CTEs to generate two subsets of price data, one for purchase one for retail and assign a row number in ascending sequence with the lowest number having the most recent moment less than the currenttimestmap. And then when we join to these ctes, we only return the lowest number assigned.
With Pur_prices as (SELECT P.*, row_Number() over (partition by product_ID order by moment desc) RN
FROM prices P
WHERE price_Type = 'purchase'
and p.moment < current_timestamp)
, Retail_prices as (SELECT P.*, row_Number() over (partition by product_ID order by moment desc) RN
FROM prices P
WHERE price_Type = 'retail'
and p.moment < current_timestamp)
SELECT
p.id,
p.title_translations AS title,
rp.moment AS ret_moment,
rp.value AS retail,
pp.moment AS Pur_moment,
pp.value AS purchase
FROM products p
LEFT JOIN pur_prices pp
ON p.id=pp.product_id
AND pp.RN = 1 --Only show the most recent price less than current time
LEFT JOIN retail_prices rp
ON p.id=rp.product_id
AND RP.RN = 1 --Only show the most recent price less than current time
ORDER BY p.id;
The end result should be all products regardless if they have a retail or purchase price; but if they do show the retail/purchase pricing for the most recent moment before now. My only concern is this implies all pricing has a moment they start (no null values allowed!)
You may be wanting it be ordered with respect to moment in descending order.
Change
ORDER BY products.id;
to
ORDER BY product.id ASC, moment DESC;

Query to get top product gainers by sales over previous week

I have a database table with three columns.
WeekNumber, ProductName, SalesCount
Sample data is shown in below table. I want top 10 gainers(by %) for week 26 over previous week i.e. week 25. The only condition is that the product should have sales count greater than 0 in both the weeks.
In the sample data B,C,D are the common products and C has the highest % gain.
Similarly, I will need top 10 losers also.
What I have tried till now is to make a inner join and get common products between two weeks. However, I am not able to get the top gainers logic.
The output should be like
Product PercentGain
C 400%
D 12.5%
B 10%
This will give you a generic answer, not just for any particular week:
select top 10 product , gain [gain%]
from
(
SELECT product, ((curr.salescount-prev.salescount)/prev.salescount)*100 gain
from
(select weeknumber, product, salescount from tbl) prev
JOIN
(select weeknumber, product, salescount from tbl) curr
on prev.weeknumber = curr.weeknumber - 1
AND prev.product = curr.product
where prev.salescount > 0 and curr.salescount > 0
)A
order by gain desc
If you are interested in weeks 25 and 26, then just add the condition below in the WHERE clause:
and prev.weeknumber = 25
If you are using SQL-Server 2012 (or newer), you could use the lag function to match "this" weeks sales with the previous week's. From there on, it's just some math:
SELECT TOP 10 product, sales/prev_sales - 1 AS gain
FROM (SELECT product,
sales,
LAG(sales) OVER (PARTITION BY product
ORDER BY weeknumber) AS prev_sales
FROM mytable) t
WHERE weeknumber = 26 AND
sales > 0 AND
prev_sales > 0 AND
sales > prev_sales
ORDER BY sales/prev_sales
this is the Query .
select top 10 product , gain [gain%]
from
(
SELECT curr.Product, ( (curr.Sales - prev.Sales ) *100)/prev.Sales gain
from
(select weeknumber, product, sales from ProductInfo where weeknumber = 25 ) prev
JOIN
(select weeknumber, product, sales from ProductInfo where weeknumber = 26 ) curr
on prev.product = curr.product
where prev.Sales > 0 and curr.Sales > 0
)A
order by gain desc

SQL join to get field name

I have the following query and I'm looking to write a join to give me the direction of a stock trend based on the id.
stock_trends
------------
stock_id
trend_id
direction_id
timestamp
price
breakout_price
trend_direction
---------------
id
direction
select s.*, v.latest_trend_date,
dbo.GetStockAverageVolume(s.id, latest_trend_date, GETDATE())
as avg_volume from stocks s
join(select stock_id, MAX(timestamp)as latest_trend_date from stock_trends st
group by st.stock_id) v on v.stock_id = s.id
where
(select top 1 trend_id from stock_trends
where s.id = stock_trends.stock_id order by [timestamp] desc) =
#trend_id and s.market_id = #market_id
and dbo.GetStockAverageVolume(s.id, latest_trend_date, GETDATE()) > 300000
order by latest_trend_date desc
How can I modify the above query to get the direction of the trend based on the direction_id within the stock_trends table?
For example:
select s.*, v.latest_trend_date,
dbo.GetStockAverageVolume(s.id, latest_trend_date, GETDATE())
as avg_volume, **direction** from stock s
...
...
...
Man I'm bad at joins!
Thanks so much.
This should work, but I think your query could be optimized
select stock_id, td.direction, MAX(timestamp) as latest_trend_date
from stock_trends st
join trend_direction td on st.direction_id = td.id
group by st.stock_id, td.direction
oh yeah and add v.direction to the main list.