SQL AVG and SUM GROUP BY - mysql

i have two tables, say Costs and Payments, and i need to calculate the AVG of monthly payments for each costs.
i.e. Costs_IdCost = 2 has three payments
+-----------+--------------+----------+--------+
| IdPayment | Costs_IdCost | Date | Amount |
+-----------+--------------+----------+--------+
| 1 | 2 |2012/09/10| 1000 |
+-----------+--------------+----------+--------+
| 2 | 2 |2012/09/20| 3000 |
+-----------+--------------+----------+--------+
| 3 | 2 |2012/10/01| 5000 |
+-----------+--------------+----------+--------+
now i need not just the average of payments (3000) but the average between:
September : (1000+3000)/2 = 2000
October : 5000 /1 = 5000
AVG : (2000+5000)/2 = 3500
and i'm getting pretty messy with group by and subquery >_<
thank you!
--------------------EDIT---------------------------
i'm using mySql, this is what i did so far:
SELECT c.IdCost,
c.Name,
c.Amount,
AVG(p.monthly_sum) Mean,
SUM( p.Amount ) Total,
COUNT( p.IdPayment ) Num
FROM Costs c
LEFT JOIN (SELECT MONTH(Date),
YEAR(Date),
Costs_IdCost,
IdPayment,
Amount,
AVG (Amount) monthly_sum
FROM Payments
GROUP BY YEAR(Date), MONTH(Date)
) p ON p.Costs_IdCost = c.IdCost
GROUP BY c.IdCost

I am not sure if this is exactly what you are looking for but it will give you the desired results:
select date_format(date, '%Y-%m') Month,
avg(amount) AvgAmount
from costs c
left join payments p
on c.id = p.costs_idcost
group by date_format(date, '%Y-%m')
union all
select concat(cast(count(*) as char), ' Months Averaged'),
avg(AvgAmount) TotalAvg
from
(
select avg(amount) AvgAmount,
date_format(date, '%Y-%m') Month
from costs c
left join payments p
on c.id = p.costs_idcost
group by date_format(date, '%Y-%m')
) x
See SQL Fiddle with Demo

You can group by month part of date.
GROUP BY datepart(MONTH, Cost.Date)
This may not the most efficient way, but it might work nicely. BTW datepart is build in function of MSSQL.

I do similar by using a select entry of
SELECT format(DATE_TIME,''yyyy-MM'') as [Month], .......
GROUP BY format(DATE_TIME,''yyyy-MM''), .........
The SORT BY is the same as what GROUP BY I am using. Not that one can even got to the hour, minute and second level with this concept if you want
example: SELECT format(DATE_TIME,''yyyy-MM-dd.HH'') as [Hour]

Related

Using mysql 8 window functions

My salary table looks like this,
employeeId Salary salaryEffectiveFrom
19966 10000.00 2022-07-01
19966 20000.00 2022-07-15
My role/grades table looks like this,
employeeId grade roleEffectiveFrom
19966 grade 3 2022-07-01
19966 grade 2 2022-07-10
I am trying to get the salary a grade is paid for by taking into account the effective date in both tables.
grade 3 is effective from 1-July-2022. grade 2 is effective from the 10th of July, implying grade 3 is effective till the 9th of July i.e. 9 days.
grade 2 is effective from 10-July-2022 onwards.
A salary of 10000 is effective from 1-July-2022 till 14-July-2022 as the salary of 20000 is effective from the 15th. Therefore grade 3 had a salary of 10000 for 9 days, grade 2 salary of 10000 for 4 days with grade 2 with a salary of 20000 from the 10th onwards. The role effectivefrom
date takes precedence over the salary effectivefrom date.
This query,
SELECT er.employeeId,
es.salary,
`grade`,
date(er.effectiveFrom) roleEffectiveFrom,
date(es.effectiveFrom) salaryEffectiveFrom,
DATEDIFF(LEAST(COALESCE(LEAD(er.effectiveFrom)
OVER (PARTITION BY er.employeeId ORDER By er.effectiveFrom),
DATE_ADD(LAST_DAY(er.effectiveFrom),INTERVAL 1 DAY)),
DATE_ADD(LAST_DAY(er.effectiveFrom),INTERVAL 1 DAY)),
er.effectiveFrom) as '#Days' ,
ROUND((salary * 12) / 365, 2) dailyRate
FROM EmployeeRole er
join EmployeeSalary es ON (es.employeeId = er.employeeId)
and er.employeeId = 19966
;
gives me the result set shown below,
employeeId Salary grade roleEffectiveFrom salaryEffectiveFrom Days dailyRate
19966 10000.00 grade 3 2022-07-01 2022-07-01 0 328.77
19966 20000.00 grade 3 2022-07-01 2022-07-15 9 657.53
19966 10000.00 grade 2 2022-07-10 2022-07-01 0 328.77
19966 20000.00 grade 2 2022-07-10 2022-07-15 22 657.53
grade3 is effective for 9 days in July so I want to get the total salary for those 9 days using a daily rate column, 328.77 * 9 = 2985.93 as a separate column but I am unable to do as I am getting the days for the wrong row i.e. 9 should be the result for the first row.
dbfiddle
merge the 2 table dates, lead them then use correlated sub queries
with cte as
(
SELECT employeeid,effectivefrom from EMPLOYEEROLE
union
select employeeid,effectivefrom from employeesalary
)
,cte1 as
(select employeeid,effectivefrom,
coalesce(
date_sub(lead(effectivefrom) over (partition by employeeid order by effectivefrom),interval 1 day) ,
now()) nexteff
from cte
)
select *,
datediff(nexteff,effectivefrom) + 1 diff,
(select grade from employeerole e where e.effectivefrom <= cte1.effectivefrom order by e.effectivefrom desc limit 1) grade,
(select salary from employeesalary e where e.effectivefrom <= cte1.nexteff order by e.effectivefrom desc limit 1) salary
from cte1;
+------------+---------------------+---------------------+------+---------+--------+
| employeeid | effectivefrom | nexteff | diff | grade | salary |
+------------+---------------------+---------------------+------+---------+--------+
| 19966 | 2022-07-01 00:00:00 | 2022-07-09 00:00:00 | 9 | grade 3 | 10000 |
| 19966 | 2022-07-10 00:00:00 | 2022-07-14 00:00:00 | 5 | grade 2 | 10000 |
| 19966 | 2022-07-15 00:00:00 | 2022-10-08 08:51:49 | 86 | grade 2 | 20000 |
+------------+---------------------+---------------------+------+---------+--------+
3 rows in set (0.003 sec)
with cte as
(
SELECT employeeid,effectivefrom from EMPLOYEEROLE
union
select employeeid,effectivefrom from employeesalary
)
,cte1 as
(select cte.employeeid,effectivefrom,
coalesce(
date_sub(lead(effectivefrom) over (partition by employeeid order by effectivefrom),interval 1 day) ,
last_day(maxdt)) nexteff
from cte
JOIN (select cte.employeeid,max(effectivefrom) maxdt from cte group by employeeid) c1
on c1.employeeid = cte.employeeid
)
select *,
datediff(nexteff,effectivefrom) + 1 diff,
(select grade from employeerole e where e.effectivefrom <= cte1.effectivefrom order by e.effectivefrom desc limit 1) grade,
(select salary from employeesalary e where e.effectivefrom <= cte1.nexteff order by e.effectivefrom desc limit 1) salary
from cte1;
+------------+---------------------+---------------------+------+---------+--------+
| employeeid | effectivefrom | nexteff | diff | grade | salary |
+------------+---------------------+---------------------+------+---------+--------+
| 19966 | 2022-07-01 00:00:00 | 2022-07-09 00:00:00 | 9 | grade 3 | 10000 |
| 19966 | 2022-07-10 00:00:00 | 2022-07-14 00:00:00 | 5 | grade 2 | 10000 |
| 19966 | 2022-07-15 00:00:00 | 2022-07-31 00:00:00 | 17 | grade 2 | 20000 |
+------------+---------------------+---------------------+------+---------+--------+
3 rows in set (0.004 sec)
I think if it were me, I'd generate a list containing an entry for each day with the effective grade and salary, and then just aggregate at the end. Take a look at this fiddle:
https://dbfiddle.uk/4t2RW2M2
I've started with the aggregate query, just so we can see the output, then I break out pieces of the query to show intermediate outputs. Here is an image of the final output and the query generating it:
SELECT grade, gradeEffective, salary, salaryEffective,
min(dt) as startsOn, max(dt) as endsOn, count(*) as days,
dailyRate,
sum(dailyRate) as pay
FROM (
SELECT DISTINCT dt, grade, gradeEffective, salary, salaryEffective,
ROUND((salary * 12) / 365, 2) as dailyRate
FROM (
SELECT dts.dt,
first_value(r.grade) OVER w as grade,
first_value(r.effectiveFrom) OVER w as gradeEffective,
first_value(s.salary) OVER w as salary,
first_value(s.effectiveFrom) OVER w as salaryEffective
FROM (
WITH RECURSIVE dates(n) AS (SELECT 0 UNION SELECT n + 1 FROM dates WHERE n + 1 <= 30)
SELECT '2022-07-01' + INTERVAL n DAY as dt FROM dates
) dts
LEFT JOIN EmployeeSalary s ON dts.dt >= s.effectiveFrom
LEFT JOIN EmployeeRole r on dts.dt >= r.effectiveFrom
WINDOW w AS (
PARTITION BY dts.dt
ORDER BY r.effectiveFrom DESC, s.effectiveFrom DESC
ROWS UNBOUNDED PRECEDING
)
) z
) a GROUP BY grade, gradeEffective, salary, salaryEffective, dailyRate
ORDER BY min(dt);
Now, the first thing I've done is create a list of dates using a recursive CTE:
WITH RECURSIVE dates(n) AS (SELECT 0 UNION SELECT n + 1 FROM dates WHERE n + 1 <= 30)
SELECT '2022-07-01' + INTERVAL n DAY as dt FROM dates
which produces a list of dates from July 1st to July 31st.
Take that list of dates and left join both of your tables to it, like so:
SELECT *
FROM (
WITH RECURSIVE dates(n) AS (SELECT 0 UNION SELECT n + 1 FROM dates WHERE n + 1 <= 30)
SELECT '2022-07-01' + INTERVAL n DAY as dt FROM dates
) dts
LEFT JOIN EmployeeSalary s ON dts.dt >= s.effectiveFrom
LEFT JOIN EmployeeRole r on dts.dt >= r.effectiveFrom
with the dt greater than or equal to the effective dates. Notice that after the 9th you start to get duplicate rows for each date.
We'll create a window to get the first values for grade and salary for each date, and we'll order first by role effectiveFrom and then salary effectiveFrom, to fulfil your priority condition.
SELECT dts.dt,
first_value(r.grade) OVER w as grade,
first_value(r.effectiveFrom) OVER w as gradeEffective,
first_value(s.salary) OVER w as salary,
first_value(s.effectiveFrom) OVER w as salaryEffective
FROM (
WITH RECURSIVE dates(n) AS (SELECT 0 UNION SELECT n + 1 FROM dates WHERE n + 1 <= 30)
SELECT '2022-07-01' + INTERVAL n DAY as dt FROM dates
) dts
LEFT JOIN EmployeeSalary s ON dts.dt >= s.effectiveFrom
LEFT JOIN EmployeeRole r on dts.dt >= r.effectiveFrom
WINDOW w AS (
PARTITION BY dts.dt
ORDER BY r.effectiveFrom DESC, s.effectiveFrom DESC
ROWS UNBOUNDED PRECEDING
);
This is still going to leave us multiple entries for some dates, although they are duplicates, so let's use that output in a new query, using DISTINCT to leave us only one copy of each row and using the opportunity to add the daily rate field:
SELECT DISTINCT dt, grade, gradeEffective, salary, salaryEffective,
ROUND((salary * 12) / 365, 2) as dailyRate
FROM (
SELECT dts.dt,
first_value(r.grade) OVER w as grade,
first_value(r.effectiveFrom) OVER w as gradeEffective,
first_value(s.salary) OVER w as salary,
first_value(s.effectiveFrom) OVER w as salaryEffective
FROM (
WITH RECURSIVE dates(n) AS (SELECT 0 UNION SELECT n + 1 FROM dates WHERE n + 1 <= 30)
SELECT '2022-07-01' + INTERVAL n DAY as dt FROM dates
) dts
LEFT JOIN EmployeeSalary s ON dts.dt >= s.effectiveFrom
LEFT JOIN EmployeeRole r on dts.dt >= r.effectiveFrom
WINDOW w AS (
PARTITION BY dts.dt
ORDER BY r.effectiveFrom DESC, s.effectiveFrom DESC
ROWS UNBOUNDED PRECEDING
)
) z;
This produces the deduplicated daily data
and now all we have to do is use aggregation to pull out the sums for each combination of grade and salary, which is the query that I started off with.
Let me know if this is what you were looking for, or if anything is unclear.
Since the start and end conditions weren't fleshed out in the question, I just created the date list arbitrarily. It's not difficult to generate the list based on the first effectiveFrom in both tables, and here is an example that runs from that start date until current:
WITH RECURSIVE dates(n) AS (
SELECT min(effectiveFrom) FROM (
select effectiveFrom from EmployeeRole UNION
select effectiveFrom from EmployeeSalary
) z
UNION SELECT n + INTERVAL 1 DAY FROM dates WHERE n <= now()
)
SELECT n as dt FROM dates
I also didn't handle for multiple employees, since there was only one given and I would just be guessing at the shape of the actual data.
You can start adding two new columns (i.e. tmpFrom and tmpTo), which should give the correct dates which are needed to calculate the 9 Days.
SELECT
er.employeeId,
es.salary,
`grade`,
date(er.effectiveFrom) roleEffectiveFrom,
date(es.effectiveFrom) salaryEffectiveFrom,
DATEDIFF(LEAST(COALESCE(LEAD(er.effectiveFrom)
OVER (PARTITION BY er.employeeId ORDER By er.effectiveFrom),
DATE_ADD(LAST_DAY(er.effectiveFrom),INTERVAL 1 DAY)),
DATE_ADD(LAST_DAY(er.effectiveFrom),INTERVAL 1 DAY)),
er.effectiveFrom) as '#Days' ,
ROUND((salary * 12) / 365, 2) dailyRate,
date(er.effectiveFrom) tmpFrom,
(select e2.effectiveFrom
from EmployeeRole e2
where e2.employeeId = er.employeeId and e2.effectiveFrom > er.effectiveFrom
order by e2.effectiveFrom
limit 1) as tmpTo
FROM EmployeeRole er
join EmployeeSalary es ON (es.employeeId = er.employeeId)
and er.employeeId = 19966
order by er.effectiveFrom
;
In above query I used a sub-select, which might hurt performance. You can study Window Function, and check if there is a function which suits your needs better than this sub-query.
It's up to you to calculate the number of days between those two columns, but you should also solve the NULL value which should be end of month (But I am not sure if I remember your problem correctly...)
see: DBFIDDLE

How to sum up records from starting month to current per month

I've searched for this topic but all I got was questions about grouping results by month. I need to retrieve rows grouped by month with summed up cost from start date to this whole month
Here is an example table
Date | Val
----------- | -----
2017-01-20 | 10
----------- | -----
2017-02-15 | 5
----------- | -----
2017-02-24 | 15
----------- | -----
2017-03-14 | 20
I need to get following output (date format is not the case):
2017-01-20 | 10
2017-02-24 | 30
2017-03-14 | 50
When I run
SELECT SUM(`val`) as `sum`, DATE(`date`) as `date` FROM table
AND `date` BETWEEN :startDate
AND :endDate GROUP BY year(`date`), month(`date`)
I got sum per month of course.
Nothing comes to my mind how to put in nicely in one query to achieve my desired effect, probably W will need to do some nested queries but maybe You know some better solution.
Something like this should work (untestet). You could also solve this by using subqueries, but i guess that would be more costly. In case you want to sort the result by the total value the subquery variant might be faster.
SET #total:=0;
SELECT
(#total := #total + q.sum) AS total, q.date
FROM
(SELECT SUM(`val`) as `sum`, DATE(`date`) as `date` FROM table
AND `date` BETWEEN :startDate
AND :endDate GROUP BY year(`date`), month(`date`)) AS q
You can use DATE_FORMAT function to both, format your query and group by.
DATE_FORMAT(date,format)
Formats the date value according to the format string.
SELECT Date, #total := #total + val as total
FROM
(select #total := 0) x,
(select Sum(Val) as Val, DATE_FORMAT(Date, '%m-%Y') as Date
FROM st where Date >= '2017-01-01' and Date <= '2017-12-31'
GROUP BY DATE_FORMAT(Date, '%m-%Y')) y
;
+---------+-------+
| Date | total |
+---------+-------+
| 01-2017 | 10 |
+---------+-------+
| 02-2017 | 30 |
+---------+-------+
| 03-2017 | 50 |
+---------+-------+
Can check it here: http://rextester.com/FOQO81166
Try this.
I use yearmonth as an integer (the year of the date multiplied by 100 plus the month of the date) . If you want to re-format, your call, but integers are always a bit faster.
It's the complete scenario, including input data.
CREATE TABLE tab (
dt DATE
, qty INT
);
INSERT INTO tab(dt,qty) VALUES( '2017-01-20',10);
INSERT INTO tab(dt,qty) VALUES( '2017-02-15', 5);
INSERT INTO tab(dt,qty) VALUES( '2017-02-24',15);
INSERT INTO tab(dt,qty) VALUES( '2017-03-14',20);
SELECT
yearmonths.yearmonth
, SUM(by_month.month_qty) AS running_qty
FROM (
SELECT DISTINCT
YEAR(dt) * 100 + MONTH(dt) AS yearmonth
FROM tab
) yearmonths
INNER JOIN (
SELECT
YEAR(dt) * 100 + MONTH(dt) AS yearmonth
, SUM(qty) AS month_qty
FROM tab
GROUP BY YEAR(dt) * 100 + MONTH(dt)
) by_month
ON yearmonths.yearmonth >= by_month.yearmonth
GROUP BY yearmonths.yearmonth
ORDER BY 1;
;
yearmonth|running_qty
201,701| 10.0
201,702| 30.0
201,703| 50.0
select succeeded; 3 rows fetched
Need explanations?
My solution has the advantage over the others that it will be re-usable without change when you move it to a more modern database - and you can convert it to using analytic functions when you have time.
Marco the Sane

SQL AVG of a SUM

I want to get the average of a calculated sum. I have tried the syntax from this stackoverflow answer So my SQL query looks like this:
SELECT AVG(iq.stockvalue_sum), iq.date
FROM(
SELECT CONCAT(DATE_FORMAT(s.date, '%Y'), '-01-01') as date,
SUM(GREATEST(s.stockvalue,0)) as stockvalue_sum
FROM stockvalues s
GROUP BY CONCAT(DATE_FORMAT(s.date, '%Y'), '-01-01')
) iq
However this is not giving me a correct average. I want to get the average stockvalue for each year. The idea behind the table is to save every day the stock and stockvalue for each product. So this specifiq query is to show the average stockvalue for each year it has data for.
Edit: Sample output data
Stockvalue | Year
_________________
- 205 | 2015
- 300 | 2014
Input data:
pid | val | date
______________________
- 1 | 100 | 28-04-2015
- 2 | 150 | 28-04-2015
- 1 | 80 | 27-04-2015
- 2 | 80 | 27-04-2015
....
- 1 | 100 | 29-01-2014
- 2 | 100 | 29-01-2014
- 1 | 200 | 30-01-2014
- 2 | 200 | 30-01-2014
So I need to calculate know the average of the total stockvalue. So the sum of all stockvalues for day X and the average of X
At minimum you are missing a group by in your outer query:
SELECT AVG(iq.stockvalue_sum), iq.date
FROM(
SELECT CONCAT(DATE_FORMAT(s.date, '%Y'), '-01-01') as date,
SUM(GREATEST(s.stockvalue,0)) as stockvalue_sum
FROM stockvalues s
GROUP BY CONCAT(DATE_FORMAT(s.date, '%Y'), '-01-01')
) iq
GROUP BY iq.date
However, given your inner query is returning a single year with a summed value, the average of that value would be the same. Perhaps you can clarify your intentions. Are you sure you need the inner query at all? Perhaps this is all you need?
select avg(GREATEST(stockvalue,0)), CONCAT(DATE_FORMAT(s.date, '%Y'), '-01-01') as date
from stockvalues
group by CONCAT(DATE_FORMAT(s.date, '%Y'), '-01-01')
I think you need to group your inner query by date, and your outer query by year to get the results you are after:
SELECT AVG(s.Stockvalue) AS Stockvalue
YEAR(s.Date) AS Date
FROM (
SELECT DATE(s.Date) AS Date,
SUM(GREATEST(s.stockvalue,0)) AS Stockvalue
FROM stockvalues AS s
GROUP BY DATE(s.Date)
) AS s
GROUP BY YEAR(s.Date);

Need to sum transaction totals from one table using customer information in another

I have spent the last hour looking for something I can use to implement here, but haven't found exactly what I need.
I have 2 tables: TRANSACTIONS & CUSTOMERS
CUSTOMER
internal_id | name | email
TRANSACTIONS
internal_id | customer_id | transaction_date | total_amount
I would like to cycle through all CUSTOMERS, then sum up the total TRANSACTIONS for each by month and year. I thought it would be as easy as just adding select statements as columns to the initial query, but that isn't working obviously:
NOT WORKING:
select customer.internal_id,
(sum(total_amount) as 'total' from TRANSACTIONS where transactions.customer_id = customer.internal_id and transaction_date >= DATE_SUB(NOW(),INTERVAL 1 month)),
(sum(total_amount) as 'total' from TRANSACTIONS where transactions.customer_id = customer.internal_id and transaction_date >= DATE_SUB(NOW(),INTERVAL 1 year))
from CUSTOMER join TRANSACTIONS on CUSTOMER.internal_id = TRANSACTIONS.customer_id
Basically I would like the output to look like this:
CUSTOMER.name | TRANSACTIONS.total_amount_month | TRANSACTIONS.total_amount_year
ABC Company | $335.00 | $8900.34
Is this possible with a single query? I have it implemented with multiple queries using PHP and would just prefer a single query if possible for performance sake.
Thanks!
SELECT c.name,
SUM(IF(transaction_date >= DATE__SUB(NOW(), INTERVAL 1 MONTH), total_amount, 0) AS total_amount_month,
SUM(total_amount) AS total_amount_year
FROM transactions AS t
JOIN customer AS c ON c.internal_id = t.customer_id
WHERE transaction_date >= DATE__SUB(NOW(), INTERVAL 1 YEAR
GROUP BY t.customer_id

Group BY product using MAX(price) or MAX(date) according to time interval

I've been searching for answers for 2 day and still nothing. Please, help me.
I have a database with products, product's prices and the date when this prices were registered:
product_id | price | date
-------------------------
1 | 8.95 | 2012-12-01
2 | 3.40 | 2012-12-01
1 | 9.05 | 2012-12-19
3 | 2.34 | 2012-12-24
3 | 2.15 | 2012-12-01
1 | 8.80 | 2012-12-19
1 | 8.99 | 2012-12-02
2 | 3.45 | 2012-12-02
Observe that is possible to have different price values for a product on the same day (rows 3 and 6). This is because there are many suppliers for a single product. There is a supplier column on database too, but I found it irrelevant for the solution. You can add it to the solution if I'm wrong.
Basically what I want is to write a query that returns two combined sets of data, as follow:
First set is made by minimum price of products inserted in the last month. As today is jan, 15, query should read rows 3, 4 and 6, apply the minimum price, and return only rows 4 and 6, both with minimum price for that product on the last month.
Second set is made by last products inserted, with no price registered on last month. i.e, for products not shown in the first set, query should search for the last inserted ones.
I hope that is clear. Ask me more if it isn't.
The query result for this database should be:
product_id | price | date
-------------------------
1 | 8.80 | 2012-12-19 <-Min price for product 1 on last month
3 | 2.34 | 2012-12-24 <-Min price for product 3 on last month
2 | 3.45 | 2012-12-02 <-No reg for product 2 on last month, show last reg.
I've tried everything: UNION, (DATE_SUB(CURDATE(), INTERVAL 1 MONTH), MIN(price), MAX(date) etc, etc. Nothing works. I don't know where to search now, please help me.
(SELECT product_id, MIN(price), date
FROM products
WHERE date + INTERVAL 1 MONTH > NOW()
GROUP BY product_id)
UNION
(SELECT product_id, price, MAX(date)
FROM products
WHERE product_id NOT IN (SELECT product_id
FROM products
WHERE date + INTERVAL 1 MONTH > NOW()
GROUP BY product_id)
GROUP BY product_id)
This should work but I'm not sure it's the most optimized way to do it.
something like this will do the trick:
SELECT * FROM (
SELECT DISTINCT b.product_id, IF (c.min IS NULL,(SELECT ROUND(e.price,2) FROM products AS e WHERE e.product_id = b.product_id ORDER BY e.date DESC LIMIT 1 ),c.min) AS min, IF (c.date IS NULL,(SELECT f.date FROM products AS f WHERE f.product_id = b.product_id ORDER BY f.date DESC LIMIT 1 ),c.date) AS date, IF(c.min IS NULL,'<-No reg for product 2 on last month, show last reg.','<-Min price for product 1 on last month') as text FROM products AS b
LEFT JOIN
(SELECT a.product_id, round(MIN(a.price),2) AS min, a.date FROM products AS a WHERE a.date BETWEEN DATE_SUB(CURDATE(), INTERVAL 1 MONTH) AND CURDATE() GROUP BY a.product_id) AS c
ON (b.product_id = c.product_id)
) AS d
ORDER BY d.text, d.product_id
Gives output:
product_id|min|date|text
1|8.80|2012-12-19|<-Min price for product 1 on last month
3|2.34|2012-12-24|<-Min price for product 1 on last month
2|3.45|2012-12-02|<-No reg for product 2 on last month, show last reg.
Break it down into several sub-queries:
Products with prices in the last month, min price
join in date for that price
UNION
Products with no-prices in the last month, max date
join in price on that date
SQL Fiddle
Here
Query
SELECT MINPRICE.product_id, P.date, MINPRICE.price
FROM
(
-- Min price in last 31 days
SELECT product_id, MIN(price) AS price
FROM Prices
WHERE DATEDIFF(CURDATE(), date) < 31
GROUP BY product_id
) MINPRICE
-- Join in to get the date that the price occured on
INNER JOIN Prices P ON
P.product_id = MINPRICE.product_id
AND
P.price = MINPRICE.price
UNION
SELECT MAXDATE.product_id, MAXDATE.date, P.price
FROM
(
-- Product with no price in last 31 days - get most recent date
SELECT product_id, MAX(date) AS date
FROM Prices
WHERE product_id NOT IN
(
SELECT product_id
FROM Prices
WHERE DATEDIFF(CURDATE(), date) < 31
)
) MAXDATE
-- join in price on that date
INNER JOIN Prices P ON
P.product_id = MAXDATE.product_id
AND
P.date = MAXDATE.date
Not that I tested but you can try...
SELECT * FROM
(SELECT * FROM (
SELECT * FROM table ORDER BY date DESC)
as tmp GROUP BY product_id) t1
LEFT JOIN
(SELECT * FROM (
SELECT * FROM table WHERE date => CURDATE() ORDER BY price)
as tmp2 GROUP BY product_id) t2
ON t1.product_id = t2.product_id