ทางเลือกในการเข้าร่วมด้วยตนเอง


10

ฉันได้ถามคำถามที่นี่: /programming/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

ไม่ตัวส่วนเป็นผลรวมของค่าด้วย uns 56, 57, 58
สุ่ม

เสียงเหมือนทางออกที่ดีที่สุดคือการหมุนข้อมูลเพื่อให้unsกลายเป็นชื่อคอลัมน์ - จากที่นั่นสูตรใดก็ตามที่ใช้ค่าควรจะสามารถใช้การได้ สูตรจะมีการกำหนดค่าตายตัวหรือได้รับแบบไดนามิกหรือไม่?
RDFozz

มีสูตรไม่กี่ (~ 30) ซึ่งจะใช้ในการสร้างตารางมากเกินไป
สุ่ม

คำตอบ:


3

นี่เป็นปัญหาpivot / crosstabที่แกนกลางเช่นเดียวกับMichael วินิจฉัยอย่างถูกต้องแล้ว

หากคุณไม่คุ้นเคยกับtablefuncโมดูลใน Postgres อ่านคำแนะนำพื้นฐานที่นี่:

การสืบค้นกลายเป็นเรื่องง่ายและรวดเร็วมาก (เร็วกว่าโซลูชันอื่น ๆ ที่แสดงไว้ที่นี่):

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

คุณสามารถรวมคู่ที่ไม่ได้ / ค่าทั้งหมดลงในวัตถุ 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), รูปแบบ PIVOT ตามที่แสดงความคิดเห็นแล้วโดย @ michael-green สามารถนำมาใช้อย่างพกพาโดยใช้แบบสอบถามต่อไปนี้:

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.