Is it possible to output a fixed number of groups in SQL even if there are fewer groups in the result?

Mister Vanderbilt :

I want to create a diagram based on an SQL query. The diagram should show every 12 months of the current year, even if some of them are still in the future.

My SQL query is something like this:

SELECT
     MONTH (created) as m,
     sum (price) as s
FROM
   product
WHERE
   YEAR (created) = 2020
GROUP BY
   m

Of course, this only shows results for the current or past months, which also have any entries.

Is it possible

  • to formulate the query so that 12 groups are displayed in any case and
  • if there is no group it gets a default value of 0?
Caius Jard :

Sure, left join it to a collection of all 12 months. This is MySQL (because you used GROUP BY alias, which I think is MySQL only, maybe Postgres too):

WITH months as (
 SELECT 1 as m
 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
 UNION ALL SELECT 12 
)

SELECT
  m.m,
  sum (p.price) as s
FROM
  months m
  LEFT JOIN product p 
  ON 
    m.m = MONTH(p.created) AND
    p.created > '2020-01-01' and p.Created < '2021-01-01'
GROUP BY
  m.m

Months that don't match will have a NULL, which will sum as 0

You should try and prefer to not use functions like YEAR() in the WHERE clause because they mean that indexes can't be used; instead leave the table data alone and put a ranged search in instead

If your MySQL is old and doesn't support CTE, make that a subquery instead. If your database isn't MySQL/Pg, or it has to select from something you can make it select from a table that has only one row


An alternative trick that doesn't employ a join, would be to union a load of zero months in; they wont contribute to sums but will provide a month with 0:

SELECT
   m,
   sum (price) as s
FROM
(
   SELECT MONTH(created) as m, price FROM product WHERE created >= '2020-01-01' and created < '2021-01-01'
   UNION ALL SELECT 1, 0 UNION ALL SELECT 2, 0 UNION ALL SELECT 3, 0 
   UNION ALL SELECT 4, 0 UNION ALL SELECT 5, 0 UNION ALL SELECT 6, 0 
   UNION ALL SELECT 7, 0 UNION ALL SELECT 8, 0 UNION ALL SELECT 9, 0 
   UNION ALL SELECT 10, 0 UNION ALL SELECT 11, 0 UNION ALL SELECT 12, 0 
) x
GROUP BY
   m

If you have a table dedicated to numbers and dates, the union all block can be replaced with a call to it. If you're on Postgres, it has a series generator that can do cool things

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=395066&siteId=1