Clique aqui para ler esse artigo em PDF.imagem_pdf.jpg

capaSQL12.JPG

Clique aqui para ler todos os artigos desta edição

Oracle Obtendo performance com funções analíticas

 

As funções analíticas foram introduzidas no Oracle (a partir da versão 8.1.6) para facilitar as análises de negócios (Business Inteligence - BI) e dar suporte a tarefas analíticas. Sua aplicabilidade está bastante relacionada a sistemas de data warehouse e data mining, uma vez que estes utilizam dados agrupados e sumarizados para facilitar a análise. Entre outras coisas, as funções analíticas possibilitam gerar:

1.           Classificações de elementos com base em perguntas do tipo “quais os produtos mais vendidos?” ou “quais os setores mais lucrativos?”;

2.           Faixas de valores (partições), o que permite agrupá-las por número específico (por exemplo, pela categoria “cidades com desempenho semelhantes”);

3.           Agregados cumulativos ou centralizados nos quais o valor de uma linha depende do valor gerado por outra, respondendo a questões como “qual o valor acumulado de vendas deste ano com base no total de vendas mensal?” ou “qual o percentual de crescimento das vendas no último ano?”.

Em geral, as funções analíticas são pós-processadas (ou seja, são executadas sobre o resultado de um comando SELECT) e seguem este princípio de funcionamento:

1. Inicialmente, são resolvidas as associações e cláusulas WHERE, GROUP BY e HAVING, a fim de que sejam fornecidos os dados considerados base;

2. Uma fez feito isso, todos os cálculos serão resolvidos, inclusive as funções analíticas.

 

Embora seja possível criar funções semelhantes às analíticas por meio de visões auxiliares e subqueries, a eficiência não é a mesma, porque o conjunto da base de dados é resolvido primeiro e só depois são aplicadas as funções analíticas. Vejamos o exemplo da Listagem 1, na qual é calculada “a soma dos salários dos departamentos em função dos salários totais da empresa”.

 

Listagem 1 - Utilização de Função Analítica no Lugar de uma View

 

-- Sem pós processamento

-- Visão com o salário total

Create view sal_geral As Select SUM(sal) total_sal From Emp;

 

-- Comando Select Utilizando a View

Select deptno, (SUM(sal) / total_sal)

*100 From    Emp, sal_geral Group by deptno, total_sal

 

-- Comando Select Com pós processamento sem utilizar a view

Select deptno, (SUM(sal) / SUM(SUM(sal)) OVER ())

*100 From    Emp Group by deptno

 

Nota: As tabelas utilizadas neste exemplo pertencem ao esquema SCOTT (senha tiger), instalado automaticamente com o banco de dados e o qual fornece algumas tabelas já populadas.

 

O comando Select com pós-processamento da Listagem 1 apresenta a soma dos salários por departamento (sum(sal)) em relação a soma de todos os salários (SUM(sal) OVER()) da tabela de empregados. A cláusula OVER (sem parâmetros) especifica que serão somados todos os registros da tabela (sem restrições ou particionamentos).

O uso da cláusula OVER após uma função a caracteriza como função analítica. Essas funções seguem uma sintaxe padrão, que será explicada no decorrer da apresentação das funções existentes na Listagem 2.

 

Listagem 2

 

<nome_da_função>([parâmetrto(a)])

OVER ( [PARTITION BY <lista de itens para particionamento>]

[ORDER BY <lista de itens para ordenação>

[ASC ou DESC]]

[NULLS FIRST ou NULLS LAST] [BETWEEN

<valor inicial ou UNBOUNDED PRECEDING>

AND                     <valor final ou CURRENT ROW>])

 

Ordenação

A cláusula ORDER BY especifica a seqüência (ordenação) em que os dados serão agregados dentro de uma partição ou do conjunto total dos dados retornados. Assim como na cláusula ORDER BY do SQL padrão, os complementos ASC e DESC são utilizados para especificar o sentido da ordenação (ascendente ou descendente, respectivamente). As cláusulas NULLS FIRST e NULLS LAST especificam se os valores nulos dos itens a serem ordenados serão colocados no início ou no final da classificação. Caso esta cláusula seja omitida, os valores nulos serão considerados maiores que os outros.

O exemplo da Listagem 3 traz para cada mês do resumo de vendas o valor total vendido (SUM(vl_venda)) e o valor acumulado (SUM(SUM(vl_venda)) OVER (ORDER BY nr_mes)), ordenado pelo mês da venda. Por valor acumulado, entende-se o somatório dos valores para os meses menores ou iguais ao mês do registro atual. A cláusula ORDER BY nr_mes causa esse ‘efeito’ de acumulado. Por exemplo, se o item de ordenação fosse o valor da soma, o resultado acumulado seria 60, 180, 310, 460, 670 e 910.

 

Listagem 3 - Utilizando a Função Over com a Cláusula Order By

 

SELECT nr_mes, SUM(vl_venda) TotMes,

SUM(SUM(vl_venda))

OVER(ORDER BY nr_mes) Acumulado

FROM resumo_venda

WHERE nr_ano = 2002

GROUP BY nr_mes;

NR_MES

------------

TOTMES

------------

ACUMULADO

-------------------

1

2

3

4

5

6

60

150

240

130

120

210

60

210

450

580

700

910

 

Particionamento de Dados

A cláusula PARTITION BY não é obrigatória. Seu uso significa que o cálculo (soma, rank, média etc) recomeçará a cada mudança no conteúdo dos campos listados na cláusula. Ou seja, o resultado da consulta é dividido em grupos (partições), com os quais a função analítica irá trabalhar. Caso não haja partição, a função analítica operará sobre o conjunto completo de dados retornado.

A Listagem 4 traz, para cada produto do resumo de vendas em todos os meses inferiores a maio, o valor total vendido (SUM(v.vl_venda)) e acumulado (SUM(SUM(v.vl_venda)) OVER (PARTITION BY p.nm_produto ORDER BY p.nm_produto, v.nr_mes)).

 

Listagem 4 - Exemplo da função Partition By

 

SELECT p.nm_produto, v.nr_mes,

SUM(v.vl_venda) TotMes,

SUM(SUM(v.vl_venda))

OVER(PARTITION BY p.nm_produto

ORDER BY p.nm_produto,

v.nr_mes) Acumulado

FROM produto p, resumo_venda v

WHERE p.cd_produto = v.cd_produto

AND    v.nr_ano = 2002

AND    v.nr_mes < 5

GROUP BY p.nm_produto, v.nr_mes;

 

NM_PRODUTO

---------------------

NR_MÊS

------------

TOTMES

------------

ACUMULADO

-------------------

teste 1

teste 1
teste 1
teste 1
teste 2
teste 2
teste 2
teste 2

teste 3

teste 3

teste 3

teste 3

1

2

3

4

1

2

3

4

1

2

3

4

10

40

70

100

20

50

80

10

30

60

90

20

10

50

120

220

20

70

150

160

30

90

180

200

 

Janelas

A cláusula de janela (windowing) é utilizada para quebrar a cláusula PARTITION BY em subconjuntos. A janela pode fazer referência a um número físico de linhas (janela física) ou a um intervalo, como por exemplo, de tempo ou de valores (janela lógica). Ou seja, quando uma janela física é definida, pode-se especificar a quantidade de linhas anteriores, posteriores ou ambas em relação à linha atual. Já na janela lógica, é possível valorar o limite inferior e/ou superior do conjunto de dados com base nas colunas que irão compor a janela (por exemplo, 2 meses antes com relação à data de venda ou 5000 a menos com relação ao valor da nota fiscal).

A Listagem 5 apresenta a sintaxe para a definição de janelas e as Listagens 6, 7 e 8 exemplificam a sua utilização.

 

Listagem 5 - Sintaxe utilizada para a definição de janelas.

 

ROWS / RANGE <[UNBOUNDED PRECEDING ou valor PRECEDING] /

BETWEEN           [UNBOUNDED PRECEDING ou valor PRECEDING]

AND                     [CURRENT ROW ou valor FOLLOWING]>

 

Onde:

ROWS: Janela física.

RANGE: Janela lógica.

BETWEEN ... AND ...: Especifica os pontos inicial e final da janela. Se BETWEEN for omitido e um ponto final for

especificado, este ponto será utilizado como ponto de inicio e o CURRENT ROW será utilizado como ponto final;

UNBOUNDED PRECEDING: Especifica que a janela começa na primeira linha da partição, ou na primeira linha do

conjunto de resultados, se não houver partição;

UNBOUNDED FOLLOWING: Especifica que a janela termina na última linha da partição, ou na última linha do conjunto

de resultados, se não houver partição.

CURRENT_ROW: Se for ponto de inicio para ROWS, faz com que a linha corrente seja o início da janela; para RANGE,

faz com que o valor corrente seja o inicio da janela. Como ponto de fim para ROWS, faz com que a linha corrente

seja o fim da janela; para RANGE, faz com que o valor corrente seja o fim da janela.

<n> FOLLOWING – Indica que as n linhas seguintes serão consideradas.

<n> PRECEDING – Indica que as n linhas anteriores serão consideradas.

 

A Listagem 6 calcula a média das vendas (arredondada em duas casas – ROUND(AVG(SUM(f.sales)) ..., 2)) por ano até o mês de junho (conforme restrição da cláusula WHERE), considerando os valores cumulativos dos meses anteriores. Vamos analisar a cláusula OVER desse comando dividindo-a em duas partes, grifadas em vermelho e azul:

 

ROUND( AVG( SUM(f.sales))

OVER (PARTITION BY

extract(year from t.transaction_date)

ORDER BY

extract(month from t.transaction_date)

ROWS 2 PRECEDING), 2) “Média Até”

 

Listagem 6 - Funções Windowing

 

Select extract(year from t.transaction_date) “Ano”,

extract(month from t.transaction_date) “Mês”,

SUM(f.sales) “Vendas”,

ROUND(

AVG(SUM(f.sales))

OVER (PARTITION BY

extract(year from t.transaction_date)

ORDER BY

extract(month from t.transaction_date)

ROWS 2 PRECEDING), 2) “Média Até”

from times t, sales_fact f

where t.time_key = f.time_key

and extract(month from t.transaction_date) <= 6

and extract(year from t.transaction_date) < 2000

GROUP BY (extract(year from t.transaction_date),

extract(month from t.transaction_date))

ORDER BY 1, 2; -- Ordenação por Ano e Mês, primeiro e segundo itens do SELECT

 

Ano

------

Mês

-----

Vendas

------------

Média Até

-------------

média calculada através da linha atual e as duas linhas anteriores

 

1998

1998

1998

1998

1998

1998

1999

1999

1999

1999

1999

1999

1

2

3

4

5

6

1

2

3

4

5

6

17062,52

16784

20530,15

17966,26

19653,68

15879,39

21901,35

14431,63

20869,88

17375,8

24367,01

19731,9

17062,52

16923,26

18125,56

18426,8

19383,36

17833,11

21901,35

18166,49

19067,62

17559,1

20870,9

20491,57

 

A primeira parte (em vermelho) é responsável pela partição dos cálculos, “resetando” (PARTITION BY) o cálculo a cada ano (extract(year from t.transaction_date)) e acumulando os valores por ordem (ORDER BY) de mês (extract(month from t.transaction_date)).

A segunda parte (em azul) indica que os dados analisados irão considerar as duas linhas anteriores a linha corrente (2 PRECEDING) da janela física (ROWS).

A Listagem 7 calcula a média das vendas por ano até o mês de junho, considerando os valores cumulativos dos três meses anteriores (RANGE 3 PRECEDING). A cláusula over desse comando trabalha de forma semelhante à da listagem 6, porém a janela utilizada para a análise não é mais a janela física de x linhas (definida por ROWS na Listagem 6) e sim a janela lógica dos 3 meses anteriores (independente da posição física em que as linhas referentes a esses três meses sejam retornadas), definida pela cláusula RANGE 3 PRECEDING.

 

Listagem 7 - Funções Windowing

 

Select extract(year from t.transaction_date) “Ano”,

extract(month from t.transaction_date) “Mês”,

SUM(f.sales) “Vendas”,

ROUND(

AVG(SUM(f.sales))

OVER (PARTITION BY

extract(year from t.transaction_date)

ORDER BY

extract(month from t.transaction_date)

RANGE 3 PRECEDING), 2)

“Média Até”

from times t, sales_fact f

where t.time_key = f.time_key

and extract(month from t.transaction_date) <= 6

and extract(year from t.transaction_date) < 2000

GROUP BY (extract(year from t.transaction_date),

extract(month from t.transaction_date))

ORDER BY 1, 2;

 

Ano

Mês

Vendas

Média Até

média calculada através do mês atual e os três

meses anteriores

1998

1998

1998

1998

1998

1998

1999

1999

1999

1999

1999

1999

1

2

3

4

5

6

1

2

3

4

5

6

17062,52

16784

20530,15

17966,26

19653,68

15879,39

21901,35

14431,63

20869,88

17375,8

24367,01

19731,9

17062,52

16923,26

18125,56

18085,73

18733,52

18507,37

21901,35

18166,49

19067,62

18644,67

19261,08

20586,15

 

Os intervalos de tempo permitem analisar as mudanças nos dados no decorrer do tempo, o que facilita a identificação de tendências. Para definir os intervalos de data, é possível usar as seguintes expressões (indicando intervalos de n dias, meses ou anos - antes ou depois - com base em uma determinada data):

 

INTERVAL ‘n’ DAYS ou MONTHS ou YEARS /

PRECEDING ou FOLLOWING

 

A Listagem 8 apresenta a utilização de janelas com análises relacionadas a intervalos de tempo. Este exemplo mostra para determinada data de transação em uma tabela de movimentações de estoque os seguintes

valores: a quantidade movimentada na data, a média das quantidades movimentadas nos 30 dias anteriores (RANGE INTERVAL ‘30’ DAY PRECEDING), a média das quantidades movimentadas nos 30 dias seguintes a contar da data da linha atual (RANGE BETWEEN CURRENT ROW AND INTERVAL ‘30’ DAY FOLLOWING) e a média das quantidades movimentadas nos 60 dias (30 anteriores e 30 seguintes) (RANGE BETWEEN INTERVAL ‘30’ DAY PRECEDING AND INTERVAL ‘30’ DAY FOLLOWING).

 

Listagem 8 - Utilização de Função Analítica Aplicada num Intervalo de Tempo

 

SELECT dt_transacao, qt_movimento,

AVG(qt_movimento) OVER (ORDER BY dt_transacao

RANGE INTERVAL ‘30’ DAY PRECEDING) Ultimos30,

AVG(qt_movimento) OVER (ORDER BY dt_transacao

RANGE BETWEEN CURRENT_ROW

AND INTERVAL ‘30’ DAY FOLLOWING) Proximos30,

AVG(qt_movimento) OVER (ORDER BY dt_transacao

RANGE BETWEEN INTERVAL ‘30’ DAY PRECEDING

AND INTERVAL ‘30’ DAY FOLLOWING) Dias60

FROM movimento

 

Funções Analíticas de Agregação

As funções analíticas de agregação são as mesmas utilizadas para as agregações convencionais (SUM, AVG, MAX, MIN, COUNT, STDDEV, VARIANCE, VAR_SAMP), mas sua sintaxe é expandida para aceitar particionamento de dados. A Listagem 9 apresenta a sintaxe geral das funções analíticas de agregação.

 

Listagem 9 - Sintaxe Geral das Funções Analíticas de Agregação.

 

Função_Analítica_de_Agregação (item a ser calculado)

OVER ([PARTITION BY lista de itens para partição]

[ORDER BY lista de itens que ordenam o cálculo] [ASC ou DESC]

[BETWEEN <valor inicial ou UNBOUNDED PRECEDING>

AND <valor final ou CURRENT ROW>])

 

O exemplo da Listagem 10 permite localizar a região em que cada produto foi mais vendido com a ajuda da função analítica MAX, particionada (agrupada) por produto em uma subquery na cláusula FROM.

 

Listagem 10 - Utilizando Funções de Agregação

 

SELECT nm_produto, nm_regiao, sum_qtd

FROM (SELECT p.nm_produto, r.nm_regiao, SUM(v.qt_produto) sum_qtd,

MAX(SUM(v.qt_produto)) OVER (PARTITION BY p.nm_produto) max_sum_qtd

FROM           venda_produto v, regiao r, produto p

WHERE         v.cd_regiao = r.cd_regiao

AND             v.cd_produto = p.cd_produto

GROUP BY    p.nm_produto, r.nm_regiao)

WHERE sum_qtd = max_sum_qtd

 

A subquery apresentada na cláusula FROM da Listagem 10 retornará o nome do produto (p.nm_produto), o nome da região (r.nm_região), a quantidade total vendida deste produto nesta região (SUM(v.qt_produto) sum_qtd) e a quantidade vendida na região em que o produto vendeu melhor (MAX(SUM(v.qt_produto)) OVER (PARTITION BY p.nm_produto) max_sum_qtd). Veja a listagem 11.

 

Listagem 11

 

SELECT p.nm_produto, r.nm_regiao, SUM(v.qt_produto) sum_qtd,

MAX(SUM(v.qt_produto)) OVER (PARTITION BY p.nm_produto) max_sum_qtd

FROM            venda_produto v, regiao r, produto p

WHERE         v.cd_regiao = r.cd_regiao

AND               v.cd_produto = p.cd_produto

GROUP BY    p.nm_produto, r.nm_regiao

 

NM_PRODUTO

---------------------

NM_REGIAO

------------------

SUM_QTD

--------------

MAX_SUM_QTD

----------------------

Suco laranja

Suco laranja

Suco laranja

Suco manga

Suco manga

Suco manga

Suco limão

Suco limão

Suco limão

Sul

Sudeste

Nordeste

Sul

Sudeste

Nordeste

Centro

Sul

Nordeste

100000

200000

150000

50000

100000

120000

300000

150000

210000

200000

200000

200000

120000

120000

120000

300000

300000

210000

 

A partir dessa análise, os registros que apresentarem o somatório da quantidade vendida (sum_qtd) igual ao da coluna analítica que traz o valor máximo de vendas do produto para determinada região (max_sum_qtd), satisfarão a cláusula WHERE sum_qtd = max_sum_qtd da consulta principal e apresentarão a região onde cada produto é mais vendido. Ou seja, na listagem 12 temos o conjunto resultante do código apresentado na listagem 10.

 

Listagem 12

 

NM_PRODUTO

NM_REGIAO

SUM_QTD

Suco laranja

Suco manga

Suco limão

Sudeste

Nordeste

Centro Oeste

200000

120000

300000

 

Cuidados com o ORDER BY em funções analíticas

A utilização do ORDER BY para obter valores acumulados em funções analíticas deve ser feita com cuidado, pois o resultado pode variar de acordo com as colunas utilizadas nessa cláusula. Observe as consultas e os resultados mostrados nas listagens A e B. No primeiro comando SELECT (listagem A), a cláusula de ordenação da soma foi especificada apenas por ordem decrescente de valor de salário (OVER PARTITION BY job ORDER BY sal DESC). Assim, no caso de salários iguais, estes eram agregados de uma só vez (Scott e Ford com salário de 3000 e Martim e Ward com 1250), o que pode ser observado na coluna de valores acumulados. Isso acontece por que a cláusula ORDER BY especifica qual a ordem em que os valores serão agregados e, neste exemplo, a ordem especificada foi apenas “ordem decrescente de valor de salário”. Como não há nenhum critério de desempate estabelecido nesta cláusula, os valores iguais são agregados ao mesmo tempo. No segundo comando (listagem B), o campo ename foi incluído como diferenciador ou critério de desempate para a ordenação na seqüência de agregação (OVER PARTITION BY job ORDER BY sal DESC, ename).

 

Listagem A

 

SELECT

RPAD(ename,20) “Nome do Funcionário”,

RPAD(job, 20) “Cargo”, sal “Salário”,

SUM(sal) OVER (PARTITION BY job ORDER BY sal DESC)

“Acumulado”

FROM emp

ORDER BY 2,3 DESC;

 

Nome do Funcionário

----------------------------

Cargo

-----------------

Salário

---------

Acumulado

---------------

SCOTT

FORD

MILLER

ADAMS

JAMES

SMITH

JONES

BLAKE

CLARK

KING

ALLEN

TURNER

WARD

MARTIN

ANALYST

ANALYST

CLERK

CLERK

CLERK

CLERK

MANAGER

MANAGER

MANAGER

PRESIDENT

SALESMAN

SALESMAN

SALESMAN

SALESMAN

3000

3000

1300

1100

950

800

2975

2850

2450

5000

1600

1500

1250

1250

6000

6000

1300

2400

3350

4150

2975

5825

8275

5000

1600

3100

5600

5600

 

Listagem B

 

SELECT

RPAD(ename,20) “Nome do Funcionário”,

RPAD(job, 20) “Cargo”, sal “Salário”,

SUM(sal) OVER (PARTITION BY job ORDER BY sal DESC, ename)

“Acumulado”

FROM emp

ORDER BY 2,3 DESC;

 

Nome do Funcionário

----------------------------

Cargo

-----------------

Salário

---------

Acumulado

---------------

FORD

SCOTT

MILLER

ADAMS

JAMES

SMITH

JONES

BLAKE

CLARK

KING

ALLEN

TURNER

MARTIN

WARD

ANALYST

ANALYST

CLERK

CLERK

CLERK

CLERK

MANAGER

MANAGER

MANAGER

PRESIDENT

SALESMAN

SALESMAN

SALESMAN

SALESMAN

3000

3000

1300

1100

950

800

2975

2850

2450

5000

1600

1500

1250

1250

3000

6000

1300

2400

3350

4150

2975

5825

8275

5000

1600

3100

4350

5600

 

Conclusões

Este artigo apresentou algumas das funções analíticas disponíveis no Oracle. Este é um assunto bastante vasto, mas espero que esta introdução tenha apresentado alguns pontos que muitas vezes são subutilizados por desconhecimento. Nas próximas edições, daremos continuidade à discussão de outras funcionalidades.

Por fim, vale mencionar o Oracle Discoverer, ferramenta OLAP da Oracle (também assunto para um próximo artigo). Ela permite usar as funções apresentadas aqui bem como outras funções de forma gráfica e visual, proporcionando alta disponibilidade, tempos de resposta reduzidos e alto grau de interatividade com o usuário.