Calcul du pourcentage d'une ligne sur la somme totale

13

Toutes mes excuses pour le mauvais titre, je ne savais pas ce qui serait un bon titre pour ça.

Il s'agit actuellement (vue simplifiée des) données avec lesquelles je travaille

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

Je dois calculer le pourcentage de la commission totale dont chaque agent est responsable.

Ainsi, pour l'agent Smith, le pourcentage serait calculé comme suit: (Agent Smith's commission / Sum(commission)*100

Donc, mes données attendues seraient

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

J'ai une fonction de restitution de la commission pour chaque agent. J'ai une autre fonction renvoyant le pourcentage comme (Commission/Sum(Commission))*100. Le problème est que l' Sum(commission)on calcule pour chaque ligne, et étant donné que cette requête serait exécutée sur un entrepôt de données, l'ensemble de données serait plutôt volumineux (actuellement, c'est juste un peu moins de 2000 enregistrements) et très honnêtement, une mauvaise approche (IMO ).

Existe-t-il un moyen de Sum(Commission)ne pas calculer pour chaque ligne récupérée?

Je pensais à quelque chose sur les lignes d'une requête en 2 parties, la première partie récupérerait le sum(commission)dans une variable / type de package et la deuxième partie ferait référence à cette valeur pré-calculée, mais je ne sais pas comment je peux y parvenir.

Je suis limité à l'utilisation de SQL et j'exécute sur Oracle 10g R2.

Sathyajith Bhat
la source
Pas évidemment une question DBA (peut-être s'il s'agissait d'espaces de table plutôt que de vendeurs?) - devrait probablement être sur Stack Overflow.
Gaius

Réponses:

23

Vous recherchez le analytical function ratio_to_report

select 
  agent,
  round(ratio_to_report(commission) over ()*100) "% Comm."
from  
  commissions;
René Nyffenegger
la source
Génial, je n'en savais rien, merci!
Sathyajith Bhat
9

Pour renvoyer tous les agents avec leurs commissions et pourcentages de commission, utilisez une fonction analytique sans clause analytique afin que la partition soit sur toute la table:

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

Comme je l'ai appris de René Nyffenegger (+1), la fonction ratio_to_report resserre cette syntaxe.

L'utilisation d'un package pour stocker le SUM de la Commission impliquerait PL / SQL, que vous avez spécifiquement exclu en indiquant que vous souhaitez une solution SQL, mais puisque vous utilisez déjà des fonctions, je suppose que votre intention n'était pas d'exclure PL / SQL. Si tel est le cas, la solution de package peut vous aider, mais cela dépend du fonctionnement de votre application.

Lorsque votre session est créée et appelle la fonction dans le package pour obtenir la commission, il y a un appel implicite au constructeur des packages qui pourrait obtenir la somme et la stocker. Ensuite, vous pouvez référencer la somme stockée dans votre fonction get commission et elle ne devra effectuer la somme qu'une seule fois. Bien sûr, dès que vous appelez la fonction à partir d'une session différente, la somme sera à nouveau calculée. En outre, appeler la fonction pour chaque agent serait considérablement moins efficace que d'appeler une instruction SQL pour tous les agents si votre application pouvait être conçue de cette manière.

Vous souhaiterez peut-être envisager de transformer votre fonction en une procédure qui renvoie un curseur pour la requête ci-dessus ou peut-être avoir une fonction qui renvoie les résultats de la requête sous la forme d'un ensemble de résultats en pipeline.

Exemples de données:

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
la source
5

Vous pouvez essayer la requête suivante, la somme (commission) ne sera calculée qu'une seule fois:

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
la source
Cela fonctionne et renvoie les données correctes, mais est moins efficace qu'une fonction analytique qui effectue une analyse complète de la table plutôt que deux (en supposant qu'il n'y ait pas d'index).
Leigh Riffel
1
@Leigh ~ Comment peut-il le faire en un seul passage puisque la voie manuelle nécessite deux passages? Je ne vois pas comment les ordinateurs pourraient faire de% ofTotal une opération magique en un seul passage ...
jcolebrand
@jcolebrand Les données ne sont lues qu'une fois dans les blocs de la base de données. Il effectue probablement plusieurs passes de ses résultats en mémoire, mais cela est généralement plus rapide que de lire deux fois les blocs de base de données. Il y a des compromis en mémoire et en CPU entre ces options, donc le choix n'est peut-être pas toujours clair, mais dans ce cas, je pense que oui.
Leigh Riffel
1
@Leigh ~~ Ouais, un examen plus approfondi m'amènerait à croire que c'est tout ce qu'il pourrait faire, juste des optimisations nerveuses de la boîte noire. Quoi qu'il en soit, une solution astucieuse dans votre réponse. Remerciements: D
jcolebrand
0
  select 
  Agent, Commission,
  (
      ROUND(
       (Commission *100) / 
          (
            (SELECT SUM(Commission)
             FROM commissions AS A)
          )
       ) 
  ) AS Porcentaje
  from  
  commissions
JoeDeg
la source