GROUP BY using substring? - mysql

I have the following table:
t1:
id item
--------------------------
XR-2017-432 car
XR-2019-736 car
BC-2017-847 motorbike
XR-2017-937 car
BC-2020-758 motorbike
GK-2019-823 boat
GK-2017-928 boat
XR-2018-438 car
And I want to count the number of occurences of each year.
The answer of the query should be this:
result:
year count
-------------------
2017 4
2018 1
2019 2
2020 1
How can I do this with a MySQL query?

Consider:
select substr(id, 4, 4) yr, count(*) cnt
from mytable
group by yr
order by yr
substr(id, 4, 4) extracts the relevant part of the string (4 characters starting from the 4th character in the string), which you can then use for grouping).
Demo on DB Fiddle:
yr | cnt
:--- | --:
2017 | 4
2018 | 1
2019 | 2
2020 | 1

Try this with substring_index.
select
substring_index(substring_index(mt, "-", -2), "-", 1) as year,
count(*) as count
from yourTable
group by
year

Related

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

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+.

Adding values that occur within same month: mysql

I have a table like this:
Date Name Qty
2016-09-13 00:00:00 John 2
2016-09-15 00:00:00 Matt 3
2016-09-21 00:00:00 Rich 1
2016-09-23 00:00:00 Matt 1
2016-10-05 00:00:00 John 1
2016-10-07 00:00:00 Matt 3
2016-10-12 00:00:00 Rich 0
2016-10-23 00:00:00 Matt 2
How can I do, using MySQL, to retrieve the addition of all the Qty values that corresponds to the same month and place that info on a view?
SELECT sum(Qty) as sum, month(date) as month, year(date) as year FROM table_name GROUP BY month(date), year(date)
will return
sum month year
4 12 2015
10 12 2016
What you probably want could be one of the following queries:
SELECT YEAR(`Date`) AS yr, MONTH(`Date`) AS mnt, SUM(Qty) AS Qty
FROM `table1`
GROUP BY YEAR(`Date`), MONTH(`Date`)
or
SELECT EXTRACT(YEAR_MONTH FROM `Date`) AS mnt, SUM(Qty) as Qty
FROM `table1`
GROUP BY EXTRACT(YEAR_MONTH FROM `Date`)
This query should produce something like this:
mnt | qty
---------+-----
2016-09 | 7
2016-10 | 6
The MySQL function EXTRACT() is able to return only some components of a date.

Group and sum data based on a day of the month

I have a reoccurring payment day of 14th of each month and want to group a subset of data by month/year and sum the sent column. For example for the given data:-
Table `Counter`
Id Date Sent
1 10/04/2013 2
2 11/04/2013 4
3 15/04/2013 7
4 10/05/2013 3
5 14/05/2013 5
6 15/05/2013 3
7 16/05/2013 4
The output I want is something like:
From Count
14/03/2013 6
14/04/2013 10
14/05/2013 12
I am not worried how the from column is formatted or if its easier to split into month/year as I can recreated a date from multiple columns in the GUI. So the output could easily just be:
FromMth FromYr Count
03 2013 6
04 2013 10
05 2013 12
or even
toMth toYr Count
04 2013 6
05 2013 10
06 2013 12
If the payment date is for example the 31st then the date comparison would need to be the last date of each month. I am also not worried about missing months in the result-set.
I will also turn this into a Stored procedure so that I can push in the the payment date and other filtered criteria. It is also worth mentioning that we can go across years.
Try this query
select
if(day(STR_TO_DATE(date, "%Y-%d-%m")) >= 14,
concat('14/', month(STR_TO_DATE(date, "%Y-%d-%m")), '/', year(STR_TO_DATE(date, "%Y-%d-%m"))) ,
concat('14/', if ((month(STR_TO_DATE(date, "%Y-%d-%m")) - 1) = 0,
concat('12/', year(STR_TO_DATE(date, "%Y-%d-%m")) - 1),
concat(month(STR_TO_DATE(date, "%Y-%d-%m"))-1,'/',year(STR_TO_DATE(date, "%Y-%d-%m")))
)
)
) as fromDate,
sum(sent)
from tbl
group by fromDate
FIDDLE
| FROMDATE | SUM(SENT) |
--------------------------
| 14/10/2013 | 3 |
| 14/12/2012 | 1 |
| 14/3/2013 | 6 |
| 14/4/2013 | 10 |
| 14/5/2013 | 12 |
| 14/9/2013 | 1 |
Pay date could be grouped by months and year separatedly
select Sum(Sent) as "Count",
Extract(Month from Date - 13) as FromMth,
Extract(Year from Date - 13) as FromYr
from Counter
group by Extract(Year from Date - 13),
Extract(Month from Date - 13)
Be careful, since field's name "Date" coninsides with the keyword "date" in ANSISQL
I think the simplest way to do what you want is to just subtract 14 days rom the date and group by that month:
select date_format(date - 14, '%Y-%m'), sum(sent)
from counter
group by date_format(date - 14, '%Y-%m')

Select up to x rows of each group

With a table of:
id | name | job | rank
01 john teacher 4
02 mark teacher 2
03 phil plummer 1
04 dave teacher 7
05 jim plummer 9
06 bill plummer 2
How can I select up to 2 rows of each job (if possible sorted by rank ASC in each group, so that the lowest two ranking of each group get picked). The result I'd be looking for is:
02 mark teacher 2
01 john teacher 4
03 phil plummer 1
06 bill plummer 2
This basically groups by job, with a limit to 2 and sorted by rank. I've been trying with GROUP BY as well as LEFT JOIN, but I just can't figure out how to do this. When creating a "temporary list" of jobs with GROUPING BY job, how do I join more than once onto that job?
SELECT id, name, job, rank
FROM TableName a
WHERE
(
SELECT COUNT(*)
FROM TableName as f
WHERE f.job = a.job AND
f.rank <= a.rank
) <= 2;
SQLFiddle Demo

MySQL Query for Graphs

I've got the following query:
SELECT GVA12.FECHA_EMIS, GVA12.COD_VENDED, sum(GVA12.IMPORTE)
FROM GVA12
WHERE Month(GVA12.FECHA_EMIS)=Month(curDate())
AND Year(GVA12.FECHA_EMIS)=Year(curDate())
AND GVA12.COD_VENDED="EX"
AND GVA12.T_COMP="FAC"
GROUP BY GVA12.FECHA_EMIS
This is for a monthly graph. I've got two questions. One, how can I show all the dates of the months as zero (the ones that don't have any sales), and two, is there any way to make the values go adding up, so the last value is the total of all the values.
Edit:
#Bluefeet with your query, I created the following,
SELECT Month(Days.DMY), Year(Days.DMY), GVA12.COD_VENDED, sum(GVA12.IMPORTE)
FROM Days
left join GVA12
on Month(Days.DMY) = Month(GVA12.FECHA_EMIS)
and Year(Days.DMY) = Year(GVA12.FECHA_EMIS)
WHERE Month(GVA12.FECHA_EMIS)=Month(curDate())
AND Year(GVA12.FECHA_EMIS)=Year(curDate())
AND GVA12.COD_VENDED="EX"
AND GVA12.T_COMP="FAC"
GROUP BY Month(Days.DMY), Year(Days.DMY) WITH ROLLUP
I got the result attached (screenshot).
It doesn't show all the days of the month as I wanted. What can I do?
Edit #3
It works now, but I want to add another filter. This filter is added here http://sqlfiddle.com/#!2/9d46c/1
To get the total you can use the GROUP BY WITH ROLLUP which should give you the Total of all dates:
SELECT GVA12.FECHA_EMIS, GVA12.COD_VENDED, sum(GVA12.IMPORTE)
FROM GVA12
WHERE Month(GVA12.FECHA_EMIS)=Month(curDate())
AND Year(GVA12.FECHA_EMIS)=Year(curDate())
AND GVA12.COD_VENDED="EX"
AND GVA12.T_COMP="FAC"
GROUP BY GVA12.FECHA_EMIS WITH ROLLUP
As far as returning dates that do not exist, There are many questions on SO that answer that including the following. Sometimes it is easier in MySQL to create a table to join on:
generate days from date range
Get a list of dates between two dates
Edit #1: if you have a table with dates, then you could use something similar to this:
SELECT Month(d.yourDateCol), Year(d.yourDateCol), g.COD_VENDED, sum(g.IMPORTE)
FROM dates d
left join GVA12 g
on Month(d.yourDateCol) = Month(GVA12.FECHA_EMIS)
and Year(d.yourDateCol) = Year(GVA12.FECHA_EMIS)
WHERE Month(g.FECHA_EMIS)=Month(curDate())
AND Year(g.FECHA_EMIS)=Year(curDate())
AND g.COD_VENDED="EX"
AND g.T_COMP="FAC"
GROUP BY Month(d.yourDateCol), Year(d.yourDateCol) WITH ROLLUP
Edit #2: Without seeing your full table structure or some sample data, here is a version of the query that is working:
select month(d.dmy) Month,
year(d.dmy) Year,
coalesce(sum(g.Importe), 0) TotalImporte
from dates d
left join GVA12 g
on month(d.dmy) = month(g.FECHA_EMIS)
and year(d.dmy) = year(g.FECHA_EMIS)
group by month(d.dmy), year(d.dmy) WITH ROLLUP
See SQL Fiddle with Demo. This returns the month/year for each month/year in the dates table even if it does not exist in the GVA12 table,
Edit #3: If you want the running total, not just the final total, then you should be able to use the following:
SET #running_total := 0;
SELECT month(Days.DMY) Month,
Year(Days.DMY) Year,
Date(Days.DMY) Date,
g.COD_VENDED,
#running_total := #running_total + Coalesce(TotalImport, 0) as TotalImport
FROM Days
left join
(
select FECHA_EMIS,
COD_VENDED,
sum(IMPORTE) TotalImport
from GVA12
group by Date(FECHA_EMIS), Year(FECHA_EMIS)
) g
on date(Days.DMY) = date(g.FECHA_EMIS)
and g.COD_VENDED='EX'
and Month(g.FECHA_EMIS)=Month(curDate())
and Year(g.FECHA_EMIS)=Year(curDate())
WHERE month(days.dmy)=Month(curDate())
See SQL Fiddle with Demo
The result is:
| MONTH | YEAR | DATE | COD_VENDED | TOTALIMPORT |
----------------------------------------------------------------------------
| 1 | 2013 | January, 01 2013 00:00:00+0000 | (null) | 0 |
| 1 | 2013 | January, 02 2013 00:00:00+0000 | EX | 1000 |
| 1 | 2013 | January, 03 2013 00:00:00+0000 | EX | 4000 |
| 1 | 2013 | January, 04 2013 00:00:00+0000 | (null) | 4000 |
| 1 | 2013 | January, 05 2013 00:00:00+0000 | (null) | 4000 |
| 1 | 2013 | January, 06 2013 00:00:00+0000 | (null) | 4000 |
| 1 | 2013 | January, 07 2013 00:00:00+0000 | (null) | 4000 |
a couple ideas:
one - you need some data values to compare against - so you could build a new table to hold all the dates - statically - then you do an outer join to that to make sure you get the zeroes.
two - i'm not sure about mysql - but in Oracle this is a LAG function. maybe that is a helpful pointer for further research.
Have a look at this link, there is explained how to list all dates between 2 dates
Get a list of dates between two dates
you can list using the following but I think there are better solution in the above link:
select a.Date
from (
select curdate() - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY as Date
from (select 0 as a 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) as a
cross join (select 0 as a 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) as b
cross join (select 0 as a 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) as c
) a
where a.Date between '2012-12-01' and '2012-12-31';