Cálculo del porcentaje de una fila sobre la suma total

13

Disculpas por el mal título, no estaba seguro de cuál sería un buen título para esto.

Esta es actualmente (vista simplificada de) los datos con los que estoy trabajando

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

Necesito calcular el porcentaje de la comisión total, cada agente es responsable.

Entonces, para el Agente Smith, el Porcentaje se calcularía como (Agent Smith's commission / Sum(commission)*100

Entonces, mis datos esperados serían

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

Tengo una función que devuelve la comisión para cada agente. Tengo otra función que devuelve el porcentaje como (Commission/Sum(Commission))*100. El problema es que Sum(commission)se calcula para todas y cada una de las filas, y dado que esta consulta se ejecutaría en un Data Warehouse, el conjunto de datos sería bastante grande (actualmente, está por debajo de 2000 registros) y, sinceramente, un mal enfoque (IMO )

¿Hay alguna manera de que Sum(Commission)no se calcule para cada fila que se va a buscar?

Estaba pensando algo en las líneas de una consulta de 2 partes, la primera parte buscaría la sum(commission)variable / tipo de paquete y la segunda parte se referiría a este valor precalculado, pero no estoy seguro de cómo puedo lograr esto.

Estoy limitado a usar SQL y estoy ejecutando Oracle 10g R2.

Sathyajith Bhat
fuente
Obviamente, no es una pregunta de DBA (¿tal vez si se tratara de espacios de tablas en lugar de vendedores?), Probablemente debería estar en Stack Overflow.
Gaius

Respuestas:

23

Estás buscando el analytical function ratio_to_report

select 
  agent,
  round(ratio_to_report(commission) over ()*100) "% Comm."
from  
  commissions;
René Nyffenegger
fuente
Impresionante, no sabía sobre esto, gracias!
Sathyajith Bhat
9

Para devolver todos los agentes con sus comisiones y porcentajes de comisión, use una función analítica sin cláusula analítica para que la partición esté sobre toda la tabla:

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

Como aprendí de René Nyffenegger (+1), la función ratio_to_report refuerza esta sintaxis.

El uso de un paquete para almacenar la SUMA de la Comisión implicaría PL / SQL, que excluyó específicamente al indicar que desea una solución SQL, pero dado que ya está utilizando funciones, supongo que su intención no era excluir PL / SQL. Si este es el caso, la solución del paquete puede ayudar, pero depende de cómo funcione su aplicación.

Cuando su sesión se crea por primera vez y llama a la función en el paquete para obtener la comisión, hay una llamada implícita al constructor de paquetes que podría obtener la suma y almacenarla. Luego, podría hacer referencia a la suma almacenada en su función de obtener comisión y solo tendría que hacer la suma una vez. Por supuesto, tan pronto como llame a la función desde una sesión diferente, la suma se calculará nuevamente. Además, llamar a la función para cada agente sería considerablemente menos eficiente que llamar a una declaración SQL para todos los agentes si su aplicación se puede diseñar de esa manera.

Es posible que desee considerar convertir su función en un procedimiento que devuelva un cursor para la consulta anterior o tal vez tenga una función que devuelva los resultados de la consulta como un conjunto de resultados canalizados.

Data de muestra:

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);
Leigh Riffel
fuente
5

Puede intentar la siguiente consulta, la suma (comisión) solo se calculará una 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;
Robert Durgin
fuente
Eso funciona y devuelve los datos correctos, pero es menos eficiente que una función analítica que realiza un escaneo completo de la tabla en lugar de dos (suponiendo que no haya índices).
Leigh Riffel
1
@Leigh ~ ¿Cómo puede hacerlo en una pasada ya que la forma manual requiere dos pasadas? No puedo ver cómo las computadoras pueden hacer la operación mágica de una sola pasada% ofTotal un ...
jcolebrand
@jcolebrand Los datos solo se leen de los bloques de la base de datos una vez. Probablemente esté haciendo múltiples pases de sus resultados en memoria, pero esto generalmente es más rápido que leer los bloques de la base de datos dos veces. Hay compensaciones en la memoria y la CPU entre estas opciones, por lo que la elección no siempre es clara, pero en este caso creo que sí.
Leigh Riffel
1
@Leigh ~~ Sí, una mayor consideración me llevaría a creer que eso es todo lo que podría estar haciendo, solo optimizaciones de caja negra. De todos modos, una solución ingeniosa en su respuesta. Gracias: D
jcolebrand
0
  select 
  Agent, Commission,
  (
      ROUND(
       (Commission *100) / 
          (
            (SELECT SUM(Commission)
             FROM commissions AS A)
          )
       ) 
  ) AS Porcentaje
  from  
  commissions
JoeDeg
fuente