2016-04-06 12 views
6

Ho una tabella con i clienti, utenti e proventi assimilati al di sotto (in migliaia di realtà di record):Selezione di un sottoinsieme di righe che superano una percentuale dei valori totali

Customer User Revenue 
001  James 500 
002  James 750 
003  James 450 
004  Sarah 100 
005  Sarah 500 
006  Sarah 150 
007  Sarah 600 
008  James 150 
009  James 100 

Quello che voglio fare è quello di restituire solo i clienti con la spesa più alta che costituiscono l'80% delle entrate totali per l'utente.

farlo manualmente vorrei ordinare clienti James' dal loro reddito, il lavoro la percentuale del totale e una percentuale totale corrente, per poi tornare solo i record fino al punto che l'esecuzione visite totali 80%:

Customer User Revenue  % of total Running Total % 
002   James 750   0.38  0.38 
001   James 500   0.26  0.64 
003   James 450   0.23  0.87 <- Greater than 80%, last record 
008   James 150   0.08  0.95 
009   James 100   0.05  1.00 

Ho provato a utilizzare un CTE ma finora sono venuto fuori vuoto. C'è un modo per farlo attraverso una singola query piuttosto che manualmente in un foglio Excel?

risposta

6

SQL Server 2012+ solo

Si potrebbe utilizzare finestrata SUM:

WITH cte AS 
(
    SELECT *, 
      1.0 * Revenue/SUM(Revenue) OVER(PARTITION BY [User]) AS percentile, 
      1.0 * SUM(Revenue) OVER(PARTITION BY [User] ORDER BY [Revenue] DESC) 
       /SUM(Revenue) OVER(PARTITION BY [User]) AS running_percentile 
    FROM tab 
) 
SELECT * 
FROM cte 
WHERE running_percentile <= 0.8; 

LiveDemo


SQL Server 2008:

WITH cte AS 
(
    SELECT *, ROW_NUMBER() OVER(PARTITION BY [User] ORDER BY Revenue DESC) AS rn 
    FROM t  
), cte2 AS 
(
    SELECT c.Customer, c.[User], c.[Revenue] 
      ,percentile   = 1.0 * Revenue/NULLIF(c3.s,0) 
      ,running_percentile = 1.0 * c2.s /NULLIF(c3.s,0) 
    FROM cte c 
    CROSS APPLY 
     (SELECT SUM(Revenue) AS s 
      FROM cte c2 
      WHERE c.[User] = c2.[User] 
      AND c2.rn <= c.rn) c2 
    CROSS APPLY 
     (SELECT SUM(Revenue) AS s 
      FROM cte c2 
      WHERE c.[User] = c2.[User]) AS c3 
) 
SELECT * 
FROM cte2 
WHERE running_percentile <= 0.8; 

LiveDemo2

uscita:

╔══════════╦═══════╦═════════╦════════════════╦════════════════════╗ 
║ Customer ║ User ║ Revenue ║ percentile ║ running_percentile ║ 
╠══════════╬═══════╬═════════╬════════════════╬════════════════════╣ 
║  2 ║ James ║  750 ║ 0,384615384615 ║ 0,384615384615  ║ 
║  1 ║ James ║  500 ║ 0,256410256410 ║ 0,641025641025  ║ 
║  7 ║ Sarah ║  600 ║ 0,444444444444 ║ 0,444444444444  ║ 
╚══════════╩═══════╩═════════╩════════════════╩════════════════════╝ 

EDIT 2:

Sembra quasi lì, l'unico problema è manca l'ultima riga, la terza fila per James lo porta oltre 0,80 ma deve essere incluso.

WITH cte AS 
(
    SELECT *, ROW_NUMBER() OVER(PARTITION BY [User] ORDER BY Revenue DESC) AS rn 
    FROM t  
), cte2 AS 
(
    SELECT c.Customer, c.[User], c.[Revenue] 
      ,percentile   = 1.0 * Revenue/NULLIF(c3.s,0) 
      ,running_percentile = 1.0 * c2.s /NULLIF(c3.s,0) 
    FROM cte c 
    CROSS APPLY 
     (SELECT SUM(Revenue) AS s 
      FROM cte c2 
      WHERE c.[User] = c2.[User] 
      AND c2.rn <= c.rn) c2 
    CROSS APPLY 
     (SELECT SUM(Revenue) AS s 
      FROM cte c2 
      WHERE c.[User] = c2.[User]) AS c3 
) 
SELECT a.* 
FROM cte2 a 
CROSS APPLY (SELECT MIN(running_percentile) AS rp 
      FROM cte2 
      WHERE running_percentile >= 0.8 
       AND cte2.[User] = a.[User]) AS s 
WHERE a.running_percentile <= s.rp; 

LiveDemo3

uscita:

╔══════════╦═══════╦═════════╦════════════════╦════════════════════╗ 
║ Customer ║ User ║ Revenue ║ percentile ║ running_percentile ║ 
╠══════════╬═══════╬═════════╬════════════════╬════════════════════╣ 
║  2 ║ James ║  750 ║ 0,384615384615 ║ 0,384615384615  ║ 
║  1 ║ James ║  500 ║ 0,256410256410 ║ 0,641025641025  ║ 
║  3 ║ James ║  450 ║ 0,230769230769 ║ 0,871794871794  ║ 
║  7 ║ Sarah ║  600 ║ 0,444444444444 ║ 0,444444444444  ║ 
║  5 ║ Sarah ║  500 ║ 0,370370370370 ║ 0,814814814814  ║ 
╚══════════╩═══════╩═════════╩════════════════╩════════════════════╝ 

sembra essere perfetto, tradotto alla mia grande tavolo e ret urna quello di cui ho bisogno, ho passato 5 minuti a lavorarci e non riesco ancora a seguire quello che hai fatto!

SQL Server 2008 non supporta tutto in OVER() clausola, ma ROW_NUMBER fa.

Prima CTE basta calcolare la posizione all'interno di un gruppo:

╔═══════════╦════════╦══════════╦════╗ 
║ Customer ║ User ║ Revenue ║ rn ║ 
╠═══════════╬════════╬══════════╬════╣ 
║  2 ║ James ║  750 ║ 1 ║ 
║  1 ║ James ║  500 ║ 2 ║ 
║  3 ║ James ║  450 ║ 3 ║ 
║  8 ║ James ║  150 ║ 4 ║ 
║  9 ║ James ║  100 ║ 5 ║ 
║  7 ║ Sarah ║  600 ║ 1 ║ 
║  5 ║ Sarah ║  500 ║ 2 ║ 
║  6 ║ Sarah ║  150 ║ 3 ║ 
║  4 ║ Sarah ║  100 ║ 4 ║ 
╚═══════════╩════════╩══════════╩════╝ 

Seconda CTE:

  • c2 subquery Calcolare esecuzione totale sulla base di rango da ROW_NUMBER
  • c3 calcolare intera somma per utente

Nella query finale s la query secondaria trova il valore minimo di running che supera l'80%.

EDIT 3:

Uso ROW_NUMBER è in realtà ridondante.

WITH cte AS 
(
    SELECT c.Customer, c.[User], c.[Revenue] 
      ,percentile   = 1.0 * Revenue/NULLIF(c3.s,0) 
      ,running_percentile = 1.0 * c2.s /NULLIF(c3.s,0) 
    FROM t c 
    CROSS APPLY 
     (SELECT SUM(Revenue) AS s 
      FROM t c2 
      WHERE c.[User] = c2.[User] 
      AND c2.Revenue >= c.Revenue) c2 
    CROSS APPLY 
     (SELECT SUM(Revenue) AS s 
      FROM t c2 
      WHERE c.[User] = c2.[User]) AS c3 
) 
SELECT a.* 
FROM cte a 
CROSS APPLY (SELECT MIN(running_percentile) AS rp 
      FROM cte c2 
      WHERE running_percentile >= 0.8 
       AND c2.[User] = a.[User]) AS s 
WHERE a.running_percentile <= s.rp 
ORDER BY [User], Revenue DESC; 

LiveDemo4

+1

@bendataclear Si prega di vedere aggiornato – lad2025

+0

Che sembra quasi lì, l'unico guaio è che manca l'ultima riga, la terza fila per James lo porta oltre 0,80 ma deve essere incluso. Se questo non è possibile anche se non è un disastro. – bendataclear

+1

@bendataclear Aggiunto :) – lad2025

0

In SQL Server 2012+, si può usare la somma cumulativa - molto più efficiente. In SQL Server 2008, si può fare questo usando una subquery correlata o cross apply:

select t.*, 
     sum(t.Revenue*1.0)/sum(t.Revenue) over (partition by user) as [% of Total], 
     sum(RunningRevenue*1.0)/sum(t.Revenue) over (partition by user) as [Running Total %] 
from t cross apply 
    (select sum(Revenue) as RunningRevenue 
     from t t2 
     where t2.Revenue >= t.Revenue and t2.user = t.user 
    ) t2; 

Nota: Il *1.0 è solo nel caso in cui Revenue memorizzato come un intero. SQL Server esegue la divisione integer, che restituisce 0 per entrambe le colonne su quasi tutte le righe.

EDIT:

Aggiungi where user = 'James' se si desidera ottenere risultati solo per James.

+0

La colonna '[% di Total]' sembra funzionare, ma solo per un singolo utente, il totale parziale sembra essere dappertutto. – bendataclear

+0

@ bendataclear. . . La tua domanda originale aveva solo un utente. È banale aggiustarlo per eseguire totali per un singolo utente. E molto più semplice della risposta del ragazzo. –

+0

Il primo 'sum' intorno a 't.Revenue' non è necessario. Non funzionerà perché non c'è 'GROUP BY' (o mi manca qualcosa). Il secondo 'utente' dovrebbe essere citato' [utente] 'altrimenti si otterrà un errore. Terzo: 'SUM OVER()' calcola la percentuale per intero non tabella non per 'utente'. E non c'è nessun filtro. – lad2025

Problemi correlati