측정 단위 변환


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의 숫자가 1120보다 작습니다. 그러나 실제 상황을 사용하는 일부 상황에서는 1.1 리터 대 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

아이디어

자릿수를 결정하기 위해 로그 10 을 사용해야합니까 ?

제약

단위가 모두 10의 거듭 제곱이 아닙니다. 예를 들면 다음과 같습니다. http://unitsofmeasure.org/ucum-essence.xml


3
@mustaccio 나는 이전의 프로덕션 시스템에서 똑같은 문제를 겪었습니다. 거기서 우리는 음식 배달 주방에서 사용 된 양을 계산해야했습니다.
dezso 2016 년

2
나는 적어도 두 가지 수준의 재귀 CTE를 기억합니다. 나는 주어진 물질에 대한 목록에서 가장 작은 단위로 합계를 계산 한 다음 여전히 0이 아닌 정수 부분을 갖는 가장 큰 단위로 변환했다고 생각합니다.
dezso 2016 년

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 1
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.