MySQL, select to include earlier weeks - mysql

I'm using the SQL below to get the price history per week from two MySQL database tables:
SELECT ROUND(MIN(ph.price), 0) AS min_price, WEEK(ph.added) AS added
FROM products_data pd JOIN products_history ph ON pd.id = ph.product_id
WHERE ph.price > 0
GROUP BY added
ORDER BY added
Everything is good, except the products_history table only contains entries when a price is changed.
If a week A has a price change that creates the lowest min_price in week A, everything is good.
But if the price change happened 1+ weeks before week A, it's not detected by the query and the retrieved lowest min_price for week A may therefore not correct :(
Example, what is happening right now:
Week 13: Price change so min_price for product A = 4.78,
query returns 4.78 (correct)
Week 14: Price change so min_price for product B = 4.74,
query returns 4.74 (correct)
Week 15: Price change so min_price for product C = 4.79,
query returns 4.79 (wrong, should be 4.74 since that's still the price for product B)
Week 16: Price change so min_price for product C = 4.75,
query returns 4.75 (wrong, should be 4.74 since that's still the price for product B)
Week 17: No price changes,
query returns nothing... (wrong, should be 4.74 since that's still the price for product B)
Week 18: Price change so min_price for product B = 4.77,
query returns 4.77 (wrong, should be 4.75 since that's still the price for product C)
Any ideas for how to make the query retrieve the lowest min_price per week even if the change happend earlier?
products_data has these columns:
id, name, category
2434, 'Product A', 'Bits'
3437, 'Product B', 'Bits'
products_history has these columns:
id, product_id, price, added
4311, 2434, 4.74, 2019-05-15 22:19:50
2434, 3437, 4.78, 2019-05-15 22:19:59
Working version based on Gordon Linoff's correct answer below:
SELECT id, yw.yyyyww,
(SELECT ph2.price
FROM products_history ph2
WHERE ph2.price > 0 AND
YEARWEEK(ph2.added) <= yw.yyyyww AND ph2.product_id = pd.id
ORDER BY YEARWEEK(ph2.added) DESC, -- put the most recent values first
ph2.price ASC -- by lowest price
LIMIT 1
) AS min_price
FROM products_data pd CROSS JOIN
(SELECT DISTINCT YEARWEEK(ph.added) AS yyyyww
FROM products_history ph
) yw

If I understand correctly, you want a value for every product for every week. If so, use cross join to generate the rows and then -- in MySQL -- you can use a correlated subquery to get the price
SELECT product_id, yw.yyyyww,
(SELECT ph2.price
FROM products_history ph2
WHERE ph2.price > 0 AND
YEARWEEK(ph2.added) <= yw.yyyyww
ORDER BY YEARWEEK(ph2.added) DESC, -- put the most recent values first
ph2.price ASC -- by lowest price
) as min_price
FROM products_data pd CROSS JOIN
(SELECT DISTINCT YEARWEEK(ph.added) as yyyyww
FROM products_history ph
) yw

Related

MySQL most price change over time

price | date | product_id
100 | 2020-09-21 | 1
400 | 2020-09-20 | 2
300 | 2020-09-20 | 3
200 | 2020-09-19 | 1
400 | 2020-09-18 | 2
I add an entry into this table every day with a product's price that day.
Now I want to get most price drops for the last week (all dates up to 2020-09-14), in this example it would only return the product_id = 1, because that's the only thing that changed.
I think I have to join the table to itself, but I'm not getting it to work.
Here's something that I wanted to return the most price changes over the last day, however it's not working.
select pt.price, pt.date, pt.product_id, (pt.price - py.price) as change
from prices as pt
inner join (
select *
from prices
where date > '2020-09-20 19:33:43'
) as py
on pt.product_id = py.product_id
where pt.price - py.price > 0
order by change
I understand that you want to count how many times the price of each product changed over the last 7 days.
A naive approach would use aggregation and count(distinct price) - but it fails when a product's price changes back and forth.
A safer approach is window functions: you can use lag() to retrieve the previous price, and compare it against the current price; it is then easy to aggregate and count the price changes:
select product_id, sum(price <> lag_price) cnt_price_changes
from (
select t.*, lag(price) over(partition by product_id order by date) lag_price
from mytable t
where date >= current_date - interval 7 day
) t
group by product_id
order by price_changes desc
Try using MAX() and MIN() instead....
select MAX(pt.price), MIN(pt.price), MAX(pt.price) - MIN(pt.price) as change
from prices as pt
inner join (
select *
from prices
where date > '2020-09-20 19:33:43'
) as py
on pt.product_id = py.product_id
order by change
Instead of subtracting every row by every other row to get the result, you can find the max and min's easily by means of MAX() and MIN(), and, ultimately, **MAX() - MIN()**. Relevant lines from the linked MySQL documentation...
MAX(): Returns the maximum value of expr.
MIN(): Returns the minimum value of expr.
You won't be able to pull the other fields (id's, dates) since this is a GROUP BY() implied by the MAX() and MIN(), but you should then be able to get that info by query SELECT * FROM ... WHERE price = MAX_VALUE_JUST_ACQUIRED.
This examples will get you results per WeekOfYear and WeekOfMonth regarding the lowering of the price per product.
SELECT
COUNT(m1.product_id) as total,
m1.product_id,
WEEK(m1.ddate) AS weekofyear
FROM mytest m1
WHERE m1.price < (SELECT m2.price FROM mytest m2 WHERE m2.ddate<m1.ddate AND m1.product_id=m2.product_id LIMIT 0,1)
GROUP BY weekofyear,m1.product_id
ORDER BY weekofyear;
SELECT
COUNT(m1.product_id) as total,
m1.product_id,
FLOOR((DAYOFMONTH(ddate) - 1) / 7) + 1 AS weekofmonth
FROM mytest m1
WHERE m1.price < (SELECT m2.price FROM mytest m2 WHERE m2.ddate<m1.ddate AND m1.product_id=m2.product_id LIMIT 0,1)
GROUP BY weekofmonth,m1.product_id
ORDER BY weekofmonth;
Try this out in SQLFiddle.

Comparing day and week prices and getting cheapest

I'm trying to get the lowest price of an object. Problem is there can be daily and weekly prices. So when searching for the cheapest price i have to multiply the day price time 7 and compare to the week price to get the cheapest.
It can also happen that an object has week prices only or day prices only (or no prices at all).
BTW: It has to be such a subselect query, cause i have some more WHERE queries following later.
Pricetable
id price type oid
1 10 d 1
2 12 d 2
3 70 w 1
4 80 w 2
Objects
id name
1 house1
2 house2
This is what i'm using but its not working correctly. When the day price*7 is bigger that the week price it still gives me the day price.
SELECT p.oid, p.price, p.id, p.type FROM Pricetable p INNER JOIN (
SELECT oid, MIN(IF(type="w",price, price*7)) AS price, id, type
FROM Pricetable
GROUP BY oid
) p2 ON p.oid = p2.oid AND p.id= p2.id
Your query should work to get the minimum price. However, it should be written as:
SELECT oid, MIN(CASE WHEN type = 'w' THEN price ELSE 7*price END) AS price
FROM Pricetable
GROUP BY oid ;
If you want other values from row with the minimum price, then you need more logic. How about this?
SELECT pt.*oid, MIN(CASE WHEN type = 'w' THEN price ELSE 7*price END) AS price
FROM Pricetable pt
WHERE pt.id = (SELECT pt2.id
FROM PriceTable pt2
WHERE pt2.oid = pt.oid
ORDER BY (CASE WHEN pt2.type = 'w' THEN pt2.price ELSE 7*pt2.price END)
LIMIT 1
);
You'll need a sub-select with some sort of ranking to get the cheapest price.
Check https://dba.stackexchange.com/questions/13703/get-the-rank-of-a-user-in-a-score-table That should get you on the right track.
I don't have access to MySQL right now, and it's a bit different than MSSQL. But that link should set you on the path.

add a column in MySQL rank by deal by day

I am just learning MySQL. I need to find out rank of deals by day. Here I am adding the corresponding MYSQL query for my requirement that currently ranks all sales highest to lowest by day. Please help me to add a column that gives the rank to the deal highest to lowest and resetting the next day.
Here is my current working query,..
single day with title, price
SELECT
DATE(order_items.created_at) AS the_day,
order_items.deal_id,
SUM(order_items.item_total) AS daily_total,
SUM(order_items.qty) AS units_sold,
deals.price,
deals.title
FROM
order_items JOIN deals ON order_items.deal_id = deals.id
WHERE
order_items.created_at >= '2016-01-01 00:00:00' AND order_items.created_at < '2016-01-30 00:00:00'
AND
order_items.status=1
AND
order_items.paid=1
GROUP BY
order_items.deal_id
ORDER BY
the_day,
daily_total DESC;
The easiest way to do is that:
Use your existing SQL - I guess you need to update your SQL, make sure any non-aggregated columns in select should be in group by as well
Use PHP to loop (1-5), it works for multiple days
If you are happy to get top 5 for a single day, you can add limit 5 at end of your SQL
If you need top 5 results for each day in multiple days in one SQL, you need to update SQL to be more complicated. And here is a hint to use row id see example:
select increment counter in mysql
OK - Since you updated your question to return top 1 result per day, this is easier:
Step 1: get each day, each deal, report:
SELECT deal_id, date(created_at) ymd, sum(item_total) daily_total, sum(qty) units_sold
FROM order_items
WHERE substr(created_at,1,7) = '2016-01'
AND status = 1
AND paid = 1
GROUP BY 1,2
Step 2: Find the best deal of each day from step 1:
SELECT aa.ymd, max(aa.daily_total) max_total
FROM (
SELECT deal_id, date(created_at) ymd, sum(item_total) daily_total, sum(qty) units_sold
FROM order_items
WHERE substr(created_at,1,7) = '2016-01'
AND status = 1
AND paid = 1
GROUP BY 1,2
) as aa
GROUP BY 1;
Please note that max(item_total) not necessary same row as max(unit_sold), so you need to choose one, and cannot run togather
Step 3: Join step 2 with step 1 and deals to find out the rest of information:
SELECT aa.*, deals.price, deal.title
FROM (
SELECT aa.ymd, max(aa.daily_total) max_total
FROM (
SELECT deal_id, date(created_at) ymd, sum(item_total) daily_total, sum(qty) units_sold
FROM order_items
WHERE substr(created_at,1,7) = '2016-01'
AND status = 1
AND paid = 1
GROUP BY 1,2
) as aa
GROUP BY 1
) as bb
JOIN (
SELECT deal_id, date(created_at) ymd, sum(item_total) daily_total, sum(qty) units_sold
FROM order_items
WHERE substr(created_at,1,7) = '2016-01'
AND status = 1
AND paid = 1
GROUP BY 1,2
) as aa ON bb.ymd = aa.ymd and bb.max_total = aa.daily_total
JOIN deals ON aa.deal_id = deals.id
ORDER BY aa.ymd, aa.max_total

How to get rows with max date when grouping in MySQL?

I have a table with prices and dates on product:
id
product
price
date
I create a new record when price change. And I have a table like this:
id product price date
1 1 10 2014-01-01
2 1 20 2014-02-17
3 1 5 2014-03-28
4 2 25 2014-01-05
5 2 12 2014-02-08
6 2 30 2014-03-12
I want to get last price for all products. But when I group with "product", I can't get a price from a row with maximum date.
I can use MAX(), MIN() or COUNT() function in request, but I need a result based on other value.
I want something like this in final:
product price date
1 5 2014-03-28
2 30 2014-03-12
But I don't know how. May be like this:
SELECT product, {price with max date}, {max date}
FROM table
GROUP BY product
Alternatively, you can have subquery to get the latest get for every product and join the result on the table itself to get the other columns.
SELECT a.*
FROM tableName a
INNER JOIN
(
SELECT product, MAX(date) mxdate
FROM tableName
GROUP BY product
) b ON a.product = b.product
AND a.date = b.mxdate
I think the easiest way is a substring_index()/group_concat() trick:
SELECT product,
substring_index(group_concat(price order by date desc), ',', 1) as PriceOnMaxDate
max(date)
FROM table
GROUP BY product;
Another way, that might be more efficient than a group by is:
select p.*
from table t
where not exists (select 1
from table t2
where t2.product = t.product and
t2.date > t.date
);
This says: "Get me all rows from the table where the same product does not have a larger date." That is a fancy way of saying "get me the row with the maximum date for each product."
Note that there is a subtle difference: the second form will return all rows that on the maximum date, if there are duplicates.
Also, for performance an index on table(product, date) is recommended.
You can use a subquery that groups by product and return the maximum date for every product, and join this subquery back to the products table:
SELECT
p.product,
p.price,
p.date
FROM
products p INNER JOIN (
SELECT
product,
MAX(date) AS max_date
FROM
products
GROUP BY
product) m
ON p.product = m.product AND p.date = m.max_date
SELECT
product,
price,
date
FROM
(SELECT
product,
price,
date
FROM table_name ORDER BY date DESC) AS t1
GROUP BY product;

find price difference for different date

how find difference of price for two selected day. my table as shown
---------------------------------------
id price date product
---------------------------------------
1 10 15-12-2013 pen
2 40 15-12-2013 book
3 15 16-12-2013 pen
4 42 16-12-2013 book
-------------------------------------
i want an sql query to get output like, if startdate:15-12-2013 & enddate: 16-12-2013
product startdate(15-12-2013) enddate(16-12-2013) difference
--------------------------------------------------------------
pen 10 15 5
book 40 42 2
--------------------------------------------------------------
Maybe something like this?
select
p1.product,
p1.price,
p2.price,
p1.price - p2.price as diff
from
product p1,
product p2
where
p1.product=p2.product and
date(p1.date)='2013-12-15' and
date(p2.date)='2013-12-16'
If performance is a question then this link can provide a better alternative for the date matching part: MySQL SELECT WHERE datetime matches day (and not necessarily time)
Try this:
SELECT product, StartDatePrice, EndDatePrice, (EndDatePrice - StartDatePrice) AS Difference
FROM (SELECT product, MAX(IF(a.date = '15-12-2013', a.price, 0)) AS StartDatePrice,
MAX(IF(a.date = '16-12-2013', a.price, 0)) AS EndDatePrice
FROM tableA a
GROUP BY product
) AS A;
If you insert a row on your prices table whenewer a price changes, and not every day, you should consider using this query:
SELECT
p1.product,
p1.price as stardtade,
p2.price as enddate,
p2.price-p1.price as difference
FROM
prices p1 INNER JOIN (SELECT product, MAX(dt) max_dt
FROM prices
WHERE dt<='2013-12-15'
GROUP BY product) st
ON p1.product=st.product AND p1.dt = st.max_dt
INNER JOIN
prices p2
ON p1.product=p2.product
INNER JOIN (SELECT product, MAX(dt) max_dt
FROM prices
WHERE dt<='2013-12-16'
GROUP BY product) ed
ON p2.product=ed.product AND p2.dt = ed.max_dt
it is more complicated, but it will work even if some dates are not present in your table. In that case it will use the lask known value for the price.
Please see fiddle here.