Alternative zu Self Join

10

Ich habe hier eine Frage gestellt: /programming/43807566/how-to-divide-two-values-from-the-same-column-but-at-different-rows

Informationen zum Teilen von Werten aus derselben Tabelle in derselben Spalte, jedoch in verschiedenen Zeilen. Jetzt habe ich das Problem, wo ich mehr Zähler und Nenner (mit unterschiedlichen uns) habe. Ist dies immer noch self joinein guter Weg, um dieses Problem mit Postgres zu lösen, oder gibt es bessere Lösungen?

Beispiel:

| 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 |

Ergebnis sollte sein:

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

Wo die Werte nach Postleitzahl gruppiert sind und die Formel lautet (Wert mit uns):

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

Achten Sie darauf, eine eventuelle Division durch Null zu vermeiden. Die Formel kann noch komplexer sein, aber das ist ein gutes Beispiel.

Randomisieren
quelle
Gibt es in Ihrer Tabelle ein Feld, das angibt, welche Zeilen Zähler und Nenner sind?
McNets
nein, Nenner ist die Summe der Werte mit uns 56, 57, 58.
Randomize
Klingt so, als ob die beste Lösung darin besteht, die Daten so zu schwenken, dass sie unszu Spaltennamen werden - von dort aus sollte jede Formel, die die Werte verwendet, funktionsfähig werden. Wird die Formel fest codiert oder irgendwie dynamisch abgeleitet?
RDFozz
Es gibt einige Formeln (~ 30), die benötigt werden, um zu viele Tabellen zu erstellen
Randomize

Antworten:

3

Dies ist im Kern ein Pivot / Crosstab- Problem, wie Michael es bereits genau diagnostiziert hat .

Wenn Sie mit dem tablefuncModul in Postgres nicht vertraut sind , lesen Sie hier die grundlegenden Anweisungen:

Die Abfrage wird einfach und sehr schnell (schneller als andere hier vorgestellte Lösungen):

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 Division durch Null zu verhindern.

dbfiddle hier

Erwin Brandstetter
quelle
6

Sie können alle uns / value-Paare zu einem JSON-Objekt zusammenfassen und dann damit nach Namen auf die UNS-Werte zugreifen. Dies erfordert einige Änderungen, da die Werte nur als Text aus dem JSON-Objekt extrahiert werden können. Die Formel sieht dann jedoch Ihrer Beschreibung sehr ähnlich:

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;

Ich habe die Aggregation, die Bewertung des Nenners und des Teilers und die endgültige Unterteilung in drei Schritte unterteilt, um die Lesbarkeit zu verbessern.

Online-Beispiel: http://rextester.com/IZYT54566


Sie können die Formel vereinfachen, indem Sie eine Funktion erstellen:

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;
ein Pferd ohne Name
quelle
4

Das PIVOT-Muster würde dafür funktionieren. Es konvertiert die Werte von Zeilen in Spalten in einer einzelnen Zeile entsprechend ihrem gemeinsamen Schlüssel. Es gibt einige Möglichkeiten , dies zu implementieren. Einige erfordern nur einen einzigen Tabellenscan.

Nach dem PIVOT hätten Sie eine Tabelle mit einer Zeile pro Postleitzahl und einer Spalte pro Wert. Der Rest der Abfrage würde so geschrieben, als würde sie auf eine einzelne Tabelle verweisen.

Michael Green
quelle
3

Unter der Annahme, dass (postcode, uns)es sich UNIQUE(wahrscheinlich um eine PK) handelt, kann das PIVOT-Muster, wie bereits von @ michael-green kommentiert , mithilfe der folgenden Abfrage portabel implementiert werden :

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 ;

Überprüfen Sie es bei SQLFiddle .

joanolo
quelle
3

Angenommen, dies (postcode, uns)ist UNIQUE(wahrscheinlich eine PK), wahrscheinlich der einfachste Weg, wahrscheinlich der tragbarste, obwohl wahrscheinlich nicht der optimale: Verwenden Sie so viele Unterauswahlen wie nötig :

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 ;

Überprüfen Sie bei SQLFiddle .

joanolo
quelle