Get last row for given dates if date doesn't exist - mysql

How can we query a set of records to get data for particular dates where there might be gaps in dates.
Example Data
date | Price
------------------
2018-03-31 | 115
2018-03-29 | 114
2018-03-28 | 113
...
2017-03-29 | 117
2017-03-28 | 118
...
2016-12-30 | 143
2016-12-29 | 140
...
2015-12-31 | 110
2015-12-30 | 111
Required Data for dates: 2018-03-31, 2017-03-31, 2016-12-31, 2015-12-31
date | Price
------------------
2018-03-31 | 115
2017-03-31 | 117
2016-12-31 | 143
2015-12-31 | 110

You can do this with correlated sub query. The following will return the price for the exact date or the closest prior date:
SELECT dates.dt, (
SELECT price
FROM t
WHERE date <= dates.dt
ORDER BY date DESC
LIMIT 1
) AS price
FROM (
SELECT '2018-03-31' AS dt UNION ALL
SELECT '2017-03-31' UNION ALL
SELECT '2016-12-31' UNION ALL
SELECT '2015-12-31'
) AS dates
Demo on db<>fiddle

You can use MySQL Last day function and date_format to achieve the desired result. Check out the query :-
select last_day(a11.d_date), a11.price
from test a11
join
(select MAX(d_date) d_date, DATE_FORMAT(d_date, "%M %Y")
from test
group by DATE_FORMAT(d_date, "%M %Y")
) a12
on a11.d_date = a12.d_date
SQL Fiddle

seem you need the max price for year
select max(date), max(price)
from my_table m
inner join (
select max(date), year(date) my_year
from my_table
group by year(date)
) t on t.my_year = year(m.date)
group by year(m.date)

Related

get total amount of expenses per month in MySQL

I am currently working with 2 tables, expenses and income. To keep the structure simple and can see it, this is the fiddle: http://sqlfiddle.com/#!9/256cd64/2
The result I need from my query is the total amount for each month of the current year, for this and tried something like this:
select sum(e.amount) as expense, DATE_FORMAT(e.date,'%m') as month
from expenses e
where year(e.date) = 2019
group by month
My problem with this is that it only takes me the months where there was registration and I would like it to take 12 months whether or not they have a registration, in the case that they did not return 0 as a total amount.
At the moment I am working with the table of expenses but I would like to have a single query that returns the monthly expenses and income, this is an example of the final output that I would like to obtain:
| Month | Expense| Incomes |
|---------|--------|---------|
| 01| 0 | 0 |
| 02| 3000 | 4000 |
| 03| 1500 | 5430 |
| 04| 2430 | 2000 |
| 05| 2430 | 1000 |
| 06| 2430 | 1340 |
| 07| 0 | 5500 |
| 08| 2430 | 2000 |
| 09| 1230 | 2000 |
| 10| 8730 | 2000 |
| 11| 2430 | 2000 |
| 12| 6540 | 2000 |
You need to generate the month values and then use left join to match to expenses:
select coalesce(sum(e.amount), 0) as expense, m.month
from (select '01' as month union all
select '02' as month union all
select '03' as month union all
select '04' as month union all
select '05' as month union all
select '06' as month union all
select '07' as month union all
select '08' as month union all
select '09' as month union all
select '10' as month union all
select '11' as month union all
select '12' as month
) m left join
expenses e
on year(e.date) = 2019 and
DATE_FORMAT(e.date,'%m') = m.month
group by m.month;
Here is a db<>fiddle.
As for income, you should ask another question about that.
You can use MONTH to get month value from your date column and then GROUP BY them to get your desired output as below-
SELECT SUM(e.amount) AS expense,
MONTH(e.date) AS month
FROM expenses e
WHERE YEAR(e.date) = 2019
GROUP BY MONTH(e.date)
Try changing your sum(e.amount) as expense to: COALESCE(sum(e.amount),0) as expense
The COALESCE function returns the first non NULL value.
SELECT
t1.month,
COALESCE(t2.amount, 0) AS expenses,
COALESCE(t3.amount, 0) AS incomes
FROM
(
SELECT 1 AS month UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4 UNION ALL
SELECT 5 UNION ALL
SELECT 6 UNION ALL
SELECT 7 UNION ALL
SELECT 8 UNION ALL
SELECT 9 UNION ALL
SELECT 10 UNION ALL
SELECT 11 UNION ALL
SELECT 12
) t1
LEFT JOIN
(
SELECT MONTH(date) AS month, SUM(amount) AS amount
FROM expenses
GROUP BY MONTH(date)
) t2
ON t1.month = t2.month
LEFT JOIN
(
SELECT MONTH(date) AS month, SUM(amount) AS amount
FROM incomes
GROUP BY MONTH(date)
) t3
ON t1.month = t3.month
ORDER BY
t1.month;

Summarizing data by current and previous month from one table

I am looking to summarize data by this and previous month. My table looks like this:
Date Client Amount
2019-06-02 111 100
2019-06-03 111 90
2019-06-22 222 80
2019-07-02 111 110
2019-07-03 111 120
Expected result:
Client This_month Previous_month
111 230 190
222 80
Thank you for any hints how to do it.
You can group by client and use conditional aggregation:
select
client,
sum(case when last_day(date) = last_day(current_date) then amount end) This_month,
sum(case when last_day(date) = last_day(current_date - INTERVAL 1 MONTH) then amount end) Previous_month
from tablename
group by client
See the demo.
Results:
| client | This_month | Previous_month |
| ------ | ---------- | -------------- |
| 111 | 230 | 190 |
| 222 | | 80 |
You could try using a pair of left join
select a.Client, b.act_amount, c.prev_amount
from (
select distinct client
from my_table
) a
left join (
select month(date) act_mont, client, sum(amount) act_amount
from my_table
where month(date) = month(curdate)
group by month(date), client
) b on a.client = b.client
left join (
select month(date) prev_mont, client, sum(amount) prev_amount
from my_table
where month(date) = month(curdate) -1
group by month(date), client
) c on a.client = c.client

Getting last values for week in mysql

I would like know how to get the last value for each week.
Let's say I have the next values
-- Table 1 --
day value
2018-03-12 32
2018-02-14 42
2018-03-16 62
2018-03-19 82
2018-03-20 92
2018-03-21 102
2018-03-27 112
2018-03-28 122
2018-03-29 132
How can I get the next values which are the last values for each week. Assuming the week start on Monday.
Day Value
2018-03-16 62
2018-03-21 102
2018-03-29 132
I have everything settled here SQL Fiddle
You can get the week number of day then get the max value per week number.
select t1.*
from table1 t1
join (
select week(day) as wknum,
max(day) as day
from table1
group by week(day)
) t2
on t1.day=t2.day
Result:
day value
2018-03-16 62
2018-03-21 102
2018-03-29 132
You can group by YEARWEEK()
create table tbl (day date, value int);
✓
insert into tbl values
('2018-03-12', 32),
('2018-02-14', 42),
('2018-03-16', 62),
('2018-03-19', 82),
('2018-03-20', 92),
('2018-03-21', 102),
('2018-03-27', 112),
('2018-03-28', 122),
('2018-03-29', 132);
✓
select day, yearweek(day) from tbl;
day | yearweek(day)
:--------- | ------------:
2018-03-12 | 201810
2018-02-14 | 201806
2018-03-16 | 201810
2018-03-19 | 201811
2018-03-20 | 201811
2018-03-21 | 201811
2018-03-27 | 201812
2018-03-28 | 201812
2018-03-29 | 201812
select day, value
from tbl
join (select max(day) mday
from tbl
group by yearweek(day)) t
on day = mday
day | value
:--------- | ----:
2018-02-14 | 42
2018-03-16 | 62
2018-03-21 | 102
2018-03-29 | 132
dbfiddle here
This solution uses window functions and picks the latest date within the week.
https://dev.mysql.com/doc/refman/8.0/en/window-functions-usage.html
I use SQL Server, but I believe this is the MySQL equivalent:
with cte AS (
SELECT *, ROW_NUMBER() OVER(PARTITION BY WEEKOFYEAR([day]) ORDER BY DAYOFWEEK([day]) DESC) AS counter_
from #table1
)
SELECT [day], [value]
FROM cte
WHERE counter_ = 1
Here's how you would do it in SQL Server - Use mysql equivalent
select b.day, b.value from (
select datepart(ww,day) a, max(day) b
from yourtable
group by datepart(ww,day))a
join yourtable b on a.a=datepart(ww,b.day) and a.b=b.day
Try this FIDDLE:
= Order by the closest to the end of every week
= Group by week
SELECT day, value
FROM (SELECT * FROM table1 ORDER BY DATEDIFF(day + INTERVAL 6 - weekday(day) DAY, day) ASC) t
GROUP BY week(day);

Single query to retrieve multiple values from multiple tables

Expenses table
1/1/2016 exp1 2000
13/1/2016 exp11 2500
1/2/2016 exp2 1500
1/3/2016 exp3 1000
10/3/2016 exp1 2000
Income table
1/1/2016 income1 2500
1/2/2016 income2 3500
1/3/2016 income3 1500
10/3/2016 income3 1000
1/4/2016 income4 5000
From single query what I need is group by month, this is what I need
Expenses Incomes Month
4500 2500 Jan
1500 3500 Feb
3000 2500 Mar
0 5000 April
I need the above query to show the data in Google graph
Terrible data structure and format, but not impossible:
SELECT
IFNULL(exp.Expenses,0) Expenses,
IFNULL(inc.Incomes,0) Incomes,
inc.`monthname` `Month`
FROM
(
SELECT
SUM(i.amount) Incomes,
MONTHNAME(STR_TO_DATE(i.`date`, '%d/%m/%Y')) `monthname`,
MONTH(STR_TO_DATE(i.`date`, '%d/%m/%Y')) `month`
FROM
incomes i
GROUP BY
MONTHNAME(STR_TO_DATE(i.`date`, '%d/%m/%Y')),
MONTH(STR_TO_DATE(i.`date`, '%d/%m/%Y'))
) inc
LEFT JOIN
(
SELECT
SUM(e.amount) Expenses,
MONTHNAME(STR_TO_DATE(e.`date`, '%d/%m/%Y')) `monthname`,
MONTH(STR_TO_DATE(e.`date`, '%d/%m/%Y')) `month`
FROM
expenses e
GROUP BY
MONTHNAME(STR_TO_DATE(e.`date`, '%d/%m/%Y')),
MONTH(STR_TO_DATE(e.`date`, '%d/%m/%Y'))
) exp
ON exp.`month` = inc.`month`
ORDER BY
inc.`month`
Output of this simplicity:
+----------+---------+----------+
| Expenses | Incomes | Month |
+----------+---------+----------+
| 4500 | 2500 | January |
| 1500 | 3500 | February |
| 3000 | 2500 | March |
| 0 | 5000 | April |
+----------+---------+----------+
4 rows in set
Anyway better thing seriously how to improve and normalize your data.
In my solution, I give the number of the month rather than text. I'll leave it to you to convert it to text (using a CASE expression, for example) if you wish:
SELECT
sum(expense) AS total_expense, sum(income) AS total_income, trans_month
FROM (
SELECT
month(trans_date) AS trans_month,
0 AS income,
sum(amount) AS expense
FROM expense
GROUP BY month(trans_date)
UNION ALL
SELECT
month(trans_date) AS trans_month,
sum(amount) AS income,
0 AS expense
FROM income
GROUP BY month(trans_date)
) AS a
GROUP BY trans_month;

mysql select date from DB where NOW() between t1.from and t1.to with t1.to not defined

I have a table (t1) with:
id | from | to | item | price |
1 2014-03-14 00:00:00 2014-03-26 00:00:00 25 75
2 2014-03-27 00:00:00 NULL 25 50
3 2014-03-27 00:00:00 2014-04-01 00:00:00 26 80
4 2014-04-02 00:00:00 NULL 26 100
Now in case of item = 25 I'd like to select t1.id = 2. In this case the date is betwwen 2014-03-27 and NULL because it is not know when this price will end.
In case of item = 26 I'd like to get t1.id = 3 until the 1st of April and the starting from the 2nd of April t1.id = 4.
How should I write my query?
EDIT:
Idea for query:
SELECT `from`, IF(`to` IS NOT NULL, `to`, NOW()), price, item
FROM t1
WHERE NOW() BETWEEN `from`
AND IF(`to` IS NOT NULL,
`to`,
NOW()
)
ORDER BY item
Expected results today (31st of March):
id | from | to | item | price |
2 2014-03-27 00:00:00 NULL 25 50
3 2014-03-27 00:00:00 2014-04-01 00:00:00 26 80
Expected results on the 3rd of April:
id | from | to | item | price |
2 2014-03-27 00:00:00 NULL 25 50
4 2014-04-02 00:00:00 NULL 26 100
You can just include column to and avoid if expression in the select statement.
SELECT
id, `from`, `to`, price, item
FROM t1
WHERE NOW() BETWEEN
`from` AND IF(`to` IS NOT NULL, `to`, NOW() )
ORDER BY item;
And for any future target date records,
set #future_date := now() + interval 3 day;
SELECT
id, `from`, `to`, price, item, #future_date
FROM t1
WHERE #future_date BETWEEN `from`
AND IF(`to` IS NOT NULL, `to`, #future_date )
ORDER BY item;
Demo # SQL Fiddle
This is how it works
SELECT `from`, `to`, item, price
FROM t1
WHERE NOW() BETWEEN `from` AND `to`
OR `from` <= NOW() AND ISNULL(`to`)
ORDER BY item;
And to proof that it works in the future here's another query for 3rd of April
SELECT `from`, `to`, item, price
FROM t1
WHERE '2014-04-03' BETWEEN `from` AND `to`
OR `from` <= '2014-04-03' AND ISNULL(`to`)
ORDER BY item;
Here's my SQL Fiddle
Everything's clear?