Filter records by year and month where year and month are columns in the table

StackOverflow https://stackoverflow.com/questions/23627317

  •  21-07-2023
  •  | 
  •  

So I have this specific table structure

State_Code | ColA | ColB | Year | Month
----------  ------  ----  ------  -----
AK            5      3     2013   4
AK            6      1     2014   8
AK            3      4     2012   9
.
.
.
.
.

I do not have access to change the table structure. The required query is described as follows

Find total ColA, ColB for each state between the last 12 months and the last 24 months.

Questions Is it possible to get such a query done without using stored procedures. I can frame a query using add_months for getting the right year, but I can't get around the logical issue of selecting the month range.

有帮助吗?

解决方案

Community Wiki because it is borrowing heavily from other answers.

The answer by Farhad Jabiyev (now deleted — see Is it OK to unaccept an accepted answer after weeks) was:

SELECT STATE_CODE, SUM(COLA) SUM_COLA, SUM(COLB) SUM_COLB
FROM TABLE_NAME 
WHERE to_date(ExtractDay(sysdate) ||  MONTH || YEAR, 'ddMMyyyy')
    between add_months(sysdate,-12) AND add_months(sysdate,-24)
GROUP BY STATE_CODE;

This answer runs into problems when the current date is at the end of the month for some intervals (with the -24..-12 month range, usually only on a Leap Day, but for other ranges of months, it can run into problems from the 29th of the month onwards).

However, it is firmly on the right track and only needs (relatively) trivial fixing. The requirement is to get the year/month combination for the current month minus 12 months and the current month minus 24 months.

  • Change the 'day of month' from ExtractDay(sysdate) to '01' of month (since the data stored only has one month granularity and every month has a first of the month, but not every month has a 29th, 30th or 31st).

If you use dates, then you need to get the first day of the current month (twice) which is verbose:

SELECT STATE_CODE, SUM(COLA) SUM_COLA, SUM(COLB) SUM_COLB
  FROM TABLE_NAME 
 WHERE to_date('01' ||  MONTH || YEAR, 'ddMMyyyy')
       BETWEEN to_date('01' || ExtractMonth(add_months(sysdate, -24)) ||
                               ExtractYear(add_months(sysdate,  -24)), 'ddMMyyyy')
       AND     to_date('01' || ExtractMonth(add_months(sysdate, -12)) ||
                               ExtractYear(add_months(sysdate,  -12)), 'ddMMyyyy')
 GROUP BY STATE_CODE;

Alternatively, and probably better (if only because simpler) is to convert the year/month combination as suggested by Gordon Linoff in his answer:

SELECT STATE_CODE, SUM(COLA) SUM_COLA, SUM(COLB) SUM_COLB
  FROM TABLE_NAME 
 WHERE (MONTH + 100 * YEAR)
       BETWEEN (ExtractMonth(add_months(sysdate, -24)) + 100 *
                ExtractYear(add_months(sysdate,  -24)))
       AND     (ExtractMonth(add_months(sysdate, -12)) + 100 *
                ExtractYear(add_months(sysdate,  -12))
 GROUP BY STATE_CODE;

Some residual issues to note:

  1. The standard behaviour of x BETWEEN y AND z requires that y is less than or equal to z. That is, x BETWEEN y AND z is equivalent to x >= y AND x <= z. (Is there any DBMS that treats it as equivalent to x >= MIN(y, z) AND z <= MAX(y, z)?) Hence the code above switches -12 and -24 so the the range is correct.
  2. The bounds of x BETWEEN y AND z are inclusive, so the -24 to -12 range covers 13 months, not 12 months. As long as that's what you want, it's fine. Otherwise, you probably need -24..-13 or maybe -23..-12.
  3. You may be able to use functions YEAR() and MONTH() in lieu of ExtractYear() and ExtractMonth(). You might need to worry about ambiguity between column names Year and Month and functions of the same name. Whether this matters will depend on the DBMS.

This question does highlight that date arithmetic is tricky stuff, and leap years and ends of months always have to be kept in mind when subtracting months from dates. For example, what date corresponds to 2 months before 2014-04-29, 2014-04-30, 2014-08-31, 2016-04-29, 2016-04-30?

Warning: untested SQL: syntax errors are possible.

其他提示

SELECT STATE_CODE, SUM(COLA) SUM_COLA, SUM(COLB) SUM_COLB
FROM TABLE_NAME 
WHERE to_date(ExtractDay(sysdate) ||  MONTH || YEAR, 'ddMMyyyy')
    BETWEEN add_months(sysdate,-12) AND add_months(sysdate,-24)
GROUP BY STATE_CODE;

As a note, I find the easiest way to do this is to not use dates. Just translate the year-month expressions into a number in the format YYYYMM. For instance, May 2014 becomes 201405. I would also use conditional aggregation to get two columns with the historical summaries:

select state_code,
       sum(case when year * 100 + month > (year(sysdate) - 1) * 100 + month(sysdate) and
                     year * 100 + month <= year(sysdate) * 100 + month(sysdate)
                then colA + colB else 0
           end) as AB_12,
       sum(case when year * 100 + month > (year(sysdate) - 2) * 100 + month(sysdate) and
                     year * 100 + month <= year(sysdate) * 100 + month(sysdate)
                then colA + colB else 0
           end) as AB_24
from table t
group by state_code;
select t.state_code, sum(t.cola) total_cola, sum(t.colb) total_colb
from your_table t
where to_date(t.year || lpad(t.month, 2, '0'), 'yyyymm')
      between add_months(sysdate, -12)
          and add_months(sysdate, -24)
group by t.state_code
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top