自己結合の代替


10

私はここで質問をしました:https//stackoverflow.com/questions/43807566/how-to-divide-two-values-from-the-same-column-but-at-different-rows

同じ列の異なる行にある同じテーブルの値の分割について。今、私はより多くの分子と分母(異なるuns)を持っているという問題を抱えています。self joinPostgresでこの問題を解決する良い方法はまだありますか、それともより良い解決策がありますか?

例:

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

ゼロによる最終的な除算を回避するように注意を払います。数式はさらに複雑になる可能性がありますが、それは良い例です。


どの行が分子および分母であるかを示すテーブル上のフィールドがありますか?
McNets

いいえ、分母はuns 56、57、58の値の合計です
ランダム化

最良の解決策のように聞こえるのは、データをピボットしunsて列名になるようにすることです。そこから、値を使用するすべての式が機能するようになります。数式はハードコードされますか、それとも何らかの形で動的に導出されますか?
RDFozz

あまりにも多くのテーブルを作成するために必要ないくつかの式(〜30)があります
Randomize

回答:


3

これは、Michaelがすでに正確に診断したように、根本的にピボット/クロス集計の問題です。

tablefuncPostgres のモジュールに慣れていない場合は、こちらの基本的な手順をお読みください:

クエリは単純で非常に高速になります(ここで紹介する他のソリューションよりも高速です)。

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


6

すべての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;

4

これにはPIVOTパターンが有効です。共通キーに従って、行の値を単一行の列に変換します。これを実装するにはいくつかの方法があります。いくつかは単一のテーブルスキャンのみを必要とします。

PIVOTの後は、郵便番号ごとに1行、値ごとに1列のテーブルができます。クエリの残りの部分は、単一のテーブルを参照するかのように記述されます。


3

これ(postcode, uns)UNIQUE(おそらくPK)であると仮定すると、@ michael-greenによって既にコメントされているPIVOTパターンは、次のクエリを使用して移植可能に実装できます。

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で確認してください


3

それ(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で確認してください

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.