how to calculate the difference between 2 rows using mysql - mysql

I have a table MeterReading in mysql and it has the following columns
meterid bigint(4),
reading bigint(4),
date date,
time time,
consumption,
primary key(meterid,reading)
i am inserting the vlues to this table except consumption and my problem is how to update the table based on these values.
i tried the following query
update MeterReading a
set consumption=(select reading-IFNULL((select MAX(reading) from MeterReading where meterid=a.meterid AND (date < a.date OR date = a.date AND time < a.time )),0));
i want the result like:
meterid | reading | date | time | consumption |
+---------+---------+------------+----------+-------------+
| 1 | 450 | 2012-10-05 | 06:05:05 | 450 |
| 1 | 550 | 2012-10-06 | 08:05:05 | 100 |
| 1 | 600 | 2012-10-07 | 09:05:05 | 50 |
| 1 | 700 | 2012-10-08 | 10:05:05 | 100 |
please help me

Try this instead:
UPDATE MeterReading a
LEFT JOIN
(
SELECT meterid, MAX(reading) MaxReading
FROM MeterReading
GROUP BY meterid
) g ON g.meterid = a.meterid
SET a.consumption = IFNULL(g.MaxReading, 0) - a.reading ;
Update: Use IFNULL(g.MaxReading, 0) - a.reading or you can use the ABS(IFNULL(g.MaxReading, 0) - a.reading).
SQL Fiddle Demo
Edit: So, you need to update each composite key (meterid, reading)'s consumption value with the difference between the current reading value and the next row's reading value. If so you can do this:
UPDATE MeterReading m
INNER JOIN
(
SELECT
m.*,
#rownum := #rownum + 1 AS rank
FROM meterreading m, (SELECT #rownum := 0) r
ORDER BY m.date
) t1 ON m.meterid = t1.meterid
AND m.reading = t1.reading
LEFT JOIN
(
SELECT
m.*,
#rownum2 := #rownum2 + 1 AS rank
FROM meterreading m, (SELECT #rownum2 := 0) r
ORDER BY m.date
) t2 ON t1.rank - t2.rank = 1
SET m.consumption = (t1.reading - IFNULL(t2.reading, 0));
Updated SQL Fiddle Demo
This will make your table meterreading, after update, looks like:
| METERID | READING | DATE | TIME | CONSUMPTION |
-----------------------------------------------------------------------------------------------------
| 1 | 450 | October, 05 2012 02:00:00+0000 | January, 01 1970 06:05:05+0000 | 450 |
| 1 | 550 | October, 06 2012 02:00:00+0000 | January, 01 1970 08:05:05+0000 | 100 |
| 1 | 600 | October, 07 2012 02:00:00+0000 | January, 01 1970 09:05:05+0000 | 50 |
| 1 | 700 | October, 08 2012 02:00:00+0000 | January, 01 1970 10:05:05+0000 | 100 |
Note that: I used the #rownum variable to get each row's rank, and the thing is, this rank is based on the order of the Date column, so it will get the next date's reading value.

Related

SQL query to count the number of rows in different columns with group by clauses

In my Mysql database, I have 2 columns that store the start and end date of the process, respectively. I need to write a query that allows me to count the number of rows for each month in each column, and presents the count separately.
Table example:
+----+------------+----------------+
| id | startData | endData |
+----+-------------+----------------+
| 1 | 02/03/2020 | 02/03/2020 |
| 2 | 02/04/2020 | 02/04/2020 |
| 3 | 02/04/2020 | 02/05/2020 |
| 4 | 02/04/2020 | 02/05/2020 |
| 5 | 02/05/2020 | 02/06/2020 |
| 6 | 02/05/2020 | 02/06/2020 |
| 7 | 02/06/2020 | 02/07/2020 |
+----+-------------+----------------+
I want as a result:
+-------+--------------------+-------------------+
| month | count_month_start | count_month_end |
+-------+--------------------+-------------------+
| 03 | 01 | 01 |
| 04 | 03 | 01 |
| 05 | 02 | 02 |
| 06 | 01 | 02 |
| 07 | 00 | 01 |
+-------+--------------------+-------------------+
Assuming your start date and end date columns are of datatype date, you can do -
Select ifnull(Tb1.mn,Tb2.mn) As mn, ifnull(count_mn_start,0) As count_mn_start, ifnull(count_mn_end,0) As count_mn_end
from
(Select Month(StartDate) as mn, count(id) as count_mn_start
from
my_table
Group by Month(StartDate))Tb1
left Join (Select Month(EndDate) as mn, count(id) as count_mn_end
from my_table
Group by Month(EndDate)) Tb2
on Tb1.mn = Tb2.mn
UNION
Select ifnull(Tb1.mn,Tb2.mn) As mn, ifnull(count_mn_start,0) As count_mn_start, ifnull(count_mn_end,0) As count_mn_end
from
(Select Month(StartDate) as mn, count(id) as count_mn_start
from
my_table
Group by Month(StartDate))Tb1
Right Join (Select Month(EndDate) as mn, count(id) as count_mn_end
from my_table
Group by Month(EndDate)) Tb2
on Tb1.mn = Tb2.mn;
DB fiddle - https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=84ecddb9f5ed431ddff6a9eaab87e5df
PS : If your dates only have one year (2020 as your example) this would work, however ideally if you have different years in the data, consider having the year in the output as well and in that case use the same syntax ie Year(datefield) and add it in the select and group by in the sub-queries (same way as month in the above query).
A pretty simple way is to expand the time periods into days using a recursive CTE. Then just aggregate:
with recursive cte as (
select id, startdate as dte, enddate
from t
union all
select id, dte + interval 1 day, enddate
from cte
where dte < enddate
)
select year(dte), month(dte),
sum( day(dte) = 1 ) as cnt_start,
sum( day(dte) = day(last_day(dte)) ) as cnt_end
from cte
group by year(dte), month(dte) ;
Here is a db<>fiddle.

MySQL cumulative sum query not returning expected results

I am trying to create a MySQL query (MySQL v5) that totals call records by day/inbound number with a running cumulative total. I have referenced other pages on Stack Overflow but the results I'm getting are not adding up.
References:
MYSQL cumulative sum by date
MySQL cumulative sum order by date
The query looks like so:
SET #RUNNING_TOTAL :=0;
SELECT
DATE_FORMAT(start,'%d/%m/%Y') As CallDate,
ch.did AS InboundNo,
COUNT(*) AS DayTotal,
(#RUNNING_TOTAL := #RUNNING_TOTAL + COUNT(*)) AS CumulativeCalls
FROM
`call_history` ch
LEFT JOIN (SELECT callid, event FROM ast_queue_log WHERE event = 'ENTERQUEUE') aql ON aql.callid = ch.callid
WHERE
ch.did = '01234567891' AND
start BETWEEN DATE_FORMAT(NOW() ,'%Y-%m-01') AND NOW()
GROUP BY
ch.did, DATE(start)
ORDER BY
ch.did;
I would expect the following output:
+-------------------------------+-------------+----------+-----------------+
| CallDate | InboundNo | DayTotal | CumulativeCalls |
+-------------------------------+-------------+----------+-----------------+
| 01/05/2020 | 01234567891 | 232 | 232 |
| 02/05/2020 | 01234567891 | 50 | 282 |
| 03/05/2020 | 01234567891 | 14 | 296 |
| 04/05/2020 | 01234567891 | 246 | 542 |
| 05/05/2020 | 01234567891 | 187 | 729 |
| 06/05/2020 | 01234567891 | 182 | 911 |
| 07/05/2020 | 01234567891 | 105 | 1016 |
| 08/05/2020 | 01234567891 | 46 | 1062 |
| 09/05/2020 | 01234567891 | 26 | 1088 |
| 10/05/2020 | 01234567891 | 7 | 1095 |
| 11/05/2020 | 01234567891 | 255 | 1350 |
+-------------------------------+-------------+----------+-----------------+
What I am getting is the same values in DayTotal and CumulativeCalls for each day.
In MySQL 8+, you should be using window functions:
SELECT DATE_FORMAT(DATE(start), '%d/%m/%Y') As CallDate,
ch.did AS InboundNo,
COUNT(*) AS DayTotal,
SUM(COUNT(*)) OVER (PARTITION BY ch.did ORDER BY DATE(start)) as CumulativeCalls
FROM call_history ch LEFT JOIN
ast_queue_log aql
ON aql.event = 'ENTERQUEUE' AND aql.callid = ch.callid
WHERE ch.did = '01234567891' AND
start BETWEEN DATE_FORMAT(NOW(), '%Y-%m-01') AND NOW()
GROUP BY ch.did, DATE(start)
ORDER BY ch.did;
Notes:
The subquery is not needed for the LEFT JOIN.
All columns should be qualified. What table does start come from?
The GROUP BY and SELECT are consistent, by using DATE(start) in the SELECT statement.
In older versions of MySQL, you'll need variables and a subquery:
SELECT dc.*,
(#s := #s + DayTotal) as CumulativeCalls
FROM (SELECT DATE_FORMAT(DATE(start), '%d/%m/%Y') As CallDate,
ch.did AS InboundNo,
COUNT(*) AS DayTotal
FROM call_history ch LEFT JOIN
ast_queue_log aql
ON aql.event = 'ENTERQUEUE' AND aql.callid = ch.callid
WHERE ch.did = '01234567891' AND
start BETWEEN DATE_FORMAT(NOW(), '%Y-%m-01') AND NOW()
GROUP BY ch.did, DATE(start)
ORDER BY ch.did, DATE(start)
) dc CROSS JOIN
(SELECT #s := 0) params;

MySQL perform subquery if count is zero

I have three tables: monthly_revenue, currencies and foreign_exchange.
monthly_revenue table
|------------------------------------------------------|
| id | product_id | currency_id | value | month | year |
|------------------------------------------------------|
| 1 | 1 | 1 | 100 | 1 | 2015 |
| 2 | 1 | 2 | 125 | 1 | 2015 |
| 3 | 1 | 3 | 115 | 1 | 2015 |
| 4 | 1 | 1 | 100 | 2 | 2015 |
| 5 | 1 | 2 | 125 | 2 | 2015 |
| 6 | 1 | 3 | 115 | 2 | 2015 |
|------------------------------------------------------|
foreign_exchange table
|---------------------------------------|
| id | base | target | rate | rate_date |
|---------------------------------------|
| 1 | GBP | USD | 1.6 |2015-01-01 |
| 2 | GBP | USD | 1.62 |2015-01-15 |
| 3 | GBP | USD | 1.61 |2015-01-31 |
| 4 | EUR | USD | 1.2 |2015-01-01 |
| 5 | EUR | USD | 1.4 |2015-01-15 |
| 6 | EUR | USD | 1.4 |2015-01-31 |
| 7 | GBP | EUR | 1.4 |2015-01-01 |
| 8 | GBP | EUR | 1.45 |2015-01-15 |
| 9 | GBP | EUR | 1.44 |2015-01-31 |
|---------------------------------------|
From this, we can see the average fx rates:
GBP > USD in January is 1.61
EUR > USD in January is 1.33
GBP > EUR in January is 1.43
No rates are available for USD as a base currency, and no rates are available for February.
currencies table
|-----------|
| id | name |
|-----------|
| 1 | GBP |
| 2 | USD |
| 3 | EUR |
|-----------|
What i'm trying to achieve
Each row within the monthly_revenue table can have a different currency_id, as orders are placed is different currencies. I want to see all revenue for a given month, in a common currency. So, rather than looking at all revenue in January in GBP, and then separately looking at all revenue in January in USD, I'd like to get one value for all revenue in January - converted to USD (for example).
This can be calculated for each row, using the following (using January for this example):
revenue value x average fx rate for January between base and target currency
If I have 50 orders in January, in 4 different currencies, this let's me see all revenue in any single currency.
Example - get all revenue in January, in USD
This should return:
|------------------------------------------------------|
| id | product_id | currency_id | value | month | year |
|------------------------------------------------------|
| 1 | 1 | 1 | 100 | 1 | 2015 |
| 2 | 1 | 2 | 125 | 1 | 2015 |
| 3 | 1 | 3 | 115 | 1 | 2015 |
|------------------------------------------------------|
However, rows 1 and 3 are not in USD (these are GBP, and EUR respectively).
What I'd like to see is each row returned with the average FX rate that is being converted to, and a converted column. For example:
|-------------------------------------------------------------------------|
| id | prod_id | currency_id | value | month | year | fx_avg | converted |
|-------------------------------------------------------------------------|
| 1 | 1 | 1 | 100 | 1 | 2015 | 1.61 | 161 |
| 2 | 1 | 2 | 125 | 1 | 2015 | 1 | 125 |
| 3 | 1 | 3 | 115 | 1 | 2015 | 1.33 | 152.95 |
|-------------------------------------------------------------------------|
Where I'm at
I can currently get the basic calculation done using the query below, but a couple of key features are lacking:
If there is no FX rate available (for example for future dates where of course an FX rate isn't available) then the entire row is ignored. What I'd like in this instance is for the latest month's average to be used.
If the calculation is being performed where the target currency is the same as the base currency, the entire row is ignored (as there is no record in the FX table where the base equals the target). In this instance, the rate should be hard defined as 1.
Query so far
SELECT
r.value * IFNULL(AVG(fx.rate),1) as converted, AVG(fx.rate) as averageFx,
r.*, fx.*
FROM
foreign_exchange fx, monthly_revenue r, order_headers h
WHERE
fx.base IN (SELECT name FROM currencies WHERE id = r.currency_id) AND
r.order_header_id = h.id AND
fx.target = 'USD' AND
MONTH(fx.rate_date) = r.month AND
YEAR(fx.rate_date) = r.year AND
r.year = 2015
GROUP BY r.id
ORDER BY month ASC
If there are no records available for FX, it looks like a separate subquery should be performed to get the average of the latest month's rates.
Any input would be appreciated. If any further info is required, please post a comment.
Thanks.
Edit Here is a SQFiddle which has the example schemas and the code which highlights the issue.
Here is an approximation of a function that computes your exchange for a given currency and start of month:
DELIMITER //
CREATE FUNCTION MonthRate(IN _curr CHAR(3) CHARACTER SET ascii,
IN _date DATE)
RETURNS FLOAT
DETERMINISTIC
BEGIN
-- Note: _date must be the first of some month, such as '2015-02-01'
DECLARE _avg FLOAT;
DECLARE _prev FLOAT;
-- First, try to get the average for the month:
SELECT AVG(rate) INTO _avg FROM foreign_exchange
WHERE base = _curr
AND target = 'USD'
AND rate_date >= _date
AND rate_date < _date + INTERVAL 1 MONTH;
IF _avg IS NOT NULL THEN
RETURN _avg;
END;
-- Fall back onto the last rate before the month:
SELECT rate INTO _prev
FROM foreign_exchange
WHERE base = _curr
AND target = 'USD'
AND rate_date < _date
ORDER BY _date
LIMIT 1;
IF _prev IS NOT NULL THEN
RETURN _prev;
END;
SELECT "Could not get value -- ran off start of Rates table";
END;
DELIMITER ;
There are probably syntax errors, etc. But hopefully you can work with it.
It should be easy to call the function from the rest of the code.
For performance, this would be beneficial:
INDEX(base, target, rate_date, rate)
Create a view :
create view avg_rate as
select base, target, year(fx.rate_date) year, month(fx.rate_date) month,
avg(rate) avg_rate
from foreign_exchange group by base, target
Join it twice, once for current month, and once for previous
select r.id, r.month,
r.value * avg(coalesce(cr.avg_rate, pr.avg_rate, 1)) converted,
avg(coalesce(cr.avg_rate, pr.avg_rate), 0) rate
from monthly_revenue r, avg_rate cr, avg_rate pr, order_headers h
where
r.year = 2015 and
cr.year = r.year and cr.month = r.month and cr.target='USD' and
pr.year = r.year and pr.month = r.month - 1 and pr.target='USD' and
r.order_header_id = h.id
group by r.id
order by r.month
Also I personally don't like this way of writing query and prefer to using explicit joins as you group conditions logically and don't have a mess in where clause. i.e.:
...
from monthly_revenue r
inner join order_headers h on r.order_header_id = h.id
left join avg_rate cr on cr.year = r.year and cr.month = r.month and cr.target='USD'
left join avg_rate pr on pr.year = r.year and pr.month = r.month - 1 and pr.target='USD'
where r.year = 2015
http://sqlfiddle.com/#!9/6a41a/1
This fiddle based on your original one but I added some rate values for February and March to test and show how it works.
SELECT t.*,
IF(#first=t.id, #flag := #flag+1,#flag:=1) `flag`,
#first:=t.id
FROM
(SELECT
coalesce(fx.rate,1) `rate`, (r.value * coalesce(fx.rate,1)) as converted,
r.*, fx.base,fx.target, fx.avg_date, fx.rate frate
FROM
monthly_revenue r
LEFT JOIN
currencies
ON r.currency_id = currencies.id
LEFT JOIN
(SELECT AVG(rate) `rate`,
`base`,
`target`,
STR_TO_DATE(CONCAT('1/',MONTH(rate_date),'/',YEAR(rate_date)), '%d/%m/%Y') avg_date
FROM foreign_exchange
GROUP BY `base`, `target`, `avg_date`
) fx
ON currencies.name = fx.base
AND fx.target = 'USD'
AND fx.avg_date <= STR_TO_DATE(CONCAT('1/',r.month,'/',r.year), '%d/%m/%Y')
ORDER BY r.id, fx.avg_date DESC) t
HAVING `flag` = 1
and if you need records just for specific month you can add WHERE before ORDER like this:
WHERE r.month = 1 and r.year = 2015
ORDER BY r.id, fx.avg_date DESC) t
You may test this query on the fiddle link you provided : http://sqlfiddle.com/#!9/33def/2
select id,product_id,currency_id,currency_name,
value,month,year,#prev_fx_avg:=ifnull(fx_avg,#prev_fx_avg) fx_avg,
value*#prev_fx_avg as converted
from (SELECT
r.id,r.product_id,r.currency_id,c.name as currency_name,
r.value,r.month,r.year,if(c.name="USD",1,temp.avg_rate) as fx_avg
FROM
monthly_revenue r
left join currencies c on r.currency_id=c.id
left join
(select base , avg(rate) as avg_rate, MONTH(fx.rate_date) month,
YEAR(fx.rate_date) year
from foreign_exchange fx
where target="USD"
group by base,target,MONTH(fx.rate_date),
YEAR(fx.rate_date)) temp on(r.month=temp.month and r.year=temp.year and c.name=temp.base)
group by r.id
order by r.currency_id,r.month ASC, r.year ASC) final,(select #prev_fx_avg:=-1) temp2;

SQL: Increment for every GROUP BY

I'm wanting to return the total number of times an exerciseID is listed, but filter it so each exerciseID may only be increment once per a date. For this reason I believe I cannot do a group by date.
id | exerciseid | date
1 | 105 | 2014-01-01 00:00:00
2 | 105 | 2014-02-01 00:00:00
3 | 105 | 2014-03-11 00:00:00
4 | 105 | 2014-03-11 00:00:00
5 | 105 | 2014-03-11 00:00:00
6 | 127 | 2014-01-01 00:00:00
7 | 127 | 2014-02-02 00:00:00
8 | 127 | 2014-02-02 00:00:00
// 105 = 5 total rows but 3 unique
// 127 = 3 total rows but 2 unique
$db->query("SELECT exerciseid as id, sum(1) as total
FROM `users exercises` as ue
WHERE userid = $userid
GROUP BY exerciseid
ORDER BY date DESC");
Current Output:
Array
(
[id] => 105
[date] => 2014-05-06
[total] => 5
)
Array
(
[id] => 127
[date] => 2014-05-06
[total] => 3
)
As you can see it's not merging the rows where the date and exerciseid are the same.
Expected Result:
Array
(
[id] => 105
[date] => 2014-05-06
[total] => 3
)
Array
(
[id] => 127
[date] => 2014-05-06
[total] => 2
)
for V2.0 question:
select
exerciseid
, count(distinct date) as exercise_count
from user_exercises
group by
exerciseid
;
| EXERCISEID | EXERCISE_COUNT |
|------------|----------------|
| 54 | 1 |
| 85 | 3 |
| 420 | 2 |
see this sqlfiddle
If you want count how many group you have on group by :
$db->query("SELECT e.id as id, e.name, count(id) as total, ue.date
FROM `users exercises` as ue
LEFT JOIN `exercises` as e ON exerciseid = e.id
WHERE ue.`userid` = $userid
GROUP BY id ASC
ORDER BY total DESC");
else if you want take previous total for addition, create a procedure like this (I think there are errors in my procedure)
CREATE PROCEDURE name
DECLARE
record your_table%ROWTYPE;
nb int DEFAULT 0;
BEGIN
FOR record IN SELECT e.id as id, e.name as name, count(id) as nbid, ue.date as date
FROM `users exercises` as ue
LEFT JOIN `exercises` as e ON exerciseid = e.id
WHERE ue.`userid` = $userid
GROUP BY id ASC
ORDER BY total DESC
LOOP
set nb := nb + record.nbid;
SELECT record.id,record.name,nb,date;
END LOOP;
END
regards
Dragondark De Lonlindil
I think you are seeking a running total.
| USERID | DATE | NAME | RUNNINGSUM |
|--------|------------------------------|---------------------|------------|
| 1 | May, 10 2014 00:00:00+0000 | football | 1 |
| 1 | June, 10 2014 00:00:00+0000 | football | 2 |
| 1 | July, 10 2014 00:00:00+0000 | football | 3 |
| 1 | July, 10 2014 00:00:00+0000 | football | 4 |
| 1 | May, 10 2014 00:00:00+0000 | Machine Bench Press | 5 |
| 1 | June, 10 2014 00:00:00+0000 | Machine Bench Press | 6 |
| 1 | March, 10 2014 00:00:00+0000 | salsa | 7 |
MySQL lacks functions like row_number() that make this simple, but here is an approach that achieves the equivalent of row_number() partitioned by userid.
SELECT
userid
, date
, name
, RunningSum
FROM(
SELECT #row_num := IF(#prev_user = ue.userid, #row_num + 1 ,1) AS RunningSum
, ue.userid
, ue.date
, e.name
, #prev_user := ue.userid
FROM user_exercises ue
INNER JOIN exercises e ON ue.exerciseid = e.id
CROSS JOIN (SELECT #row_num :=1, #prev_user :='') vars
ORDER BY
ue.userid
, ue.date
, ue.exerciseid
) x
ORDER BY
userid
, RunningSum
see this sqlfiddle

total user count monthwise

mySQL total user count
Grouping by month
I want to list the total count of registered users grouped by month
Well, the difficulty about this is that I don't want the count per month,
but the the total count of users up to (and including) the month.
User table structure
+---------------+--------------+------+-------------------+----------------+
| Field | Type | Null | Default | Extra |
+---------------+--------------+------+-------------------+----------------+
| ID | int(11) | NO | NULL | auto_increment |
| email | varchar(225) | NO | NULL | |
................................-CUT-.......................................
| registered | timestamp | NO | CURRENT_TIMESTAMP | |
+---------------+--------------+------+-------------------+----------------+
Example data
1 example1#mail 2012-04-04 xx:xx:xx
2 example2#mail 2012-05-04 xx:xx:xx
3 example3#mail 2012-05-04 xx:xx:xx
Preferred output
+------+-------+-------+
| Year | Month | Count |
+------+-------+-------+
| 2012 | 01 | 0 |
| 2012 | 02 | 0 |
| 2012 | 03 | 0 |
| 2012 | 04 | 1 |
| 2012 | 05 | 3 |
+------+-------+-------+
The NULL results aren't necessary.
How could I achieve that result in pure mySQL?
I have not tried this but something along these lines should work -
SELECT tots.*, #var := #var + tots.`count`
FROM (
SELECT
YEAR(registered) AS `year`,
MONTH(registered) AS `month`,
COUNT(*) AS `count`
FROM user
GROUP BY `year`, `month`
) AS tots, (SELECT #var := 0) AS inc
You can do it with a couple of user variables:
set #c = 0;
set #d = 0;
select y, m, #d := #d + Count as Count from
(select year(registered) as y,
month(registered) as m,
#c := #c + count(*) as Count
from user
group by y,m) as t;
gives you
+------+------+-------+
| y | m | Count |
+------+------+-------+
| 2011 | 1 | 2455 |
| 2011 | 2 | 14253 |
| 2011 | 3 | 42311 |
This approach first gets the first day of the month for all months in which any registration occurred. It then joins to every user that had a registration greater than the first day of the month, and then counts the number of users.
SELECT
YEAR(dates.first_day_of_month) AS registration_year,
MONTH(dates.first_day_of_month) AS registration_month,
COUNT(u.ID)
FROM (
SELECT DISTINCT
DATE_SUB(
DATE_ADD(
DATE_SUB(registered,INTERVAL (DAY(registered)-1) DAY),
INTERVAL 1 MONTH),
INTERVAL 1 SECOND) first_day_of_month
FROM user
) dates
LEFT JOIN user u ON u.registered <= dates.first_day_of_month
GROUP BY dates.first_day_of_month
If you want to avoid the gaps in months where no registrations occurred, you could substitute the sub-query with another that used a pre-existing "numbers" table to get a list of all possible months.