8

I need to display the total of 'orders' for each year and month. But for some months there is no data, but I DO want to display that month (with a total value of zero). I could make a helpertable 'months' with 12 records for each year, but is there maybe a way to get a range of months, without introducing a new table?

Something like:

SELECT [all year-month combinations between january 2000 and march 2011] 
FROM DUAL AS years_months

Does anybody have an idea how to do this? Can you use SELECT with some kind of formula, to 'create' data on the fly?!

UPDATE:

Found this myself: generate days from date range

The accepted answer in this question is kind of what I'm looking for. Maybe not the easiest method, but it does what I want: fill a select with data, based on a formula....

To 'create' a table on the fly with all months of the last 10 years:

SELECT CONCAT(MONTHNAME(datetime), ' ' , YEAR(datetime)) AS YearMonth,
       MONTH(datetime) AS Month,
       YEAR(datetime) AS Year 
FROM (
    select (curdate() - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) MONTH) as datetime
    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
    LIMIT 120
) AS t
ORDER BY datetime ASC 

I must admit, this is VERY exotic, but it DOES work...

I can use this select to join it with my 'orders'-table and get the totals for each month, even when there is no data in a certain month.

But using a 'numbers' or 'calendar' table is probably the best option, so I'm going to use that.

Community
  • 1
  • 1
Dylan
  • 9,129
  • 20
  • 96
  • 153
  • In SQL Server you can use recursive CTEs or cross joined CTEs to do this. I suppose you could select from a sufficiently large table and use user variables. – Martin Smith Mar 26 '11 at 13:37
  • Can you give a little more information about your table(s) ? – Jonny Mar 26 '11 at 13:41
  • @Jonny Basically there's only 1 table : orders, which contains a date-field. Now I need a list of how many orders there are in each month from January 2000. But for some months, there are no orders at all. But I do want these months to show up in the list. So I need a second table, which holds all months from January 2000, and then join this with the orders-table. – Dylan Mar 26 '11 at 13:46

9 Answers9

6

You could try something like this

select * from 
    (select 2000 as year union
     select 2001 as year union
     select 2009
    ) as years, 
    (select 1 as month union 
     select 2 as month union 
     select 3 as month union 
     select 4 as month union 
     select 5 as month union 
     select 6 as month union 
     select 7 as month union 
     select 8 as month union 
     select 9 as month
     )
    AS months 
    WHERE year between 2001 AND 2008 OR (year=2000 and month>0) OR (year = 2009 AND month < 4) 
    ORDER by year,month
Yvan
  • 2,539
  • 26
  • 28
johnlemon
  • 20,761
  • 42
  • 119
  • 178
6

If at all possible, try to stay away from generating data on the fly. It makes very simple queries ridiculusly complex, but above all: it confuses the optimizer to no end.

If you need a series of integers, use a static table of integers. If you need a series of dates, months or whatever, use a calendar table. Unless you are dealing with some truly extraordinary requirements, a static table is the way to go.

I gave an example on how to create a table of numbers and a minimal calendar table(only dates) in this answer.

If you have those tables in place, it becomes easy to solve your query.

  1. Aggregate the order data to MONTH.
  2. Right join to the table of months (or distinct MONTH from the table of dates)
Community
  • 1
  • 1
Ronnis
  • 12,593
  • 2
  • 32
  • 52
  • I agree that my 'solution' is very complex... I'll think I'll use a 'numbers' or 'calendar' table then. Quite strange though that SQL after all these years is not capable of creating a simple series of data. – Dylan Mar 26 '11 at 20:48
3

You could just fill in the missing months after you've done your query in your application logic.

Matt
  • 9,068
  • 12
  • 64
  • 84
1

I do following query to generate months in a given interval. For my case it generate list of month started from may 2013 until now.

SELECT date_format(@dt:= DATE_ADD( @dt, INTERVAL 1 MONTH),'%M %Y') date_string, 
   @dt as date_full 
FROM (SELECT @dt := DATE_SUB(CAST(DATE_FORMAT('2013-05-01' ,'%Y-%m-01') AS DATE),
   INTERVAL 1 MONTH) ) vars,
   your_tables 
WHERE @dt<NOW()

The concern is, it should be joined with table containing sufficient rows to supply number of month you expected. E.g. if you need to generate all month in a particular year, you will need a tables consisting at least 12 rows.

For me it is a bit straight forward. I joined it with my configuration table, consisting around 370 rows. So it could generate months in a year, or days in a year if I need it. Changing from month interval into days interval would be easy, as I need only to change the interval from MONTH to DAY.

rama3i
  • 156
  • 1
  • 7
1

If you're using PostgreSQL, you can combine both date_trunc and generate_series to do some very fun grouping and series generation.

For example, you could use this to generate a table of all dates in the last year:

SELECT current_date - s.a as date 
FROM generate_series(0,365,1) as s(a);

Then, you could use date_trunc to grab the months and group by that date_trunc'ed field:

SELECT date(date_trunc('month', series.date)) as month, COUNT(*) as days 
FROM (SELECT current_date - s.a as date 
FROM generate_series(0,365,1) as s(a)) series 
GROUP BY month;
Sia
  • 8,894
  • 5
  • 31
  • 50
1

You should most definitely do this in your application rather than the DB layer. Simply create an array of dates for the time range, and merge the actual data with the empty dates you pre-created. See this answer to similar question

Community
  • 1
  • 1
code_burgar
  • 12,025
  • 4
  • 35
  • 53
  • 1
    I disagree... I think it's much cleaner to do this in SQL (if possible). I'm using a standard javascript grid that gets it's data from a standard php script that executes the query and produces JSON data. I'm not going to write a separate php script to fetch data for each query in my application (there are hundreds of query's in my application) – Dylan Mar 26 '11 at 13:56
  • Why would you need to write a separate script for each query? – code_burgar Mar 26 '11 at 13:58
  • I was exaggerating. I was just saying that I don't think it's clean to use PHP (or some other kind of serverside scripting) to manipulate the data that you want to show. – Dylan Mar 26 '11 at 14:06
  • Yet you are going to use the scripting langue anyway to execute the query, JSON encode it, and output the results. To each his own, but doing this in the DB layer is as pointless as building HTML via SQL queries. – code_burgar Mar 26 '11 at 14:11
  • Not pointless at all. In my solution I can use the same script over and over again to display data. The only thing that is different each time is the database-query, which I store in separate files. – Dylan Mar 26 '11 at 14:20
  • As I said, to each his own. There is nothing preventing you from doing what you described in your last comment when you use the approach i outlined. – code_burgar Mar 26 '11 at 14:32
0

Create a table (e.g. tblMonths) that includes all 12 months and use a LEFT JOIN (or RIGHT JOIN) on it and your partial source data.

Check out the reference and this tutorial for how this works.

D.N.
  • 2,160
  • 18
  • 26
  • Thanx, I know how to do joins. I just wanted to know if it's possible in SQL to 'create' data on the fly using a formula, but apparently this is not possible. Strange... – Dylan Mar 26 '11 at 13:48
  • Sure, you *can* use the dummy table `dual` to create data using formulas, but I'm not convinced that's the easiest way to solve this one. – D.N. Mar 26 '11 at 13:50
0

I would do something like this:

SELECT COUNT(Order.OrderID)
FROM Orders
WHERE YEAR(Order.DateOrdered) > 2000
GROUP BY MONTH(Order.DateOrdered)

This will give you the number of orders grouped by each month. Then in you application simply assign a ZERO to the months in which no data was returned

I hope this Helps

Jonny
  • 2,787
  • 10
  • 40
  • 62
  • This doesnt work when there is no data in a certain month. That month will not be displayed then. – Dylan Mar 28 '11 at 10:16
  • I know that's why I said "Then in you application simply assign a ZERO to the months in which no data was returned". IMO that is cleaner. – Jonny Mar 28 '11 at 10:51
0

Query on static data MySQL.

You can select static data from hardcoded list with table by this query

SELECT *
FROM (
    values row('Hamza','23'), row('Ali', '24')
) t1 (name, age);
M. Hamza Rajput
  • 7,810
  • 2
  • 41
  • 36