user1366606
user1366606

Reputation: 111

How do I fetch data grouped by month and year including the period with no data?

Relatively new to SQL and I am stumped on this little issue. This doesn't seem to be very difficult to do, but I just can't seem to figure it out.

I am trying to get a count of transactions from a table, but I can't seem to get sql to get me to show all of the months instead of only the months and the year that the transactions occured in.

Here is the query:

SELECT      YEAR(dbo.countproject.trans_date)   AS [TransYear]
        ,   MONTH (dbo.countproject.trans_date) AS [TransMonth]
        ,   COUNT(Id)                           AS TransNum 
FROM        dbo.countproject
WHERE       dbo.countproject.make_name  = 'Honda'
AND         dbo.countproject.model_name = 'Civic'
AND         dbo.countproject.type       = 'Sale'
AND         dbo.countproject.trans_type LIKE '%%EU'
AND         dbo.countproject.mfr        = '2000'
GROUP BY    YEAR(dbo.countproject.trans_date)
        ,   MONTH(dbo.countproject.trans_date)
ORDER BY    YEAR(dbo.countproject.trans_date)

The query returns the following result set:

| TransYear | TransMonth | TransNum |
|-----------|------------|----------|
|  2004     |     1      |     5    |
|  2004     |     3      |     1    |
|  2005     |     4      |     2    |

and so forth....

I am trying to get it to show all the months and years even if the value is NULL.

I tried creating a new table which will have the year and the month as columns to get it to join somehow, but I am lost.

Any help would be appreciated.

Upvotes: 2

Views: 4243

Answers (3)

Gordon Linoff
Gordon Linoff

Reputation: 1269513

Alas, the SQL statement can only return the data in the table. If you want all months, you need either a table with the year/month combinations you are intererested in or, preferably, a calendar table with all days and information about them.

With a calendar table, your query have a from clause that looked like:

from 
(
   select distinct year(date) as yr, month(date) as mon
   from calendar c
   where date between <earliest> and <latest>
) c 
left outer join CountTable ct
  on c.yr = year(ct.trans_date) 
  and c.mon = month(ct.trans_date)

Upvotes: 0

user756519
user756519

Reputation:

If you are using SQL Server 2005 or above, you could use Common Table Expressions (CTE) to get the desired result. Below example shows how you can fetch the results as you had described in the question.

Click here to view the demo in SQL Fiddle.

Description:

  • Create and insert statements create the table and populates with some sample data. I have created the table based on the query provided in the question.
  • The statement within the WITH clause is executing a recursive expression. In this case the SELECT above the UNION ALL fetches the minimum and maximum dates available in the table dbo.countproject
  • Once the minimum date is fetched, the second SELECT statement after the UNION ALL increments the date in 1 month intervals until the recursive expression reaches the maximum date available in the table.
  • The recursive CTE has produced all the available dates possible. This output is available in the table named alltransactions.
  • We have to join this CTE output alltransactions with the actual table countproject using LEFT OUTER JOIN since we want to show all years and months even if there are no transactions.
  • The tables alltransactions and countproject are joined on the year and month parts of the date. The query then applies the necessary filters in the WHERE clause and then groups the data by year and month before ordering it by year and month.
  • You can notice from the sample data that the earliest date in the table is 2004-07-01 and the latest date is 2005-12-01. Hence the output shows from year 2004 / month 07 till year 2005 / month 12.

Hope that helps.

Script:

CREATE TABLE dbo.countproject
(   
        id          INT         NOT NULL IDENTITY
    ,   trans_date  DATETIME    NOT NULL
    ,   make_name   VARCHAR(20) NOT NULL
    ,   model_name  VARCHAR(20) NOT NULL
    ,   type        VARCHAR(20) NOT NULL
    ,   trans_type  VARCHAR(20) NOT NULL
    ,   mfr         INT         NOT NULL
);

INSERT INTO dbo.countproject (trans_date, make_name, model_name, type, trans_type, mfr) VALUES
    ('1900-01-01', 'Honda',     'Civic',    'Sale', 'EU', 2000),
    ('1900-01-01', 'Toyota',    'Corolla',  'Sale', 'EU', 2000),
    ('2004-07-01', 'Nissan',    'Altima',   'Sale', 'EU', 2000),
    ('2005-12-01', 'Toyota',    'Camry',    'Sale', 'EU', 2000),
    ('2004-04-01', 'Ford',      'Focus',    'Sale', 'EU', 2000),
    ('2005-08-01', 'Honda',     'Civic',    'Sale', 'EU', 2000),
    ('2005-11-01', 'Toyota',    'Camry',    'Sale', 'EU', 2000),
    ('2004-08-01', 'Toyota',    'Corolla',  'Sale', 'EU', 2000),
    ('2005-12-01', 'Honda',     'Civic',    'Sale', 'EU', 2000),
    ('2004-07-01', 'Honda',     'Civic',    'Sale', 'EU', 2000),
    ('2004-11-01', 'Honda',     'Civic',    'Sale', 'EU', 2000),
    ('2005-08-01', 'Honda',     'Civic',    'Sale', 'EU', 2000);


;WITH alltransactions
AS
(
    SELECT      MIN(trans_date) AS continuousdate
            ,   MAX(trans_date) AS maximumdate
    FROM        dbo.countproject
    WHERE           trans_date <> '1900-01-01'
    UNION ALL 
    SELECT      DATEADD(MONTH, 1, continuousdate) AS continuousdate
            ,   maximumdate
    FROM        alltransactions
    WHERE       DATEADD(MONTH, 1, continuousdate) <= maximumdate
)
SELECT          YEAR(at.continuousdate)     AS [Year]
            ,   MONTH(at.continuousdate)    AS [Month]

            ,   COUNT(cp.trans_date)        AS [Count]
FROM            alltransactions at
LEFT OUTER JOIN countproject    cp
ON              YEAR(at.continuousdate)     = YEAR(cp.trans_date)
AND             MONTH(at.continuousdate)    = MONTH(cp.trans_date)
AND             cp.make_name                = 'Honda'
and             cp.model_name               = 'Civic'
and             cp.type                     = 'Sale'
and             cp.trans_type               LIKE '%EU'
and             cp.mfr                      = '2000'
GROUP BY        YEAR(at.continuousdate)
            ,   MONTH(at.continuousdate)
ORDER BY        [Year]
            ,   [Month];

Output:

Year   Month  Count
-----  ------ -----
2004      4     0
2004      5     0
2004      6     0
2004      7     1
2004      8     0
2004      9     0
2004     10     0
2004     11     1
2004     12     0
2005      1     0
2005      2     0
2005      3     0
2005      4     1
2005      5     0
2005      6     0
2005      7     0
2005      8     2
2005      9     0
2005     10     0
2005     11     0
2005     12     1

Upvotes: 3

MUG4N
MUG4N

Reputation: 19717

You have to use an LEFT or RIGHT OUTER JOIN!

Here is an easy sample: http://www.w3schools.com/sql/sql_join_left.asp

You should get it done by yourself.

Greetings

Upvotes: 0

Related Questions