No Oracle 12, você pode usar
MATCH_RECOGNIZE
:SELECT cat,
month,
COUNT(*)
FROM (
SELECT t.*,
TRUNC( "DATE", 'MM' ) AS month
FROM table_name t
)
MATCH_RECOGNIZE(
PARTITION BY cat, month
ORDER BY "DATE", version
ONE ROW PER MATCH
AFTER MATCH SKIP TO LAST change_code
PATTERN ( strt change_code )
DEFINE
change_code AS change_code.some_code <> strt.some_code
)
GROUP BY cat, month
Que, para os dados de amostra:
CREATE TABLE table_name ( CAT, NR, "DATE", VERSION, SOME_CODE ) AS
SELECT 'ABC', 123, TIMESTAMP '2009-02-19 00:00:00 UTC', 1, 'OPP' FROM DUAL UNION ALL
SELECT 'ABC', 456, TIMESTAMP '2009-03-18 00:00:00 UTC', 1, 'ZUM' FROM DUAL UNION ALL
SELECT 'ABC', 444, TIMESTAMP '2009-03-18 00:00:00 UTC', 1, 'ZUM' FROM DUAL UNION ALL
SELECT 'ABC', 444, TIMESTAMP '2009-03-18 00:00:00 UTC', 2, 'MUZ' FROM DUAL UNION ALL
SELECT 'ABC', 456, TIMESTAMP '2009-04-18 00:00:00 UTC', 2, 'XXX' FROM DUAL UNION ALL
SELECT 'ABC', 456, TIMESTAMP '2009-04-18 00:00:00 UTC', 3, 'XXX' FROM DUAL UNION ALL
SELECT 'ABC', 456, TIMESTAMP '2009-04-18 00:00:00 UTC', 4, 'UIO' FROM DUAL UNION ALL
SELECT 'ABC', 456, TIMESTAMP '2009-05-18 00:00:00 UTC', 5, 'RQA' FROM DUAL UNION ALL
SELECT 'DEF', 637, TIMESTAMP '2018-02-16 00:00:00 UTC', 1, 'FAW' FROM DUAL UNION ALL
SELECT 'DEF', 789, TIMESTAMP '2018-02-17 00:00:00 UTC', 1, 'WER' FROM DUAL UNION ALL
SELECT 'GHI', 248, TIMESTAMP '2018-02-17 00:00:00 UTC', 1, 'QWE' FROM DUAL UNION ALL
SELECT 'GHI', 248, TIMESTAMP '2019-02-17 00:00:00 UTC', 2, 'PPP' FROM DUAL UNION ALL
SELECT 'GHI', 357, TIMESTAMP '2020-02-16 00:00:00 UTC', 1, 'FFF' FROM DUAL UNION ALL
SELECT 'GHI', 420, TIMESTAMP '2020-02-16 00:00:00 UTC', 1, 'QDS' FROM DUAL UNION ALL
SELECT 'GHI', 357, TIMESTAMP '2020-02-16 00:00:00 UTC', 2, 'GGG' FROM DUAL UNION ALL
SELECT 'GHI', 357, TIMESTAMP '2020-02-16 00:00:00 UTC', 3, 'LLL' FROM DUAL UNION ALL
SELECT 'GHI', 357, TIMESTAMP '2020-02-16 00:00:00 UTC', 4, 'LLL' FROM DUAL UNION ALL
SELECT 'GHI', 357, TIMESTAMP '2020-08-16 00:00:00 UTC', 4, 'FFF' FROM DUAL UNION ALL
SELECT 'GHI', 357, TIMESTAMP '2020-10-16 00:00:00 UTC', 5, 'ZZZ' FROM DUAL
Saídas:
Se você quiser ver as alterações, então você pode usar:
SELECT *
FROM (
SELECT t.*,
TRUNC( "DATE", 'MM' ) AS month
FROM table_name t
)
MATCH_RECOGNIZE(
PARTITION BY cat, month
ORDER BY "DATE", version
MEASURES
MATCH_NUMBER() AS mn,
FIRST( some_code ) AS change_from,
LAST( some_code ) AS change_to
ONE ROW PER MATCH
AFTER MATCH SKIP TO LAST change_code
PATTERN ( strt change_code )
DEFINE
change_code AS change_code.some_code <> strt.some_code
)
Quais saídas:
db<>fiddle aqui
Se o seu requisito para "dentro de um mês" for que você deseja alterações em que haja no máximo uma diferença de um mês entre a linha anterior e a linha alterada, mesmo que as linhas estejam em dois meses de calendário diferentes (em vez de apenas as alterações que acontecem no mesmo mês), então você pode usar:
SELECT cat,
TRUNC( change_date, 'MM' ) AS month,
COUNT(*)
FROM table_name
MATCH_RECOGNIZE(
PARTITION BY cat
ORDER BY "DATE", version
MEASURES
LAST( "DATE" ) AS change_date
ONE ROW PER MATCH
AFTER MATCH SKIP TO LAST change_code
PATTERN ( strt change_code )
DEFINE
change_code AS (
change_code.some_code <> strt.some_code
AND MONTHS_BETWEEN( change_code."DATE", strt."DATE" ) <= 1
)
)
GROUP BY cat, TRUNC( change_date, 'MM' )
Quais saídas:
db<>fiddle aqui