Alternativa a Self Join


10

Ho fatto una domanda qui: /programming/43807566/how-to-divide-two-values-from-the-same-column-but-at-different-rows

sulla divisione dei valori dalla stessa tabella, nella stessa colonna ma su righe diverse. Ora ho il problema di avere più numeratori e denominatori (con differenti uns). È ancora self joinun buon modo per risolvere questo problema con Postgres o ci sono soluzioni migliori?

Esempio:

| postcode | value | uns |
|----------|-------|-----|
|       AA |    40 |  53 |
|       BB |    20 |  53 |
|       AA |    10 |  54 |
|       AA |    20 |  55 |
|       AA |    10 |  56 |
|       AA |    30 |  57 |
|       AA |    50 |  58 |
|       BB |    10 |  54 |
|       BB |    10 |  55 |
|       BB |    70 |  56 |
|       BB |    80 |  57 |
|       BB |    10 |  58 |

Il risultato dovrebbe essere:

| postcode | formula    |
|----------|------------|
|       AA | 18.888...  |
|       BB | 14.375     |

Dove il valore è raggruppato per codice postale e la formula è (valore con uns):

(V53 * V56 + V54 * V57 + V55 * V58) / (V56 + V57 + V58)

Prestare attenzione per evitare l'eventuale divisione per zero. La formula può essere ancora più complessa ma questo è un buon esempio.


c'è un campo sul tuo tavolo che contrassegna quali righe sono numeratori e denominatori?
McNets,

no, denominatore è la somma di valori con uns 56, 57, 58.
Randomizza

Sembra che la soluzione migliore sarebbe quella di ruotare i dati in modo che unsdiventino i nomi delle colonne - da lì, qualunque formula usi i valori dovrebbe diventare fattibile. La formula sarà codificata o derivata in modo dinamico in qualche modo?
RDFozz,

ci sono alcune formule (~ 30) che serviranno per creare troppe tabelle
Randomizza

Risposte:


3

Questo è un problema pivot / crosstab al suo interno, come Michael ha già diagnosticato con precisione.

Se non hai familiarità con il tablefuncmodulo in Postgres, leggi le istruzioni di base qui:

La query diventa semplice e molto veloce (più veloce di altre soluzioni presentate qui):

SELECT (v53 * v56 + v54 * v57 + v55 * v58) / NULLIF(v56 + v57 + v58, 0)
FROM   crosstab(
   'SELECT postcode, uns, value FROM tbl ORDER BY 1'
 , 'SELECT generate_series(53,58)'
   ) AS ct (postcode text
          , v53 numeric, v54 numeric, v55 numeric
          , v56 numeric, v57 numeric, v58 numeric);

NULLIF per impedire la divisione per zero.

dbfiddle qui


6

È possibile aggregare tutte le coppie uns / value in un oggetto JSON, quindi utilizzarlo per accedere ai valori UNS per nome. Ciò richiede un po 'di casting poiché i valori possono essere estratti solo come testo dall'oggetto JSON, ma la formula sembra molto simile alla tua descrizione quindi:

with vals(postcode, v) as (
  select postcode, json_object_agg(uns, value)
  from x
  group by postcode
), factors (postcode, denominator, divisor) as (
  select postcode, 
         (v->>'53')::decimal * (v->>'56')::decimal + (v->>'54')::decimal * (v->>'57')::decimal + (v->>'55')::decimal * (v->>'58')::decimal,
         (v->>'56')::decimal + (v->>'57')::decimal + (v->>'58')::decimal
  from vals
)
select postcode, 
       denominator / nullif(divisor, 0)
from factors;

Ho diviso l'aggregazione, la valutazione del denominatore e del divisore e la divisione finale in tre fasi per renderla più leggibile.

Esempio online: http://rextester.com/IZYT54566


Puoi semplificare la formula creando una funzione:

create function val(p_vals json, p_uns text)
  returns decimal
as $$
  select (p_vals ->> p_uns)::decimal;
$$
language sql;

with vals (postcode, v) as (
  select postcode, json_object_agg(uns, value)
  from x
  group by postcode
), factors (postcode, denominator, divisor) as (
  select postcode, 
         val(v, '53') * val(v, '56') + val(v, '54') * val(v, '57') + val(v, '55') * val(v, '58'),
         val(v, '56') + val(v, '57') + val(v, '58')
  from vals
)
select postcode, 
       denominator / nullif(divisor, 0)
from factors;

4

Il modello PIVOT funzionerebbe per questo. Converte i valori delle righe in colonne in un'unica riga, in base alla loro chiave comune. Ci sono alcuni modi per implementarlo. Alcuni richiedono una sola scansione della tabella.

Dopo il PIVOT avresti una tabella con una riga per codice postale e una colonna per valore. Il resto della query verrebbe scritto come se facesse riferimento a una singola tabella.


3

Supponendo che (postcode, uns)siano UNIQUE(probabilmente, un PK), il modello PIVOT, come già commentato da @ michael-green, può essere implementato in modo portabile usando la seguente query:

SELECT
     postcode, 
     CAST(V53 * V56 + V54 * V57 + V55 * V58 AS numeric) 
         / nullif(V56 + V57 + V58, 0) AS formula
FROM
    (SELECT
         postcode,
         sum(case when uns=53 then value end) AS v53,     
         sum(case when uns=54 then value end) AS v54,     
         sum(case when uns=55 then value end) AS v55,     
         sum(case when uns=56 then value end) AS v56,
         sum(case when uns=57 then value end) AS v57,
         sum(case when uns=58 then value end) AS v58
    FROM
         t
    GROUP BY
         postcode
    ) AS s
ORDER BY
    postcode ;

Controllalo su SQLFiddle .


3

Supponendo che (postcode, uns)siano UNIQUE(probabilmente, un PK), probabilmente il modo più semplice, probabilmente il più portatile, anche se probabilmente non ottimale: utilizzare tutte le sottoselezioni necessarie :

SELECT
    postcode,
    ((SELECT value FROM t WHERE t.uns = 53 AND t.postcode = p.postcode) *
     (SELECT value FROM t WHERE t.uns = 56 AND t.postcode = p.postcode) +
     (SELECT value FROM t WHERE t.uns = 54 AND t.postcode = p.postcode) *
     (SELECT value FROM t WHERE t.uns = 57 AND t.postcode = p.postcode) +
     (SELECT value FROM t WHERE t.uns = 55 AND t.postcode = p.postcode) *
     (SELECT value FROM t WHERE t.uns = 58 AND t.postcode = p.postcode)
    )::double precision / 
     nullif( (SELECT sum(value) FROM t 
              WHERE t.uns IN (56, 57, 58) AND t.postcode = p.postcode), 0)
    AS formula
FROM
    (SELECT DISTINCT postcode FROM t) AS p
ORDER BY
    postcode ;

Controlla su SQLFiddle .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.