私はここに質問をしました: https://stackoverflow.com/questions/43807566/how-to-divide-two-values-from-the-same-column-but-at-different-rows =
同じテーブルの値を同じ列で異なる行に分割することについて。今、私はより多くの分子と分母(異なるuns
)を持つ問題を抱えています。まだself join
Postgresでこの問題を解決する良い方法はありますか、それともより良い解決策がありますか?
例:
| 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 |
結果は次のようになります。
| postcode | formula |
|----------|------------|
| AA | 18.888... |
| BB | 14.375 |
値が郵便番号でグループ化され、式が(uns付きの値)である場合:
(V53 * V56 + V54 * V57 + V55 * V58) / (V56 + V57 + V58)
ゼロによる最終的な除算を回避するように注意を払います。数式はさらに複雑になる可能性がありますが、これは良い例です。
これは、コアでのpivot/crosstabの問題であり、 Michaelはすでに診断済み のように正確です。
Postgresの tablefunc
モジュールに慣れていない場合は、こちらの基本的な手順をお読みください:
クエリはシンプルになり、very高速になります(ここに示す他のソリューションよりも高速です)。
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
ゼロによる除算を防止します。
dbfiddle ここ
すべてのuns/valueペアをJSONオブジェクトに集約し、それを使用して名前でUNS値にアクセスできます。値はJSONオブジェクトからテキストとしてのみ抽出できるため、これにはいくつかのキャストが必要ですが、式は説明に非常に似ています。
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;
読みやすくするために、集計、分母と除数の評価、および最終的な分割を3つのステップに分けました。
オンラインの例: http://rextester.com/IZYT54566
関数を作成して式を簡略化できます。
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;
これにはPIVOTパターンが有効です。共通キーに従って、行の値を単一行の列に変換します。これを実装するには いくつかの方法 があります。単一のテーブルスキャンのみを必要とするものもあります。
PIVOTの後は、郵便番号ごとに1行、値ごとに1列のテーブルができます。クエリの残りの部分は、単一のテーブルを参照するかのように記述されます。
仮定して (postcode, uns)
はUNIQUE
(おそらくPK)であり、おそらく最も単純な方法であり、おそらく最も移植性の高い方法ですが、おそらく最適ではありません:必要な数の副選択を使用します:
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 ;
SQLFiddle で確認してください。
仮定して (postcode, uns)
はUNIQUE
(おそらくPK)です。PIVOTパターンは、@ michael-greenによってすでにコメントされているように、実装できますportably次のクエリを使用します。
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 ;
SQLFiddle で確認してください。