Oracle
 sql >> Base de Dados >  >> RDS >> Oracle

Oracle SQL - Identificar intervalos de valores sequenciais


Isso é fácil de fazer com uma técnica chamada Tabibitosan.

O que essa técnica faz é comparar as posições das linhas de cada grupo com o conjunto geral de linhas, para descobrir se as linhas do mesmo grupo estão próximas umas das outras ou não.

Por exemplo, com seus dados de exemplo, fica assim:
WITH your_table AS (SELECT 1 ID, 'Michael' NAME, 'Marketing' department FROM dual UNION ALL
                    SELECT 2 ID, 'Alex' NAME, 'Marketing' department FROM dual UNION ALL
                    SELECT 3 ID, 'Tom' NAME, 'Marketing' department FROM dual UNION ALL
                    SELECT 4 ID, 'John' NAME, 'Sales' department FROM dual UNION ALL
                    SELECT 5 ID, 'Brad' NAME, 'Marketing' department FROM dual UNION ALL
                    SELECT 6 ID, 'Leo' NAME, 'Marketing' department FROM dual UNION ALL
                    SELECT 7 ID, 'Kevin' NAME, 'Production' department FROM dual)
-- end of mimicking your table with data in it. See the SQL below:
SELECT ID,
       NAME,
       department,
       row_number() OVER (ORDER BY ID) overall_rn,
       row_number() OVER (PARTITION BY department ORDER BY ID) department_rn,
       row_number() OVER (ORDER BY ID) - row_number() OVER (PARTITION BY department ORDER BY ID) grp
FROM   your_table;

        ID NAME    DEPARTMENT OVERALL_RN DEPARTMENT_RN        GRP
---------- ------- ---------- ---------- ------------- ----------
         1 Michael Marketing           1             1          0
         2 Alex    Marketing           2             2          0
         3 Tom     Marketing           3             3          0
         4 John    Sales               4             1          3
         5 Brad    Marketing           5             4          1
         6 Leo     Marketing           6             5          1
         7 Kevin   Production          7             1          6

Aqui, dei a todas as linhas em todo o conjunto de dados um número de linha em ordem crescente de ID (o overall_rn coluna), e dei às linhas em cada departamento um número de linha (o department_rn coluna), novamente em ordem crescente de id.

Agora que fiz isso, podemos subtrair um do outro (o grp coluna).

Observe como o número na coluna grp permanece o mesmo para as linhas do departamento que estão próximas umas das outras, mas muda cada vez que há uma lacuna.

Por exemplo. para o departamento de Marketing, as linhas 1-3 estão próximas umas das outras e têm grp =0, mas a 4ª linha de Marketing está na verdade na 5ª linha do conjunto de resultados geral, então agora tem um número de grp diferente. Como a 5ª linha de marketing está na 6ª linha do conjunto geral, ela tem o mesmo número de grp que a 4ª linha de marketing, então sabemos que elas estão uma ao lado da outra.

Uma vez que tenhamos essas informações de grp, é uma questão simples de fazer um agrupamento de consulta agregada no departamento e em nossa nova coluna grp, usando min e max para encontrar os ids inicial e final:
WITH your_table AS (SELECT 1 ID, 'Michael' NAME, 'Marketing' department FROM dual UNION ALL
                    SELECT 2 ID, 'Alex' NAME, 'Marketing' department FROM dual UNION ALL
                    SELECT 3 ID, 'Tom' NAME, 'Marketing' department FROM dual UNION ALL
                    SELECT 4 ID, 'John' NAME, 'Sales' department FROM dual UNION ALL
                    SELECT 5 ID, 'Brad' NAME, 'Marketing' department FROM dual UNION ALL
                    SELECT 6 ID, 'Leo' NAME, 'Marketing' department FROM dual UNION ALL
                    SELECT 7 ID, 'Kevin' NAME, 'Production' department FROM dual)
-- end of mimicking your table with data in it. See the SQL below:
SELECT department,
       MIN(ID) start_id,
       MAX(ID) end_id
FROM   (SELECT ID,
               NAME,
               department,
               row_number() OVER (ORDER BY ID) - row_number() OVER (PARTITION BY department ORDER BY ID) grp
        FROM   your_table)
GROUP BY department, grp;

DEPARTMENT   START_ID     END_ID
---------- ---------- ----------
Marketing           1          3
Marketing           5          6
Sales               4          4
Production          7          7

N.B., eu assumi que as lacunas nas colunas id não são importantes (ou seja, se não houvesse linha para id =6 (portanto, os ids de Leo e Kevin eram 7 e 8 respectivamente), então Leo e Brad ainda apareceriam no mesmo grupo, com um id inicial =5 e id final =7.

Se as lacunas nas colunas id contarem como indicando um novo grupo, você poderá usar o id para rotular o conjunto geral de linhas (ou seja, não é necessário calcular o global_rn; basta usar a coluna id).

Isso significa que sua consulta se tornaria:
WITH your_table AS (SELECT 1 ID, 'Michael' NAME, 'Marketing' department FROM dual UNION ALL
                    SELECT 2 ID, 'Alex' NAME, 'Marketing' department FROM dual UNION ALL
                    SELECT 3 ID, 'Tom' NAME, 'Marketing' department FROM dual UNION ALL
                    SELECT 4 ID, 'John' NAME, 'Sales' department FROM dual UNION ALL
                    SELECT 5 ID, 'Brad' NAME, 'Marketing' department FROM dual UNION ALL
                    SELECT 7 ID, 'Leo' NAME, 'Marketing' department FROM dual UNION ALL
                    SELECT 8 ID, 'Kevin' NAME, 'Production' department FROM dual)
-- end of mimicking your table with data in it. See the SQL below:
SELECT department,
       MIN(ID) start_id,
       MAX(ID) end_id
FROM   (SELECT ID,
               NAME,
               department,
               ID - row_number() OVER (PARTITION BY department ORDER BY ID) grp
        FROM   your_table)
GROUP BY department, grp;

DEPARTMENT   START_ID     END_ID
---------- ---------- ----------
Marketing           1          3
Sales               4          4
Marketing           5          5
Marketing           7          7
Production          8          8