1

I am currently programming an SQL view which should provide a count of a populated field for a particular month.

This is how I would like the view to be constructed:

Country   | (Current Month - 12) Eg Feb 2011 | (Current Month - 11) | (Current Month - 10)
----------|----------------------------------|----------------------|---------------------
UK        | 10                               | 11                   | 23

The number under the month should be a count of all populated fields for a particular country. The field is named eldate and is a date (cast as a char) of format 10-12-2011. I want the count to only count dates which match the month.

So column "Current Month - 12" should only include a count of dates which fall within the month which is 12 months before now. Eg Current Month - 12 for UK should include a count of dates which fall within February-2011.

I would like the column headings to actually reflect the month it is looking at so:

Country | Feb 2011 | March 2011 | April 2011
--------|----------|------------|------------
UK      | 4        | 12         | 0

So something like:

SELECT c.country_name,
        (SELECT COUNT("C1".eldate) FROM "C1" WHERE "C1".eldate LIKE %NOW()-12 Months% AS NOW() - 12 Months
        (SELECT COUNT("C1".eldate) FROM "C1" WHERE "C1".eldate LIKE %NOW()-11 Months% AS NOW() - 11 Months
FROM country AS c
INNER JOIN "site" AS s using (country_id)
INNER JOIN "subject_C1" AS "C1" ON "s"."site_id" = "C1"."site_id"

Obviously this doesn't work but just to give you an idea of what I am getting at.

Any ideas?

Thank you for your help, any more queries please ask.

Aydin Hassan
  • 1,465
  • 2
  • 20
  • 41
  • 1
    I don't know if you can achieve this with a view but you could use a function. Notice in PostgreSQL you can call a function like this: `SELECT * FROM funName()` – DavidEG Feb 27 '12 at 15:12
  • Yeah I thought it would probably be the way, however, I am a novice and not sure how I would go about constructing a function to do this – Aydin Hassan Feb 27 '12 at 15:17

2 Answers2

2

My first inclination is to produce this table:

+---------+-------+--------+
| Country | Month | Amount |
+---------+-------+--------+
| UK      | Jan   | 4      |
+---------+-------+--------+
| UK      | Feb   | 12     |
+---------+-------+--------+

etc. and pivot it. So you'd start with (for example):

SELECT 
  c.country, 
  EXTRACT(MONTH FROM s.eldate) AS month, 
  COUNT(*) AS amount
FROM country AS c
JOIN site AS s ON s.country_id = c.id
WHERE 
  s.eldate > NOW() - INTERVAL '1 year'
GROUP BY c.country, EXTRACT(MONTH FROM s.eldate);

You could then plug that into one the crosstab functions from the tablefunc module to achieve the pivot, doing something like this:

SELECT * 
FROM crosstab('<query from above goes here>') 
  AS ct(country varchar, january integer, february integer, ... december integer);
Daniel Lyons
  • 22,421
  • 2
  • 50
  • 77
1

You could truncate the dates to make the comparable:

WHERE date_trunc('month', eldate) = date_trunc('month', now()) - interval '12 months'

UPDATE

This kind of replacement for your query:

(SELECT COUNT("C1".eldate) FROM "C1" WHERE date_trunc('month', "C1".eldate) =
    date_trunc('month', now()) - interval '12 months') AS TWELVE_MONTHS_AGO

But that would involve a scan of the table for each month, so you could do a single scan with something more along these lines:

SELECT SUM( CASE WHEN date_trunc('month', "C1".eldate) =  date_trunc('month', now()) - interval '12 months' THEN 1 ELSE 0 END ) AS TWELVE_MONTHS_AGO
      ,SUM( CASE WHEN date_trunc('month', "C1".eldate) =  date_trunc('month', now()) - interval '11 months' THEN 1 ELSE 0 END ) AS ELEVEN_MONTHS_AGO
...

or do a join with a table of months as others are showing.

UPDATE2

Further to the comment on fixing the columns from Jan to Dec, I was thinking something like this: filter on the last years worth of records, then sum on the appropriate month. Perhaps like this:

SELECT SUM( CASE WHEN EXTRACT(MONTH FROM "C1".eldate) = 1 THEN 1 ELSE 0 END ) AS JAN
      ,SUM( CASE WHEN EXTRACT(MONTH FROM "C1".eldate) = 2 THEN 1 ELSE 0 END ) AS FEB
      ...

  WHERE date_trunc('month', "C1".eldate) <  date_trunc('month', now())
    AND date_trunc('month', "C1".eldate) >= date_trunc('month', now()) - interval '12 months'
Glenn
  • 8,932
  • 2
  • 41
  • 54
  • Can you explain further please? – Aydin Hassan Feb 27 '12 at 15:49
  • Okay this works good thanks, but is there no way to actually dynamically name the columns? – Aydin Hassan Feb 27 '12 at 16:15
  • @AydinHassan Not that I am aware of. In this case I would lean to fixing the columns (JAN - DEC), then do a bit of math involving now() - jan, now() - feb, etc rather than the hard-coded -12, -11, ... There would need to be some handling of year boundaries, but that is probably some min/max stuff. – Glenn Feb 27 '12 at 16:27