How to find difference in same column applying `group by` in SQL? - mysql

I need to find the difference between the points grouping by Id column.
Id | Year | Points
---+------+-------
1 | 2017 | 10
1 | 2018 | 20
2 | 2017 | 13
2 | 2018 | 16
3 | 2017 | 25
3 | 2018 | 20
Expected result:
Id | Points
---+-------
1 | 10
2 | 3
3 | -5

do aggregation
select
id,sum(case when year = 2018 then points end) -sum(case when year = 2017 then points end) as diff
from tablename group by id

If you want the difference between the years, you don't need group by:
select t2017.id, t2017.points as points_2017,
t2018.points as points_2018,
(t2018.points - t2017.points) as diff
from t t2017 join
t t2018
on t2017.id = t2018.id and
t2017.year = 2017 and
t2018.year = 2018;
You can do something very similar with conditional aggregation:
select id,
sum(case when year = 2017 then points end) as points_2017,
sum(case when year = 2018 then points end) as points_2018,
(sum(case when year = 2018 then points end) -
sum(case when year = 2017 then points end)
) as diff
from t
group by id;

SELECT *,
points - LAG(points) OVER ( PARTITION BY id
ORDER BY year ) delta_to_prev
FROM sourcetable
PS. Needs MySQL 8+.

Related

Records added since specific month this year in SQL

I have a table which contains the following data
userId | name | from
1 | aaa | 2020-09-23
2 | bbb | 2020-09-01
3 | ccc | 2019-05-12
4 | ddd | 2019-06-01
5 | eee | 2018-06-23
6 | fff | 2018-07-23
It is for an educational purpose and therefore the year runs from September to August rather than January to December. How do I output all of the users who were added since the previous September (so if it's October 2020 then 1 month ago, if it's January 2021 then 4 months ago)? The query has to be relative so that it always outputs the previous September to the time that the query is run rather than a specific September.
The result of the query should be
bbb 2020-09-01
aaa 2020-09-23
With NOT EXISTS:
select t.* from tablename t
where t.`from` >= concat(year(current_date), '-09-01')
and not exists (
select 1 from tablename
where name = t.name
and `from` between
concat(year(current_date), '-09-01') - interval 1 year
and
concat(year(current_date), '-09-01') - interval 1 day
)
Or maybe:
select t.* from tablename t
where t.`from` >= concat(year(current_date) - (month(current_date) < 9), '-09-01')
and not exists (
select 1 from tablename
where name = t.name
and `from` between
concat(year(current_date) - (month(current_date) < 9), '-09-01') - interval 1 year
and
concat(year(current_date) - (month(current_date) < 9), '-09-01') - interval 1 day
)
so if you execute the query in say March 2021, you will get the correct results that compare the current educational year with the previous one.
See the demo.
Results:
> userId | name | from
> -----: | :--- | :---------
> 1 | aaa | 2020-09-23
> 2 | bbb | 2020-09-01
You can subtract 9 months and compare to the year:
where year(`from` - interval 9 month) = year(curdate() - interval 9 month)
Actually, you might want year(curdate()) +/- 1 depending on how you are identifying the year.

Mysql: Fill column with consecutive numbers of days in a month

I have this table: "sales"
+-------------+---------+
| date | total |
+-------------+---------+
| 2018-12-04 | 269.10 |
| 2018-12-05 | 29.00 |
| 2018-12-06 | 107.10 |
| 2018-12-06 | 34.00 |
| 2018-12-08 | 69.50 |
| 2018-12-08 | 223.00 |
| 2018-12-08 | 68.00 |
| 2018-12-09 | 99.00 |
| 2018-12-10 | 59.50 |
| ... | ... |
+-------------+---------+
I'm trying this query
SELECT DAY(date) AS Days,
SUM(CASE WHEN MONTH(date) = 12 THEN total ELSE NULL END) AS December
FROM sales WHERE YEAR(date) = 2018 GROUP BY date
And I get
+-------+----------+
| Days | December |
+-------+----------+
| 4 | 269.10 |
| 5 | 29.00 |
| 6 | 141.10 |
| 8 | 360.50 |
| 9 | 99.00 |
| 10 | 59.50 |
| ... | ... |
+-------+----------+
But I want consecutive days like this:
+-------+----------+
| Days | December |
+-------+----------+
| 1 | NULL |
| 2 | NULL |
| 3 | NULL |
| 4 | 269.10 |
| 5 | 29.00 |
| 6 | 141.10 |
| 7 | NULL |
| 8 | 360.50 |
| 9 | 99.00 |
| 10 | 59.50 |
| ... | ... |
| 31 | 123.00 |
+-------+----------+
Can you help me plss..
PS: I have several months and years in "date" column from "sales" table.
This recursive CTE generates a list of dates corresponding to the month and year specified in the doi CTE, and then LEFT JOINs that to the sales table to get the sales for that month. It will work for any month/year, just change the values in the doi CTE, and the title of the SUM column (currently December) to suit.
WITH RECURSIVE doi AS (
SELECT 12 AS month,
2018 AS year
),
cte AS (
SELECT DATE(CONCAT_WS('-', year, month, 1)) AS date
FROM doi
UNION ALL
SELECT date + INTERVAL 1 DAY
FROM cte
WHERE date < LAST_DAY(date)
)
SELECT DAY(cte.date) AS Days,
ROUND(SUM(s.total),2) AS December
FROM cte
LEFT JOIN sales s ON s.date = cte.date
GROUP BY cte.date
ORDER BY cte.date
Output is too long to show here but can be seen at this demo on dbfiddle
Update
To expand this query to cover an entire year requires changing the approach slightly in terms of generating an entire year's worth of dates, and then using conditional aggregation to get the sums for each day of each month:
WITH RECURSIVE doi AS (
SELECT 2018 AS year
),
cte AS (
SELECT DATE(CONCAT_WS('-', year, 1, 1)) AS date
FROM doi
UNION ALL
SELECT date + INTERVAL 1 DAY
FROM cte
CROSS JOIN doi
WHERE date < DATE(CONCAT_WS('-', doi.year, 12, 31))
)
SELECT DAY(cte.date) AS Days,
ROUND(SUM(CASE WHEN MONTH(s.date) = 1 THEN s.total END),2) AS January,
ROUND(SUM(CASE WHEN MONTH(s.date) = 2 THEN s.total END),2) AS February,
ROUND(SUM(CASE WHEN MONTH(s.date) = 3 THEN s.total END),2) AS March,
ROUND(SUM(CASE WHEN MONTH(s.date) = 4 THEN s.total END),2) AS April,
ROUND(SUM(CASE WHEN MONTH(s.date) = 5 THEN s.total END),2) AS May,
ROUND(SUM(CASE WHEN MONTH(s.date) = 6 THEN s.total END),2) AS June,
ROUND(SUM(CASE WHEN MONTH(s.date) = 7 THEN s.total END),2) AS July,
ROUND(SUM(CASE WHEN MONTH(s.date) = 8 THEN s.total END),2) AS August,
ROUND(SUM(CASE WHEN MONTH(s.date) = 9 THEN s.total END),2) AS September,
ROUND(SUM(CASE WHEN MONTH(s.date) = 10 THEN s.total END),2) AS October,
ROUND(SUM(CASE WHEN MONTH(s.date) = 11 THEN s.total END),2) AS November,
ROUND(SUM(CASE WHEN MONTH(s.date) = 12 THEN s.total END),2) AS December
FROM cte
LEFT JOIN sales s ON s.date = cte.date
GROUP BY DAY(cte.date)
ORDER BY DAY(cte.date)
Demo on dbfiddle
generate your months using union and do right join
select t1.d as Days
, sum(iif(month(date) = 12, total, null) as December
from sales
right join (select 1 as d
union select 2 union select 3 union select 4 union select 5 union select 6
union select 7 union select 8 union select 9 union select 10 union select 11
.... ) as t1 on t1.d = day(date)
where year(date) = 2012
group by date
if you are using mysql v8.0, you can use recursive queries.
with recursive cte as(
select 1 as d
union all
select d + 1 from cte where d < day(last_day('2019-12-01'))
)
select coalesce(day(s.date), t1.d) as Days
, sum(iif(month(s.date) = 12, total, null) as December
from sales s
right join cte as t1 on t1.d = day(s.date)
where year(date) = 2012
group by coalesce(day(s.date), t1.d)

MySQL record between fixed date range

I have an small application which was build with CodeIgniter 3 and need to perform a report which will be converted to Chart.js. The report should be in yearly basis but at given specific date every month. The requirement are all data count must be from 4th to 3rd monthly. Like this:
For example January Report would be from 4th January to 3rd February, 4th February to 3rd March,... and so on.
I have created a MySQL query but I'm stuck on how to get the date too date. My Query are as follows:
SELECT DATE_FORMAT(odd_date_created, '%Y') as 'year',
DATE_FORMAT(odd_date_created, '%m') as 'month',
COUNT(odd_id) as 'total', status
FROM odd_data
WHERE status = $id and
GROUP BY DATE_FORMAT(odd_date_created, '%Y%m'), status
I'm new to MySQl. Could somebody help me on this. I'm stuck where should I put the date to date query.
Firstly I want to caution you not to use "between" with the following when you come to join your data, use this method instead data.date >= r.period_start_dt and data.date < r.period_end_dt
Secondly I am assuming your data does have dates or timestamps and that will fall between the calculated ranges that follow:
set #year :=2017;
select
*
from (
select
start_dt + INTERVAL m.n MONTH period_start_dt
, start_dt + INTERVAL m.n + 1 MONTH period_end_dt
from (
select str_to_date(concat(#year,'-01-04'),'%Y-%m-%d') start_dt ) seed
cross join (select 0 n union all
select 1 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
) m
) r
## LEFT JOIN YOUR DATA
## ON data.date >= r.period_start_dt and data.date < r.period_end_dt
Example ranges: (produce you own at this demo: http://rextester.com/CHTKSA95303 )
nb dd.mm.yyyy (.de format)
+----+---------------------+---------------------+
| | period_start_dt | period_end_dt |
+----+---------------------+---------------------+
| 1 | 04.01.2017 00:00:00 | 04.02.2017 00:00:00 |
| 2 | 04.02.2017 00:00:00 | 04.03.2017 00:00:00 |
| 3 | 04.03.2017 00:00:00 | 04.04.2017 00:00:00 |
| 4 | 04.04.2017 00:00:00 | 04.05.2017 00:00:00 |
| 5 | 04.05.2017 00:00:00 | 04.06.2017 00:00:00 |
| 6 | 04.06.2017 00:00:00 | 04.07.2017 00:00:00 |
| 7 | 04.07.2017 00:00:00 | 04.08.2017 00:00:00 |
| 8 | 04.08.2017 00:00:00 | 04.09.2017 00:00:00 |
| 9 | 04.09.2017 00:00:00 | 04.10.2017 00:00:00 |
| 10 | 04.10.2017 00:00:00 | 04.11.2017 00:00:00 |
| 11 | 04.11.2017 00:00:00 | 04.12.2017 00:00:00 |
| 12 | 04.12.2017 00:00:00 | 04.01.2018 00:00:00 |
+----+---------------------+---------------------+
Given the specification, I think I would tempted to cheat it... subtract 3 days from the date. Doing that, Jan 4 backs up to Jan 1, Feb 3 backs up to Jan 31... so those all end up as January.
SELECT DATE_FORMAT(odd_date_created + INTERVAL -3 DAY, '%Y') AS `year`
, DATE_FORMAT(odd_date_created + INTERVAL -3 DAY, '%m') AS `month`
, ...
FROM ...
GROUP
BY DATE_FORMAT(odd_date_created + INTERVAL -3 DAY, '%Y')
, DATE_FORMAT(odd_date_created + INTERVAL -3 DAY, '%m')
This falls apart if there's oddball ranges... if it's not always the 4th and 3rd.

Sum Column Value

I Have query result like this , Select Type,Jan,FEB From TableName :
+-----------+------+------+
| Type | JAN | FEB |
+-----------+------+------+
| MATIC | 137 | 128 |
| MOPED | 41 | 23 |
| MOPED PRM | 8 | 9 |
| SPORT | 55 | 62 |
+-----------+------+------+
4 rows in set (1.23 sec)
I want to add up the columns jan and columns feb, be like this
+-----------+------+------+------+
| Type | JAN | FEB | TOT |
+-----------+------+------+------+
| MATIC | 137 | 128 | 165 |
| MOPED | 41 | 23 | 64 |
| MOPED PRM | 8 | 9 | 17 |
| SPORT | 55 | 62 | 117 |
+-----------+------+------+------+
4 rows in set (1.23 sec)
is there a command such as
select type, jan, feb, sum (jan) + sum (feb) As TOT
From Table Name
This is my syntax
Select SubTbl2.jenis,
sum(If(Month = 1 and Year = 2013 ,Result,0)) as 'JAN',
sum(If(Month = 2 and Year = 2013,Result,0)) as 'FEB'
From (
select SubTbl1.jenis,month(SubTbl1.TglAmb) as Month,
year(SubTbl1.tglAmb) as Year,
count(SubTbl1.jenis) as Result
From (
select jual_leasing.TglAmb,jual_leasing.NoMsn,typemotor.jenis
,lokasi.lokasi3s
from jual_leasing
left join typeconvert
on jual_leasing.typeK = typeconvert.typesystem
left join typemotor
on typeconvert.typeconv = typemotor.type
left join lokasi
on jual_leasing.kodelok = lokasi.kodelokasi
where
lokasi.lokasi3S = 'YMS PURWODADI 3S'
group by jual_leasing.NoMsn)
as SubTbl1
group by SubTbl1.jenis,Month,Year )
as SubTbl2 group by SubTbl2.jenis;
The result is on Top , i try to add script on line 4 , with
JAN + FEB as TOT
but there is stil warning :
ERROR 1054 (42S22): Unknown column 'JAN' in 'field list'
Tq For advanced
To obtain tour desired output, simply do:
select type, jan, feb, jan + feb As TOT
from TableName
You do not need SUM, that is designed to add the values of different rows.
edit For your actual query, yes you can add two sums. Also, the following should work for you:
sum(If((Month = 1 or Month = 2) and Year = 2013 ,Result,0)) as 'TOT',
The MySQL documentation on arithmetic functions indicates that + is indeed the addition operator.
So, your query would be:
select type, jan, feb, jan + feb from table
Sum works over columns and not rows; you'd use this if you wanted the sum of both columns added together.
P.S. 137 + 128 = 265
I think you will find this question is getting down-voted because it is not really about programming - this is more of a general SQL syntax question. That being said, I think you should just be able to sum your values:
select type, jan, feb, jan + feb As TOT From Table Name
Just change the beginning of your query with this :
Select SubTbl2.jenis,
sum(If(Month = 1 and Year = 2013 ,Result,0)) as 'JAN',
sum(If(Month = 2 and Year = 2013,Result,0)) as 'FEB',
sum(If(Month = 1 and Year = 2013 ,Result,0)) +
sum(If(Month = 2 and Year = 2013,Result,0)) as 'TOT'
From (
Or
Select SubTbl2.jenis,
sum(If(Month = 1 and Year = 2013 ,Result,0)) as 'JAN',
sum(If(Month = 2 and Year = 2013,Result,0)) as 'FEB',
sum(If((Month = 1 or Month = 2) and Year = 2013,Result,0)) as 'TOT'
From (

Select highest 3 scores in each day for every user

I have a MYSQL table like this:
id | userid | score | datestamp |
-----------------------------------------------------
1 | 1 | 5 | 2012-12-06 03:55:16
2 | 2 | 0,5 | 2012-12-06 04:25:21
3 | 1 | 7 | 2012-12-06 04:35:33
4 | 3 | 12 | 2012-12-06 04:55:45
5 | 2 | 22 | 2012-12-06 05:25:11
6 | 1 | 16,5 | 2012-12-06 05:55:21
7 | 1 | 19 | 2012-12-06 13:55:16
8 | 2 | 8,5 | 2012-12-07 06:27:16
9 | 2 | 7,5 | 2012-12-07 08:33:16
10 | 1 | 10 | 2012-12-07 09:25:19
11 | 1 | 6,5 | 2012-12-07 13:33:16
12 | 3 | 6 | 2012-12-07 15:45:44
13 | 2 | 4 | 2012-12-07 16:05:16
14 | 2 | 34 | 2012-12-07 18:33:55
15 | 2 | 22 | 2012-12-07 18:42:11
I would like to display user scores like this:
if a user on a certain day has more than 3 scores it would get only highest 3, repeat that for every day for this user and then add all days together. I want to display this sum for every user.
EDIT:
So in the example above for user 1 on 06.12. I would add top 3 scores together and ignore 4th score, then add to that number top 3 from the next day and so on. I need that number for every user.
EDIT 2:
Expected output is:
userid | score
--------------------
1 | 59 //19 + 16.5 + 7 (06.12.) + 10 + 6.5 (07.12.)
2 | 87 //22 + 0.5 (06.12.) + 34 + 22 + 8.5 (07.12.)
3 | 18 //12 (06.12.) + 6 (07.12.)
I hope this is more clear :)
I would really appreciate the help because I am stuck.
Please take a look at the following code, if your answer to my comment is yes :) Since your data all in 2012, and month of november, I took day.
SQLFIDDLE sample
Query:
select y.id, y.userid, y.score, y.datestamp
from (select id, userid, score, datestamp
from scores
group by day(datestamp)) as y
where (select count(*)
from (select id, userid, score, datestamp
from scores group by day(datestamp)) as x
where y.score >= x.score
and y.userid = x.userid
) =1 -- Top 3rd, 2nd, 1st
order by y.score desc
;
Results:
ID USERID SCORE DATESTAMP
8 2 8.5 December, 07 2012 00:00:00+0000
20 3 6 December, 08 2012 00:00:00+0000
1 1 5 December, 06 2012 00:00:00+0000
Based on your latter updates to question.
If you need some per user by year/month/day and then find highest, you may simply add aggregation function like sum to the above query. I am reapeating myself, since your sample data is for just one year, there's no point group by year or month. That's why I took day.
select y.id, y.userid, y.score, y.datestamp
from (select id, userid, sum(score) as score,
datestamp
from scores
group by userid, day(datestamp)) as y
where (select count(*)
from (select id, userid, sum(score) as score
, datestamp
from scores
group by userid, day(datestamp)) as x
where y.score >= x.score
and y.userid = x.userid
) =1 -- Top 3rd, 2nd, 1st
order by y.score desc
;
Results based on sum:
ID USERID SCORE DATESTAMP
1 1 47.5 December, 06 2012 00:00:00+0000
8 2 16 December, 07 2012 00:00:00+0000
20 3 6 December, 08 2012 00:00:00+0000
UPDATED WITH NEW SOURCE DATA SAMPLE
Simon, please take a look at my own sample. As your data was changing, I used mine.
Here is the reference. I have used pure ansi style without any over partition or dense_rank.
Also note the data I used are getting top 2 not top 3 scores. You can change is accordingly.
Guess what, the answer is 10 times simpler than the first impression your first data gave....
SQLFIDDLE
Query to 1:
-- for top 2 sum by user by each day
SELECT userid, sum(Score), datestamp
FROM scores t1
where 2 >=
(SELECT count(*)
from scores t2
where t1.score <= t2.score
and t1.userid = t2.userid
and day(t1.datestamp) = day(t2.datestamp)
order by t2.score desc)
group by userid, datestamp
;
Results for query 1:
USERID SUM(SCORE) DATESTAMP
1 70 December, 06 2012 00:00:00+0000
1 30 December, 07 2012 00:00:00+0000
2 22 December, 06 2012 00:00:00+0000
2 25 December, 07 2012 00:00:00+0000
3 30 December, 06 2012 00:00:00+0000
3 30 December, 07 2012 00:00:00+0000
Final Query:
-- for all two days top 2 sum by user
SELECT userid, sum(Score)
FROM scores t1
where 2 >=
(SELECT count(*)
from scores t2
where t1.score <= t2.score
and t1.userid = t2.userid
and day(t1.datestamp) = day(t2.datestamp)
order by t2.score desc)
group by userid
;
Final Results:
USERID SUM(SCORE)
1 100
2 47
3 60
Here goes a snapshot of direct calculations of data I used.
SELECT
*
FROM
table1
LEFT JOIN
(SELECT * FROM table1 ORDER BY score LIMIT 3) as lr on DATE(lr.datestamp) = DATE(table1.datastamp)
GROUP BY
datestamp