แปลงหน่วยของการวัด


10

กำลังมองหาการคำนวณหน่วยการวัดที่เหมาะสมที่สุดสำหรับรายการของสารที่ให้สารในปริมาณหน่วยที่แตกต่างกัน (แต่เข้ากันได้)

ตารางการแปลงหน่วย

ตารางแปลงหน่วยจัดเก็บหน่วยต่าง ๆ และความเกี่ยวข้องของหน่วยเหล่านั้น:

id  unit          coefficient                 parent_id
36  "microlitre"  0.0000000010000000000000000 37
37  "millilitre"  0.0000010000000000000000000 5
 5  "centilitre"  0.0000100000000000000000000 18
18  "decilitre"   0.0001000000000000000000000 34
34  "litre"       0.0010000000000000000000000 19
19  "dekalitre"   0.0100000000000000000000000 29
29  "hectolitre"  0.1000000000000000000000000 33
33  "kilolitre"   1.0000000000000000000000000 35
35  "megalitre"   1000.0000000000000000000000 0

การเรียงลำดับตามค่าสัมประสิทธิ์แสดงให้เห็นว่าการparent_idเชื่อมโยงหน่วยรองกับตัวเลขที่เหนือกว่า

ตารางนี้สามารถสร้างได้ใน PostgreSQL โดยใช้:

CREATE TABLE unit_conversion (
  id serial NOT NULL, -- Primary key.
  unit text NOT NULL, -- Unit of measurement name.
  coefficient numeric(30,25) NOT NULL DEFAULT 0, -- Conversion value.
  parent_id integer NOT NULL DEFAULT 0, -- Relates units in order of increasing measurement volume.
  CONSTRAINT pk_unit_conversion PRIMARY KEY (id)
)

ควรจะมีต่างประเทศที่สำคัญจากการparent_idid

ตารางสาร

ตารางสารแสดงปริมาณสารเฉพาะ ตัวอย่างเช่น:

 id  unit          label     quantity
 1   "microlitre"  mercury   5
 2   "millilitre"  water     500
 3   "centilitre"  water     2
 4   "microlitre"  mercury   10
 5   "millilitre"  water     600

ตารางอาจมีลักษณะ:

CREATE TABLE substance (
  id bigserial NOT NULL, -- Uniquely identifies this row.
  unit text NOT NULL, -- Foreign key to unit conversion.
  label text NOT NULL, -- Name of the substance.
  quantity numeric( 10, 4 ) NOT NULL, -- Amount of the substance.
  CONSTRAINT pk_substance PRIMARY KEY (id)
)

ปัญหา

คุณจะสร้างแบบสอบถามที่ค้นหาการวัดเพื่อแสดงผลรวมของสารโดยใช้ตัวเลขที่น้อยที่สุดที่มีจำนวนเต็ม (และส่วนประกอบที่เป็นทางเลือกจริง) ได้อย่างไร

ตัวอย่างเช่นคุณจะกลับมาอย่างไร:

  quantity  unit        label
        15  microlitre  mercury 
       112  centilitre  water

แต่ไม่:

  quantity  unit        label
        15  microlitre  mercury 
      1.12  litre       water

เนื่องจาก 112 มีตัวเลขจริงน้อยกว่า 1.12 และ 112 มีขนาดเล็กกว่า 1,120 แต่ในบางสถานการณ์การใช้ตัวเลขจริงจะสั้นกว่าเช่น 1.1 ลิตรเทียบกับ 110 centilitres

ส่วนใหญ่ฉันมีปัญหาในการเลือกหน่วยที่ถูกต้องตามความสัมพันธ์แบบเรียกซ้ำ

รหัสแหล่งที่มา

จนถึงตอนนี้ฉันมี (เห็นได้ชัดว่าไม่ทำงาน):

-- Normalize the quantities
select
  sum( coefficient * quantity ) AS kilolitres
from
  unit_conversion uc,
  substance s
where
  uc.unit = s.unit
group by
  s.label

ไอเดีย

จำเป็นต้องใช้ log 10เพื่อกำหนดจำนวนหลักหรือไม่

ข้อ จำกัด

หน่วยไม่ได้อยู่ในอำนาจของสิบ ตัวอย่างเช่น: http://unitsofmeasure.org/ucum-essence.xml


3
@ustaccio ฉันมีปัญหาเดียวกันกับที่ฉันเคยใช้ในระบบการผลิต ที่นั่นเราต้องคำนวณจำนวนเงินที่ใช้ในครัวจัดส่งอาหาร
dezso

2
ฉันจำ CTE ซ้ำอย่างน้อยสองระดับ ฉันคิดว่าฉันคำนวณผลรวมด้วยหน่วยที่เล็กที่สุดซึ่งปรากฏในรายการสสารที่ให้มาก่อนแล้วแปลงเป็นหน่วยที่ใหญ่ที่สุดที่ยังคงมีจำนวนเต็มส่วนที่ไม่เป็นศูนย์
dezso

1
ทุกหน่วยสามารถแปลงได้ด้วยพลังของ 10 หรือไม่? รายการหน่วยของคุณเสร็จสมบูรณ์หรือไม่
Erwin Brandstetter

คำตอบ:


2

มันดูน่าเกลียด:

  with uu(unit, coefficient, u_ord) as (
    select
     unit, 
     coefficient,
     case 
      when log(u.coefficient) < 0 
      then floor (log(u.coefficient)) 
      else ceil(log(u.coefficient)) 
     end u_ord
    from
     unit_conversion u 
  ),
  norm (label, norm_qty) as (
   select
    s.label,
    sum( uc.coefficient * s.quantity ) AS norm_qty
  from
    unit_conversion uc,
    substance s
  where
    uc.unit = s.unit
  group by
    s.label
  ),
  norm_ord (label, norm_qty, log, ord) as (
   select 
    label,
    norm_qty, 
    log(t.norm_qty) as log,
    case 
     when log(t.norm_qty) < 0 
     then floor(log(t.norm_qty)) 
     else ceil(log(t.norm_qty)) 
    end ord
   from norm t
  )
  select
   norm_ord.label,
   norm_ord.norm_qty,
   norm_ord.norm_qty / uu.coefficient val,
   uu.unit
  from 
   norm_ord,
   uu where uu.u_ord = 
     (select max(uu.u_ord) 
      from uu 
      where mod(norm_ord.norm_qty , uu.coefficient) = 0);

แต่ดูเหมือนว่าจะหลอกลวง:

|   LABEL | NORM_QTY | VAL |       UNIT |
-----------------------------------------
| mercury |   1.5e-8 |  15 | microlitre |
|   water |  0.00112 | 112 | centilitre |

คุณไม่จำเป็นต้องมีความสัมพันธ์ระหว่างพ่อแม่และลูกในunit_conversionตารางเพราะหน่วยในครอบครัวเดียวกันนั้นมีความเกี่ยวข้องกันตามคำสั่งcoefficientตราบใดที่คุณมีครอบครัวระบุ


2

ฉันคิดว่าสิ่งนี้สามารถทำให้ง่ายขึ้นส่วนใหญ่

1. แก้ไขunit_conversionตาราง

หรือถ้าคุณไม่สามารถปรับเปลี่ยนตารางได้เพียงเพิ่มคอลัมน์exp10สำหรับ "เลขชี้กำลัง 10 ฐาน" ซึ่งเกิดขึ้นพร้อมกับจำนวนตัวเลขที่จะเลื่อนในระบบทศนิยม:

CREATE TABLE unit_conversion(
   unit text PRIMARY KEY
  ,exp10 int
);

INSERT INTO unit_conversion VALUES
     ('microlitre', 0)
    ,('millilitre', 3)
    ,('centilitre', 4)
    ,('litre',      6)
    ,('hectolitre', 8)
    ,('kilolitre',  9)
    ,('megalitre',  12)
    ,('decilitre',  5);

2. ฟังก์ชั่นเขียน

เพื่อคำนวณจำนวนตำแหน่งที่จะเลื่อนไปทางซ้ายหรือขวา:

CREATE OR REPLACE FUNCTION f_shift_comma(n numeric)
  RETURNS int LANGUAGE SQL IMMUTABLE AS
$$
SELECT CASE WHEN ($1 % 1) = 0 THEN                    -- no fractional digits
          CASE WHEN ($1 % 10) = 0 THEN 0              -- no trailing 0, don't shift
          ELSE length(rtrim(trunc($1, 0)::text, '0')) -- trunc() because numeric can be 1.0
                   - length(trunc($1, 0)::text)       -- trailing 0, shift right .. negative
          END
       ELSE                                           -- fractional digits
          length(rtrim(($1 % 1)::text, '0')) - 2      -- shift left .. positive
       END
$$;

3. แบบสอบถาม

SELECT DISTINCT ON (substance_id)
       s.substance_id, s.label, s.quantity, s.unit
      ,COALESCE(s.quantity * 10^(u1.exp10 - u2.exp10)::numeric
              , s.quantity)::float8 AS norm_quantity
      ,COALESCE(u2.unit, s.unit) AS norm_unit
FROM   substance s 
JOIN   unit_conversion u1 USING (unit)
LEFT   JOIN unit_conversion u2 ON f_shift_comma(s.quantity) <> 0
                              AND @(u2.exp10 - (u1.exp10 - f_shift_comma(s.quantity))) < 2
                              -- since maximum gap between exp10 in unit table = 3
                              -- adapt to ceil(to max_gap / 2) if you have bigger gaps
ORDER  BY s.substance_id
     , @(u2.exp10 - (u1.exp10 - f_shift_comma(s.quantity))) -- closest unit first
     , u2.exp10    -- smaller unit first to avoid point for ties.

อธิบาย:

  • เข้าร่วมสารและตารางหน่วย
  • คำนวณจำนวนตำแหน่งในอุดมคติเพื่อเลื่อนโดยใช้ฟังก์ชันf_shift_comma()จากด้านบน
  • ซ้ายเข้าร่วมในตารางหน่วยเป็นครั้งที่สองเพื่อค้นหาหน่วยที่อยู่ใกล้กับที่เหมาะสมที่สุด
  • เลือกหน่วยที่อยู่ใกล้กับและDISTINCT ON ()ORDER BY
  • COALESCE()ถ้าไม่มีหน่วยที่ดีขึ้นพบถอยกลับไปในสิ่งที่เรามีกับ
  • นี้ควรครอบคลุมกรณีมุมและจะสวยได้อย่างรวดเร็ว

-> การสาธิตSQLfiddle


1
@DaveJarvis: และฉันคิดว่าฉันได้ครอบคลุมทุกอย่าง ... รายละเอียดนี้จะเป็นประโยชน์จริง ๆ ในคำถามที่สร้างขึ้นอย่างอื่น
Erwin Brandstetter
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.