转换测量单位

Dav*_*vis 10 postgresql plpgsql postgresql-9.1 number-formatting

希望为一系列物质计算最合适的计量单位,其中物质以不同(但兼容)的单位体积给出。

单位换算表

单位转换表存储各种单位以及这些单位之间的关系:

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
Run Code Online (Sandbox Code Playgroud)

按系数排序表明将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)
)
Run Code Online (Sandbox Code Playgroud)

应该有一个外键 from parent_idto id

物质表

物质表列出了特定数量的物质。例如:

 id  unit          label     quantity
 1   "microlitre"  mercury   5
 2   "millilitre"  water     500
 3   "centilitre"  water     2
 4   "microlitre"  mercury   10
 5   "millilitre"  water     600
Run Code Online (Sandbox Code Playgroud)

该表可能类似于:

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)
)
Run Code Online (Sandbox Code Playgroud)

问题

您将如何创建一个查询,该查询使用具有整数(和可选的实分量)的最少数字来查找表示物质总和的测量值?

例如,您将如何返回:

  quantity  unit        label
        15  microlitre  mercury 
       112  centilitre  water
Run Code Online (Sandbox Code Playgroud)

但不是:

  quantity  unit        label
        15  microlitre  mercury 
      1.12  litre       water
Run Code Online (Sandbox Code Playgroud)

因为 112 的实数比 1.12 少,而 112 比 1120 小。但在某些情况下,使用实数更短——例如 1.1 升 vs 110 厘升。

大多数情况下,我无法根据递归关系选择正确的单位。

源代码

到目前为止,我有(显然不工作):

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

想法

这是否需要使用 log 10来确定位数?

约束

这些单位并不都是十的幂。例如:http : //unitsofmeasure.org/ucum-essence.xml

mus*_*cio 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);
Run Code Online (Sandbox Code Playgroud)

但似乎可以解决问题:

|   LABEL | NORM_QTY | VAL |       UNIT |
-----------------------------------------
| mercury |   1.5e-8 |  15 | microlitre |
|   water |  0.00112 | 112 | centilitre |
Run Code Online (Sandbox Code Playgroud)

您实际上并不需要表中的父子关系unit_conversion,因为同一个系列中的单位自然会按照 的顺序相互关联coefficient,只要您确定了系列即可。