Daily Aggregation Queries for Time-Series Chart Data

When building dashboards or analytics features, it's common to visualize metrics like daily user signups, page views, or logins over a date range. A key requirement is that the result must include every day in the interval, even if no activity occurred on that day—those antries should show a count of zero.

Sample Schema

Consider a user table with the following structure:

CREATE TABLE `user` (
  `id` BIGINT NOT NULL AUTO_INCREMENT,
  `created_at` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
  `updated_at` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
  `username` VARCHAR(20) NOT NULL COMMENT 'Username',
  `email` VARCHAR(255) NULL DEFAULT NULL,
  `phone` VARCHAR(20) NULL DEFAULT NULL,
  `password` VARCHAR(128) NULL DEFAULT NULL,
  `is_active` TINYINT(1) NOT NULL DEFAULT 1,
  `last_login` DATETIME(6) NULL DEFAULT NULL COMMENT 'Last login timestamp'
);

Solution 1: Recursive CTE (MySQL 8.0+)

Using a recursive Common Table Expression (CTE), we can generate a complete sequence of dates between two points and left-join it with actual data:

WITH RECURSIVE date_range AS (
  SELECT DATE(MIN(created_at)) AS dt FROM user
  UNION ALL
  SELECT dt + INTERVAL 1 DAY
  FROM date_range
  WHERE dt < CURDATE()
)
SELECT
  dr.dt AS registration_date,
  COUNT(u.id) AS daily_registrations
FROM date_range dr
LEFT JOIN user u
  ON u.created_at >= dr.dt
 AND u.created_at < dr.dt + INTERVAL 1 DAY
GROUP BY dr.dt
ORDER BY dr.dt;

This avoids applying DATE() on the indexed created_at column, preserving index usage. For high-frequancy queries over large datasets, consider adding a dedicated created_date DATE column with an index to enable equality joins.

Solution 2: Calendar Helper Table

Create a permanent calendar dimension table once:

CREATE TABLE dim_calendar (day DATE PRIMARY KEY);

INSERT INTO dim_calendar (day)
SELECT DATE_ADD('2020-01-01', INTERVAL seq DAY)
FROM (
  SELECT a.N + b.N*10 + c.N*100 + d.N*1000 AS seq
  FROM (SELECT 0 N UNION SELECT 1 UNION ... UNION SELECT 9) a,
       (SELECT 0 N UNION SELECT 1 UNION ... UNION SELECT 9) b,
       (SELECT 0 N UNION SELECT 1 UNION ... UNION SELECT 9) c,
       (SELECT 0 N UNION SELECT 1 UNION ... UNION SELECT 9) d
) nums
WHERE seq <= 4018; -- Covers ~11 years

Then query using this table:

SELECT
  c.day AS registration_date,
  COUNT(u.id) AS daily_registrations
FROM dim_calendar c
LEFT JOIN user u
  ON u.created_at >= c.day
 AND u.created_at < c.day + INTERVAL 1 DAY
WHERE c.day BETWEEN DATE_SUB(CURDATE(), INTERVAL 29 DAY) AND CURDATE()
GROUP BY c.day
ORDER BY c.day;

Solution 3: Numbers Table Approach

For shorter ranges, use a numbers table:

CREATE TABLE numbers (n TINYINT PRIMARY KEY);
INSERT INTO numbers (n)
SELECT units.n + tens.n * 10
FROM (SELECT 0 n UNION ... UNION SELECT 9) units,
     (SELECT 0 n UNION ... UNION SELECT 9) tens;

Query example for a specific range:

SET @start = '2024-03-01';
SET @end   = '2024-03-31';

SELECT
  DATE_ADD(@start, INTERVAL n DAY) AS registration_date,
  COUNT(u.id) AS daily_registrations
FROM numbers
LEFT JOIN user u
  ON u.created_at >= DATE_ADD(@start, INTERVAL n DAY)
 AND u.created_at < DATE_ADD(@start, INTERVAL n + 1 DAY)
WHERE n <= DATEDIFF(@end, @start)
GROUP BY n
ORDER BY registration_date;

ECharts Integration

The backend should return a dense array of daily records, including zeros. In JavaScript, format the data for ECharts using either explicit arrays or the dataset API:

// Option 1: Manual split
const rawData = [
  { date: '2025-07-01', count: 10 },
  { date: '2025-07-02', count: 0 },
  // ...
];
const [dates, counts] = rawData.reduce(
  ([d, c], item) => [ [...d, item.date], [...c, item.count] ],
  [[], []]
);
option = {
  xAxis: { type: 'category', data: dates },
  yAxis: { type: 'value' },
  series: [{ type: 'bar', data: counts }]
};
// Option 2: Dataset (preferred)
option = {
  dataset: {
    dimensions: ['date', 'count'],
    source: rawData
  },
  xAxis: { type: 'category' },
  yAxis: { type: 'value' },
  series: [{ type: 'bar' }]
};

Tags: MySQL CTE echarts time-series Data Aggregation

Posted on Sun, 18 Oct 2026 16:35:50 +0000 by aftab_jii