Calculando a porcentagem de uma linha sobre a soma total


13

Desculpas pelo título ruim, eu não tinha certeza do que seria um bom título para isso.

Atualmente, esses são (visão simplificada dos) dados com os quais estou trabalhando

Agent    |  Commission     
---------|------------
Smith    |    100
Neo      |    200
Morpheus |    300

Preciso calcular a porcentagem da comissão total pela qual cada agente é responsável.

Portanto, para o agente Smith, a porcentagem seria calculada como (Agent Smith's commission / Sum(commission)*100

Então, meus dados esperados seriam

Agent    |  Commission   |  % Commission    
---------|---------------|---------------
Smith    |    100        |     17
Neo      |    200        |     33
Morpheus |    300        |     50

Eu tenho uma função retornando a comissão para cada agente. Eu tenho outra função retornando a porcentagem como (Commission/Sum(Commission))*100. O problema é que Sum(commission)é calculado para cada linha e, como essa consulta seria executada em um Data Warehouse, o conjunto de dados seria bastante grande (atualmente, tem pouco menos de 2000 registros) e, honestamente, uma abordagem ruim (IMO )

Existe uma maneira de Sum(Commission)não calcular para cada linha que está sendo buscada?

Eu estava pensando em algo nas linhas de uma consulta de 2 partes, a primeira parte buscaria a sum(commission)variável / tipo de pacote e a segunda parte se referiria a esse valor pré-calculado, mas não tenho certeza de como conseguir isso.

Estou limitado a usar SQL e estou executando no Oracle 10g R2.


Obviamente, não é uma pergunta do DBA (talvez se fossem espaços de tabela em vez de vendedores?) - provavelmente deva estar no Stack Overflow.
Gaius

Respostas:


23

Você está procurando o analytical function ratio_to_report

select 
  agent,
  round(ratio_to_report(commission) over ()*100) "% Comm."
from  
  commissions;

Incrível, não sabia disso, obrigado!
Sathyajith Bhat

9

Para retornar todos os agentes com suas comissões e porcentagens de comissão, use uma função analítica sem cláusula analítica para que a partição esteja sobre a tabela inteira:

SELECT Agent, commission, 100* commission / (SUM(commission) OVER ()) "% Commission" 
FROM commissions;

Como aprendi com René Nyffenegger (+1), a função ratio_to_report aperta essa sintaxe.

O uso de um pacote para armazenar o SUM da Comissão envolveria PL / SQL, que você excluiu especificamente ao indicar que deseja uma solução SQL, mas como você já está usando funções, presumo que sua intenção não foi excluir PL / SQL. Se for esse o caso, a solução do pacote pode ajudar, mas depende de como seu aplicativo funciona.

Quando sua sessão é criada e chama a função no pacote para obter a comissão, há uma chamada implícita ao construtor de pacotes que pode obter a soma e armazená-la. Em seguida, você pode fazer referência à soma armazenada em sua função de comissão de comissão e ela só precisará fazer a soma uma vez. Obviamente, assim que você chamar a função de uma sessão diferente, a soma será calculada novamente. Além disso, chamar a função para cada agente seria consideravelmente menos eficiente do que chamar uma instrução SQL para todos os agentes se seu aplicativo puder ser projetado dessa maneira.

Você pode considerar transformar sua função em um procedimento que retorne um cursor para a consulta acima ou talvez tenha uma função que retorne os resultados da consulta como um conjunto de resultados em pipeline.

Dados de amostra:

create table commissions (Agent Varchar2(100), Commission Number(3));
insert into commissions values ('Smith',100);
insert into commissions values ('Neo',200);
insert into commissions values ('Morpheus',300);

5

Você pode tentar a seguinte consulta, a soma (comissão) será calculada apenas uma vez:

WITH TOTAL_COMMISSION AS 
(SELECT SUM(COMMISSION) AS TOTAL FROM AGENTS)
SELECT A.AGENT_NAME, A.COMMISSION, ((A.COMMISSION/T.TOTAL)*100) AS "% COMMISSION"
FROM AGENTS A, TOTAL_COMMISSION T;

Isso funciona e retorna os dados corretos, mas é menos eficiente que uma função analítica que faz uma varredura completa de tabela em vez de duas (assumindo que não há índices).
Leigh Riffel

1
@Leigh ~ Como é possível fazer isso em uma passagem, já que a maneira manual exige duas passagens? Eu não posso ver como os computadores podem fazer a operação% ofTotal um mágico uma passagem ...
jcolebrand

@jcolebrand Os dados são lidos apenas dos blocos do banco de dados uma vez. Provavelmente está executando várias passagens dos resultados na memória, mas geralmente é mais rápido do que ler os blocos do banco de dados duas vezes. Existem vantagens e desvantagens na memória e na CPU entre essas opções, portanto a escolha pode nem sempre ser clara, mas nesse caso eu acho que é.
Leigh Riffel

1
@Leigh ~~ Sim, uma consideração mais aprofundada me levaria a acreditar que isso é tudo o que poderia estar fazendo, apenas otimizações tremidas de caixa preta. De qualquer forma, uma solução bacana na sua resposta. Graças: D
jcolebrand

0
  select 
  Agent, Commission,
  (
      ROUND(
       (Commission *100) / 
          (
            (SELECT SUM(Commission)
             FROM commissions AS A)
          )
       ) 
  ) AS Porcentaje
  from  
  commissions
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.