I have the following table on a cycling club's website:
CREATE TABLE `mileage` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`rideID` mediumint(9) NOT NULL,
`rideDate` date NOT NULL,
`isWeekly` tinyint(1) NOT NULL,
`riderUid` smallint(6) NOT NULL,
`leaderUid` smallint(6) NOT NULL,
`mileage` smallint(6) NOT NULL,
`days` smallint(6) NOT NULL DEFAULT '1',
`eBike` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=8081 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
Each entry represents one rider/trip, where the rider is the person who rode and the trip is the rider's part of a club ride.
When the site was built, the club wanted the ability to get mileage between dates (defaulting to current month or YTD).
The following query works well for that application (variables are from PHP):
SELECT m.riderUid, w.firstName, w.lastName, w.isMale,
SUM(mileage) as miles,
COUNT(*) as cnt
FROM `mileage` m
LEFT JOIN `members` w ON (w.uid = m.riderUid)
WHERE (m.rideDate >= '$startDate' AND m.rideDate < '$endDate')
GROUP BY m.riderUid
ORDER BY miles DESC
Now they've requested to get the data month-by-month for any given pair of dates. Meaning they want to see columns output for, say, January, February, March … etc.
I suppose I can do this iteratively, using PHP to feed successive monthly start and end dates and then glomming them all together on the page, but that feels kind of clunky. I wonder if there's an easier way using a MySQL query.
So, is this kind of thing possible using a query or do I have to brute-force it using PHP or JavaScript? I should add that I'm not a database guru by any stretch, just a journeyman coder.
Best Answer
I think you just need include the month in the
GROUP BY
:If the date range is longer than a year, then months from different years will be folded together, so replace
MONTH(m.rideDate)
withLEFT(m.rideDate, 7)
as suggested by @RickJames.If you want the months as columns, then you need to "pivot" the months, see e.g. this answer on Stackoverflow. So that might give you a query with a subquery similar to the above, something like:
See here for db-fiddle example. Note: If running this query through phpMyAdmin, you may have to embrace each instance of
CASE WHEN ... END
in parentheses in order to workaround a bug in phpMyAdmin.(I suspect the above can also be re-written more elegantly without the subquey using window functions, though that requires MySQL 8.0+ or MariaDB 10.2+.)
A couple of other tips:
SELECT
in yourGROUP BY
- this is required by default in newer MySQL versions.INNER JOIN
instead ofOUTER JOIN
in this case? Or do you have mileage for members who are no longer in the members table? If so, you may want to keep theLEFT JOIN
.InnoDB
is a better choice thanMyISAM
in most use-cases now, andMyISAM
is discouraged in MySQL 8.0+.