다른 통화로 가격을 효율적으로 비교


10

사용자가 가격 범위 내에서 제품을 검색 할 수 있도록하고 싶습니다. 사용자는 제품에 의해 설정된 통화에 관계없이 모든 통화 (USD, EUR, GBP, JPY, ...)를 사용할 수 있어야합니다. 따라서 제품 가격은 200USD이며 사용자가 100EUR-200EUR 비용의 제품을 검색하면 여전히 찾을 수 있습니다. 빠르고 효과적으로 만드는 방법?

여기 내가 지금까지 한 일이 있습니다. 나는를 저장 price, currency code그리고 calculated_price기본 통화 인 유로 (EUR)의 가격입니다.

CREATE TABLE "products" (
  "id" serial,
  "price" numeric NOT NULL,
  "currency" char(3),
  "calculated_price" numeric NOT NULL,
  CONSTRAINT "products_id_pkey" PRIMARY KEY ("id")
);

CREATE TABLE "currencies" (
  "id" char(3) NOT NULL,
  "modified" timestamp NOT NULL,
  "is_default" boolean NOT NULL DEFAULT 'f',
  "value" numeric NOT NULL,       -- ratio additional to the default currency
  CONSTRAINT "currencies_id_pkey" PRIMARY KEY ("id")
);

INSERT INTO "currencies" (id, modified, is_default, value)
  VALUES
  ('EUR', '2012-05-17 11:38:45', 't', 1.0),
  ('USD', '2012-05-17 11:38:45', 'f', '1.2724'),
  ('GBP', '2012-05-17 11:38:45', 'f', '0.8005');

INSERT INTO "products" (price, currency, calculated_price)
  SELECT 200.0 AS price, 'USD' AS currency, (200.0 / value) AS calculated_price
    FROM "currencies" WHERE id = 'USD';

사용자가 다른 통화로 검색하는 경우 USD라고 가정하면 EUR로 가격을 계산하고 calculated_price열을 검색합니다 .

SELECT * FROM "products" WHERE calculated_price > 100.0 AND calculated_price < 200.0;

이러한 방식으로 모든 행의 실제 가격을 한 번 계산하기 때문에 계산할 필요가 없기 때문에 가격을 매우 빠르게 비교할 수 있습니다.

나쁜 점은 default_price환율이 변경 되었기 때문에 적어도 매일 모든 행에 대해 를 다시 계산해야한다는 것입니다 .

이것을 처리하는 더 좋은 방법이 있습니까?

다른 영리한 솔루션이 없습니까? 아마도 수학 공식? 나는 calculated_price변수가 일부 변수에 대한 비율 이라는 것을 알고 X있으며 통화가 변경되면 우리가 X아닌 해당 변수 만 업데이트 calculated_price하므로 아무것도 업데이트하지 않아도됩니다 (행) ... 어쩌면 일부 수학자가 해결할 수 있습니다. 이처럼?

답변:


4

다음 calculated_price은 엄격하게 필요한 것이 아니라 다시 계산하는 것이 최적화에 대한 다른 접근법입니다 .

currencies테이블에 다른 열을 추가 한다고 가정합니다.이 열에 는 언제 이런 상황이 발생했는지에 관계없이 마지막으로 업데이트 된 last_rate시점의 환율이 포함되어 있습니다 calculated_price.

원하는 결과 를 포함 하여 50 USD에서 100 USD 사이의 가격대를 가진 제품 세트를 신속하게 검색하려면 다음과 같이 할 수 있습니다.

  SELECT * FROM products
   WHERE calculated_price > 50.0/(:last_rate*
    (SELECT coalesce(max(value/last_rate),1) FROM currencies
      WHERE value>last_rate))
   AND calculated_price < 100.0/ (:last_rate*
    (SELECT coalesce(min(value/last_rate),1) FROM currencies
      WHERE value<last_rate))

여기서 :last_rate마지막 업데이트 시점의 EUR / USD 환율이 포함됩니다. 아이디어는 모든 통화의 최대 변동을 고려하기 위해 간격을 늘리는 것입니다. 구간의 양쪽 끝에 대한 증가 요인은 요율 업데이트간에 일정하므로 사전 계산 될 수 있습니다.

금리가 단기간에 걸쳐 약간만 변하기 때문에, 위의 쿼리는 최종 결과에 가까운 근사치를 제공 할 것입니다. 최종 결과를 얻으려면 마지막 업데이트 이후 요율 변경으로 인해 가격이 범위를 벗어난 제품을 필터링하십시오 calculated_price.

  WITH p AS (
   SELECT * FROM products
   WHERE calculated_price > 50.0/(:last_rate*
    (SELECT coalesce(max(value/last_rate),1) FROM currencies
      WHERE value>last_rate))
   AND calculated_price < 100.0/ (:last_rate*
    (SELECT coalesce(min(value/last_rate),1) FROM currencies
      WHERE value<last_rate))
  )
  SELECT price,c.value FROM p join currencies c on (p.currency=c.id)
     WHERE price/c.value>50/:current_rate
       AND price/c.value<100/:current_rate;

어디 :current_rate에서 사용자가 선택한 돈에 대한 EUR의 최신 요금입니다.

효율성은 비율의 범위가 작고 값이 서로 가깝다는 사실에서 비롯됩니다.


2

이것은 구체화 된보기의 작업처럼 들립니다. PostgreSQL은 명시 적으로 지원하지 않지만 일반 테이블의 함수와 트리거를 사용하여 구체화 된 뷰를 생성하고 유지 관리 할 수 ​​있습니다.

나는 :

  • products_summary현재 products테이블 의 스키마를 사용하여 새 테이블을 만듭니다 (예 :).
  • ALTER TABLE products DROP COLUMN calculated_pricecalculated_price열을 제거하기 위해products
  • 당신이 원하는 출력을 생성하는 뷰 쓰기 products_summarySELECT에서 보내고 productsJOIN에 보내고을 currencies. 나는 그것을 부르지 products_summary_dynamic만 이름은 당신에게 달려 있습니다. 원하는 경우보기 대신 함수를 사용할 수 있습니다.
  • 주기적으로 구체화 된 뷰 테이블을 새로 고치 products_summary에서 products_summary_dynamicBEGIN; TRUNCATE products_summary; INSERT INTO products_summary SELECT * FROM products_summary_dynamic; COMMIT;.
  • 크리에이트 AFTER INSERT OR UPDATE OR DELETE ON products유지 보수하는 트리거 프로 시저를 실행 트리거 products_summary에서 삭제 될 때 행을 삭제 테이블을 products추가하는 경우에 추가, products(에 의해 SELECT으로부터 보내고 products_summary_dynamic, 제품 변화를 자세히 설명 할 때이를 업데이트보기).

이 방법은 요약 테이블을 업데이트하는 트랜잭션 products_summary중에 독점 잠금을 수행 TRUNCATE ..; INSERT ...;합니다. 시간이 오래 걸리기 때문에 애플리케이션에서 중단이 발생하는 경우 대신 두 가지 버전의 products_summary테이블을 유지할 수 있습니다 . 사용 중이 아닌 트랜잭션을 업데이트하십시오ALTER TABLE products_summary RENAME TO products_summary_old; ALTER TABLE products_summary_new RENAME TO products_summary;


대안이지만 매우 난해한 접근법은 표현식 색인을 사용하는 것입니다. 이 방법으로 통화 테이블을 갱신하는 것은 가능성이 있기 때문에 부득이 동안 잠금을 필요로 DROP INDEX하고 CREATE INDEX내가 너무 자주 그것을 할 것입니다 -하지만 어떤 상황에 적합한 수 있습니다.

아이디어는 통화 변환을 IMMUTABLE함수 로 마무리하는 것입니다. 그건 때문에 IMMUTABLE당신이 주어진 인수에 대한 반환 값은 항상 동일하게 나타날 것이라고는 데이터베이스 엔진에 보장하고 미친 일 경우 반환 값 다르다 모든 종류의 작업을 수행하기 위해 무료로하는 것입니다. 함수를 호출하십시오 (예 :) to_euros(amount numeric, currency char(3)) returns numeric. 그러나 원하는대로 구현하십시오. CASE통화, 조회 테이블 등 으로 큰 성명서. 찾아보기 테이블을 사용하는 경우 아래 설명 된 경우를 제외하고 찾아보기 테이블을 변경해서는 안됩니다 .

다음 products과 같이 에 표현식 색인을 작성하십시오 .

CREATE INDEX products_calculated_price_idx
ON products( to_euros(price,currency) );

이제 다음과 같이 계산 된 가격으로 제품을 빠르게 검색 할 수 있습니다.

SELECT *
FROM products
WHERE to_euros(price,currency) BETWEEN $1 and $2;

이제 문제는 통화 테이블을 업데이트하는 방법이되었습니다. 여기서 중요한 것은 통화 테이블을 변경할 있다는 것입니다. 인덱스를 삭제하고 다시 작성하기 만하면됩니다.

BEGIN;

-- An exclusive lock will be held from here until commit:
DROP INDEX products_calculated_price_idx;
DROP FUNCTION to_euros(amount numeric, currency char(3)) CASCADE;

-- It's probably better to use a big CASE statement here
-- rather than selecting from the `currencies` table as shown.
-- You could dynamically regenerate the function with PL/PgSQL
-- `EXECUTE` if you really wanted.
--
CREATE FUNCTION to_euros(amount numeric, currency char(3))
RETURNS numeric LANGUAGE sql AS $$
SELECT $1 / value FROM currencies WHERE id = $2;
$$ IMMUTABLE;

-- This may take some time and will run with the exclusive lock
-- held.
CREATE INDEX products_calculated_price_idx
ON products( to_euros(price,currency) );

COMMIT;

불변 함수를 재정의하는 경우 함수를 사용하는 모든 항목 을 삭제 해야 함 을 강조하기 위해 위의 함수 만 삭제하고 재정의 합니다. 드롭을 사용하는 것이 가장 좋은 방법입니다.CASCADE

나는 구체화 된 견해가 더 나은 접근법이라고 강력하게 의심합니다. 확실히 더 안전합니다. 나는 주로 차기를 위해 이것을 포함시킵니다.


지금 나는 이것에 대해 생각하고 있습니다-왜 전혀 업데이트해야 calculated_price합니까? 나는 단지 initial_currency_value(오늘은 일정 통화 환율을) 저장하고 항상 그것에 대해 계산할 수 있습니다! 그리고 유로로 가격을 표시 할 때는 물론 실제 환율에 대해 계산하십시오. 내가 맞아? 아니면 보이지 않는 문제가 있습니까?
Taai

1

나는 내 생각을 생각 해냈다. 실제로 작동하는지 알려주세요!

문제입니다.

제품이 products테이블 에 추가 되면 가격이 기본 통화 (EUR)로 변환되어 calculated_price열에 저장 됩니다.

사용자는 모든 통화의 가격을 검색 (필터) 할 수 있기를 원합니다. 입력 가격을 기본 통화 (EUR)로 변환하고 calculated_price열과 비교하면 됩니다.

환율을 업데이트해야하므로 사용자는 새로운 환율로 검색 할 수 있습니다. 그러나 문제는 calculated_price효율적 으로 업데이트하는 방법 입니다.

해결책 (희망적으로).

calculated_price효율적 으로 업데이트하는 방법 .

하지마! :)

우리는 어제 통화료 ( 동일한 날짜 )를 그 통화 로만calculated_price 사용 하는 것입니다. 마치 ... 영원히! 매일 업데이트하지 않습니다. 가격을 비교 / 필터링 / 검색하기 전에 필요한 것은 오늘의 환율을 어제와 같이 취하는 것입니다.

그래서 calculated_price우리는 고정 날짜의 환율 만 사용합니다 (우리는 어제를 선택했습니다). 필요한 것은 오늘 가격을 어제 가격으로 변환하는 것입니다. 다시 말해 오늘 요율을 가져 와서 어제 요율로 변환합니다.

cash_in_euros * ( rate_newest / rate_fixed )

그리고 이것은 통화 테이블입니다.

CREATE TABLE "currencies" (
  "id" char(3) NOT NULL, -- currency code (EUR, USD, GBP, ...)
  "is_default" boolean NOT NULL DEFAULT 'f',

  -- Set once. If you update, update all database fields that depends on this.
  "rate_fixed" numeric NOT NULL, -- Currency rate against default currency
  "rate_fixed_updated" timestamp NOT NULL,

  -- Update as frequently as needed.
  "rate_newest" numeric NOT NULL, -- Currency rate against default currency
  "rate_newest_updated" timestamp NOT NULL,

  CONSTRAINT "currencies_id_pkey" PRIMARY KEY ("id")
);

200 USD의 제품을 추가하는 방법과 calculated_price계산 방법 : USD에서 최신 EUR 환율 및 고정 (구) 요금으로

INSERT INTO "products" (price, currency, calculated_price)
  SELECT
  200.0 AS price,
  'USD' AS currency,

  ((200.0 / rate_newest) * (rate_newest / rate_fixed)) AS calculated_price

    FROM "currencies" WHERE id = 'USD';

이것은 또한 클라이언트 측에서 미리 계산 될 수 있으며 이것이 내가 할 일입니다- calculated_price쿼리하기 전에 사용자 입력 가격을 호환 가능한 값으로 계산하십시오.SELECT * FROM products WHERE calculated_price > 100.0 AND calculated_price < 200.0;

결론.

이 아이디어는 몇 시간 전에 나에게 왔으며 현재이 솔루션에 대한 올바른지 확인하도록 요청하고 있습니다. 어떻게 생각해? 이것이 효과가 있습니까? 아니면 내가 잘못 했어?

이 모든 것을 이해하시기 바랍니다. 저는 영어를 모국어로 사용하지 않습니다. 또한 늦었고 피곤합니다. :)

최신 정보

문제를 해결하는 것처럼 보이지만 다른 문제를 소개합니다. 너무 나쁘다. :)


문제는 rate_newest / rate_fixed통화마다 통화가 다르기 때문에이 솔루션은 사용자가 선택한 돈을 검색 할 때만 고려한다는 것입니다. 다른 통화로 된 가격은 최신 요금과 비교되지 않습니다. 어떻게 든 제출 한 답변에 비슷한 문제가 있었지만 업데이트 된 버전에서 수정했다고 생각합니다.
Daniel Vérité

이 접근 방식에서 볼 수있는 주요 문제는 가격에 대한 데이터베이스 인덱스를 사용하지 않는다는 것입니다 (ORDER BY 계산 _ 가격 절).
rosenfeld
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.