自助加入的替代方法


10

我在这里提出了一个问题: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)

注意避免最终被零除。公式可能更复杂,但这是一个很好的例子。


表格上是否有任何字段标记哪些行是分子和分母?
McNets '17

不,分母是uns为56、57、58的值的总和。–
随机化

听起来最好的解决方案是将数据透视起来,使之uns成为列名-从那里开始,无论使用该值的公式如何,都应该可行。公式是硬编码的还是以某种方式动态得出的?
RDFozz

有一些公式(〜30)会用来创建太多表格
随机化

Answers:


3

这是一个枢纽/交叉表问题的核心,就像迈克尔已经准确诊断出的那样

如果您不熟悉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;

我将聚合,分母和除数的求值以及最后的划分分为三个步骤,以使其更具可读性。

在线示例: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之后,您将有一个表,每个邮政编码一行,每个值一列。查询的其余部分将被编写为好像引用了单个表一样。


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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.